mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 04:50:52 +08:00
feat(translators/claude): implement non-streaming response parsing for various translator types
- Added `ConvertCodexResponseToClaudeNonStream`, `ConvertGeminiCLIResponseToClaudeNonStream`, `ConvertGeminiResponseToClaudeNonStream`, and `ConvertOpenAIResponseToClaudeNonStream` methods for handling non-streaming JSON response conversion. - Introduced logic for parsing and structuring content, handling reasoning, text, and tool usage blocks. - Enhanced support for stop reasons and refined token usage data aggregation.
This commit is contained in:
@@ -7,9 +7,12 @@
|
|||||||
package claude
|
package claude
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
"github.com/tidwall/sjson"
|
"github.com/tidwall/sjson"
|
||||||
@@ -176,7 +179,172 @@ func ConvertCodexResponseToClaude(_ context.Context, _ string, originalRequestRa
|
|||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
// - string: A Claude Code-compatible JSON response containing all message content and metadata
|
// - string: A Claude Code-compatible JSON response containing all message content and metadata
|
||||||
func ConvertCodexResponseToClaudeNonStream(_ context.Context, _ string, originalRequestRawJSON, requestRawJSON, _ []byte, _ *any) string {
|
func ConvertCodexResponseToClaudeNonStream(_ context.Context, _ string, originalRequestRawJSON, _ []byte, rawJSON []byte, _ *any) string {
|
||||||
|
scanner := bufio.NewScanner(bytes.NewReader(rawJSON))
|
||||||
|
buffer := make([]byte, 10240*1024)
|
||||||
|
scanner.Buffer(buffer, 10240*1024)
|
||||||
|
revNames := buildReverseMapFromClaudeOriginalShortToOriginal(originalRequestRawJSON)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Bytes()
|
||||||
|
if !bytes.HasPrefix(line, dataTag) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
payload := bytes.TrimSpace(line[len(dataTag):])
|
||||||
|
if len(payload) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rootResult := gjson.ParseBytes(payload)
|
||||||
|
if rootResult.Get("type").String() != "response.completed" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData := rootResult.Get("response")
|
||||||
|
if !responseData.Exists() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"id": responseData.Get("id").String(),
|
||||||
|
"type": "message",
|
||||||
|
"role": "assistant",
|
||||||
|
"model": responseData.Get("model").String(),
|
||||||
|
"content": []interface{}{},
|
||||||
|
"stop_reason": nil,
|
||||||
|
"stop_sequence": nil,
|
||||||
|
"usage": map[string]interface{}{
|
||||||
|
"input_tokens": responseData.Get("usage.input_tokens").Int(),
|
||||||
|
"output_tokens": responseData.Get("usage.output_tokens").Int(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var contentBlocks []interface{}
|
||||||
|
hasToolCall := false
|
||||||
|
|
||||||
|
if output := responseData.Get("output"); output.Exists() && output.IsArray() {
|
||||||
|
output.ForEach(func(_, item gjson.Result) bool {
|
||||||
|
switch item.Get("type").String() {
|
||||||
|
case "reasoning":
|
||||||
|
thinkingBuilder := strings.Builder{}
|
||||||
|
if summary := item.Get("summary"); summary.Exists() {
|
||||||
|
if summary.IsArray() {
|
||||||
|
summary.ForEach(func(_, part gjson.Result) bool {
|
||||||
|
if txt := part.Get("text"); txt.Exists() {
|
||||||
|
thinkingBuilder.WriteString(txt.String())
|
||||||
|
} else {
|
||||||
|
thinkingBuilder.WriteString(part.String())
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
thinkingBuilder.WriteString(summary.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if thinkingBuilder.Len() == 0 {
|
||||||
|
if content := item.Get("content"); content.Exists() {
|
||||||
|
if content.IsArray() {
|
||||||
|
content.ForEach(func(_, part gjson.Result) bool {
|
||||||
|
if txt := part.Get("text"); txt.Exists() {
|
||||||
|
thinkingBuilder.WriteString(txt.String())
|
||||||
|
} else {
|
||||||
|
thinkingBuilder.WriteString(part.String())
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
thinkingBuilder.WriteString(content.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if thinkingBuilder.Len() > 0 {
|
||||||
|
contentBlocks = append(contentBlocks, map[string]interface{}{
|
||||||
|
"type": "thinking",
|
||||||
|
"thinking": thinkingBuilder.String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case "message":
|
||||||
|
if content := item.Get("content"); content.Exists() {
|
||||||
|
if content.IsArray() {
|
||||||
|
content.ForEach(func(_, part gjson.Result) bool {
|
||||||
|
if part.Get("type").String() == "output_text" {
|
||||||
|
text := part.Get("text").String()
|
||||||
|
if text != "" {
|
||||||
|
contentBlocks = append(contentBlocks, map[string]interface{}{
|
||||||
|
"type": "text",
|
||||||
|
"text": text,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
text := content.String()
|
||||||
|
if text != "" {
|
||||||
|
contentBlocks = append(contentBlocks, map[string]interface{}{
|
||||||
|
"type": "text",
|
||||||
|
"text": text,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "function_call":
|
||||||
|
hasToolCall = true
|
||||||
|
name := item.Get("name").String()
|
||||||
|
if original, ok := revNames[name]; ok {
|
||||||
|
name = original
|
||||||
|
}
|
||||||
|
|
||||||
|
toolBlock := map[string]interface{}{
|
||||||
|
"type": "tool_use",
|
||||||
|
"id": item.Get("call_id").String(),
|
||||||
|
"name": name,
|
||||||
|
"input": map[string]interface{}{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if argsStr := item.Get("arguments").String(); argsStr != "" {
|
||||||
|
var args interface{}
|
||||||
|
if err := json.Unmarshal([]byte(argsStr), &args); err == nil {
|
||||||
|
toolBlock["input"] = args
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentBlocks = append(contentBlocks, toolBlock)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(contentBlocks) > 0 {
|
||||||
|
response["content"] = contentBlocks
|
||||||
|
}
|
||||||
|
|
||||||
|
if stopReason := responseData.Get("stop_reason"); stopReason.Exists() && stopReason.String() != "" {
|
||||||
|
response["stop_reason"] = stopReason.String()
|
||||||
|
} else if hasToolCall {
|
||||||
|
response["stop_reason"] = "tool_use"
|
||||||
|
} else {
|
||||||
|
response["stop_reason"] = "end_turn"
|
||||||
|
}
|
||||||
|
|
||||||
|
if stopSequence := responseData.Get("stop_sequence"); stopSequence.Exists() && stopSequence.String() != "" {
|
||||||
|
response["stop_sequence"] = stopSequence.Value()
|
||||||
|
}
|
||||||
|
|
||||||
|
if responseData.Get("usage.input_tokens").Exists() || responseData.Get("usage.output_tokens").Exists() {
|
||||||
|
response["usage"] = map[string]interface{}{
|
||||||
|
"input_tokens": responseData.Get("usage.input_tokens").Int(),
|
||||||
|
"output_tokens": responseData.Get("usage.output_tokens").Int(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
responseJSON, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(responseJSON)
|
||||||
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ package claude
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
@@ -251,6 +253,126 @@ func ConvertGeminiCLIResponseToClaude(_ context.Context, _ string, originalReque
|
|||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
// - string: A Claude-compatible JSON response.
|
// - string: A Claude-compatible JSON response.
|
||||||
func ConvertGeminiCLIResponseToClaudeNonStream(_ context.Context, _ string, originalRequestRawJSON, requestRawJSON, _ []byte, _ *any) string {
|
func ConvertGeminiCLIResponseToClaudeNonStream(_ context.Context, _ string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, _ *any) string {
|
||||||
|
_ = originalRequestRawJSON
|
||||||
|
_ = requestRawJSON
|
||||||
|
|
||||||
|
root := gjson.ParseBytes(rawJSON)
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"id": root.Get("response.responseId").String(),
|
||||||
|
"type": "message",
|
||||||
|
"role": "assistant",
|
||||||
|
"model": root.Get("response.modelVersion").String(),
|
||||||
|
"content": []interface{}{},
|
||||||
|
"stop_reason": nil,
|
||||||
|
"stop_sequence": nil,
|
||||||
|
"usage": map[string]interface{}{
|
||||||
|
"input_tokens": root.Get("response.usageMetadata.promptTokenCount").Int(),
|
||||||
|
"output_tokens": root.Get("response.usageMetadata.candidatesTokenCount").Int() + root.Get("response.usageMetadata.thoughtsTokenCount").Int(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := root.Get("response.candidates.0.content.parts")
|
||||||
|
var contentBlocks []interface{}
|
||||||
|
textBuilder := strings.Builder{}
|
||||||
|
thinkingBuilder := strings.Builder{}
|
||||||
|
toolIDCounter := 0
|
||||||
|
hasToolCall := false
|
||||||
|
|
||||||
|
flushText := func() {
|
||||||
|
if textBuilder.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
contentBlocks = append(contentBlocks, map[string]interface{}{
|
||||||
|
"type": "text",
|
||||||
|
"text": textBuilder.String(),
|
||||||
|
})
|
||||||
|
textBuilder.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
flushThinking := func() {
|
||||||
|
if thinkingBuilder.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
contentBlocks = append(contentBlocks, map[string]interface{}{
|
||||||
|
"type": "thinking",
|
||||||
|
"thinking": thinkingBuilder.String(),
|
||||||
|
})
|
||||||
|
thinkingBuilder.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
if parts.IsArray() {
|
||||||
|
for _, part := range parts.Array() {
|
||||||
|
if text := part.Get("text"); text.Exists() && text.String() != "" {
|
||||||
|
if part.Get("thought").Bool() {
|
||||||
|
flushText()
|
||||||
|
thinkingBuilder.WriteString(text.String())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
flushThinking()
|
||||||
|
textBuilder.WriteString(text.String())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if functionCall := part.Get("functionCall"); functionCall.Exists() {
|
||||||
|
flushThinking()
|
||||||
|
flushText()
|
||||||
|
hasToolCall = true
|
||||||
|
|
||||||
|
name := functionCall.Get("name").String()
|
||||||
|
toolIDCounter++
|
||||||
|
toolBlock := map[string]interface{}{
|
||||||
|
"type": "tool_use",
|
||||||
|
"id": fmt.Sprintf("tool_%d", toolIDCounter),
|
||||||
|
"name": name,
|
||||||
|
"input": map[string]interface{}{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if args := functionCall.Get("args"); args.Exists() {
|
||||||
|
var parsed interface{}
|
||||||
|
if err := json.Unmarshal([]byte(args.Raw), &parsed); err == nil {
|
||||||
|
toolBlock["input"] = parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentBlocks = append(contentBlocks, toolBlock)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flushThinking()
|
||||||
|
flushText()
|
||||||
|
|
||||||
|
response["content"] = contentBlocks
|
||||||
|
|
||||||
|
stopReason := "end_turn"
|
||||||
|
if hasToolCall {
|
||||||
|
stopReason = "tool_use"
|
||||||
|
} else {
|
||||||
|
if finish := root.Get("response.candidates.0.finishReason"); finish.Exists() {
|
||||||
|
switch finish.String() {
|
||||||
|
case "MAX_TOKENS":
|
||||||
|
stopReason = "max_tokens"
|
||||||
|
case "STOP", "FINISH_REASON_UNSPECIFIED", "UNKNOWN":
|
||||||
|
stopReason = "end_turn"
|
||||||
|
default:
|
||||||
|
stopReason = "end_turn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response["stop_reason"] = stopReason
|
||||||
|
|
||||||
|
if usage := response["usage"].(map[string]interface{}); usage["input_tokens"] == int64(0) && usage["output_tokens"] == int64(0) {
|
||||||
|
if usageMeta := root.Get("response.usageMetadata"); !usageMeta.Exists() {
|
||||||
|
delete(response, "usage")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
return string(encoded)
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ package claude
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
@@ -245,6 +247,126 @@ func ConvertGeminiResponseToClaude(_ context.Context, _ string, originalRequestR
|
|||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
// - string: A Claude-compatible JSON response.
|
// - string: A Claude-compatible JSON response.
|
||||||
func ConvertGeminiResponseToClaudeNonStream(_ context.Context, _ string, originalRequestRawJSON, requestRawJSON, _ []byte, _ *any) string {
|
func ConvertGeminiResponseToClaudeNonStream(_ context.Context, _ string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, _ *any) string {
|
||||||
|
_ = originalRequestRawJSON
|
||||||
|
_ = requestRawJSON
|
||||||
|
|
||||||
|
root := gjson.ParseBytes(rawJSON)
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"id": root.Get("responseId").String(),
|
||||||
|
"type": "message",
|
||||||
|
"role": "assistant",
|
||||||
|
"model": root.Get("modelVersion").String(),
|
||||||
|
"content": []interface{}{},
|
||||||
|
"stop_reason": nil,
|
||||||
|
"stop_sequence": nil,
|
||||||
|
"usage": map[string]interface{}{
|
||||||
|
"input_tokens": root.Get("usageMetadata.promptTokenCount").Int(),
|
||||||
|
"output_tokens": root.Get("usageMetadata.candidatesTokenCount").Int() + root.Get("usageMetadata.thoughtsTokenCount").Int(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := root.Get("candidates.0.content.parts")
|
||||||
|
var contentBlocks []interface{}
|
||||||
|
textBuilder := strings.Builder{}
|
||||||
|
thinkingBuilder := strings.Builder{}
|
||||||
|
toolIDCounter := 0
|
||||||
|
hasToolCall := false
|
||||||
|
|
||||||
|
flushText := func() {
|
||||||
|
if textBuilder.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
contentBlocks = append(contentBlocks, map[string]interface{}{
|
||||||
|
"type": "text",
|
||||||
|
"text": textBuilder.String(),
|
||||||
|
})
|
||||||
|
textBuilder.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
flushThinking := func() {
|
||||||
|
if thinkingBuilder.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
contentBlocks = append(contentBlocks, map[string]interface{}{
|
||||||
|
"type": "thinking",
|
||||||
|
"thinking": thinkingBuilder.String(),
|
||||||
|
})
|
||||||
|
thinkingBuilder.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
if parts.IsArray() {
|
||||||
|
for _, part := range parts.Array() {
|
||||||
|
if text := part.Get("text"); text.Exists() && text.String() != "" {
|
||||||
|
if part.Get("thought").Bool() {
|
||||||
|
flushText()
|
||||||
|
thinkingBuilder.WriteString(text.String())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
flushThinking()
|
||||||
|
textBuilder.WriteString(text.String())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if functionCall := part.Get("functionCall"); functionCall.Exists() {
|
||||||
|
flushThinking()
|
||||||
|
flushText()
|
||||||
|
hasToolCall = true
|
||||||
|
|
||||||
|
name := functionCall.Get("name").String()
|
||||||
|
toolIDCounter++
|
||||||
|
toolBlock := map[string]interface{}{
|
||||||
|
"type": "tool_use",
|
||||||
|
"id": fmt.Sprintf("tool_%d", toolIDCounter),
|
||||||
|
"name": name,
|
||||||
|
"input": map[string]interface{}{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if args := functionCall.Get("args"); args.Exists() {
|
||||||
|
var parsed interface{}
|
||||||
|
if err := json.Unmarshal([]byte(args.Raw), &parsed); err == nil {
|
||||||
|
toolBlock["input"] = parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentBlocks = append(contentBlocks, toolBlock)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flushThinking()
|
||||||
|
flushText()
|
||||||
|
|
||||||
|
response["content"] = contentBlocks
|
||||||
|
|
||||||
|
stopReason := "end_turn"
|
||||||
|
if hasToolCall {
|
||||||
|
stopReason = "tool_use"
|
||||||
|
} else {
|
||||||
|
if finish := root.Get("candidates.0.finishReason"); finish.Exists() {
|
||||||
|
switch finish.String() {
|
||||||
|
case "MAX_TOKENS":
|
||||||
|
stopReason = "max_tokens"
|
||||||
|
case "STOP", "FINISH_REASON_UNSPECIFIED", "UNKNOWN":
|
||||||
|
stopReason = "end_turn"
|
||||||
|
default:
|
||||||
|
stopReason = "end_turn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response["stop_reason"] = stopReason
|
||||||
|
|
||||||
|
if usage := response["usage"].(map[string]interface{}); usage["input_tokens"] == int64(0) && usage["output_tokens"] == int64(0) {
|
||||||
|
if usageMeta := root.Get("usageMetadata"); !usageMeta.Exists() {
|
||||||
|
delete(response, "usage")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
return string(encoded)
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
|
"github.com/tidwall/sjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -450,6 +451,177 @@ func mapOpenAIFinishReasonToAnthropic(openAIReason string) string {
|
|||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
// - string: An Anthropic-compatible JSON response.
|
// - string: An Anthropic-compatible JSON response.
|
||||||
func ConvertOpenAIResponseToClaudeNonStream(_ context.Context, _ string, originalRequestRawJSON, requestRawJSON, _ []byte, _ *any) string {
|
func ConvertOpenAIResponseToClaudeNonStream(_ context.Context, _ string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, _ *any) string {
|
||||||
|
_ = originalRequestRawJSON
|
||||||
|
_ = requestRawJSON
|
||||||
|
|
||||||
|
root := gjson.ParseBytes(rawJSON)
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"id": root.Get("id").String(),
|
||||||
|
"type": "message",
|
||||||
|
"role": "assistant",
|
||||||
|
"model": root.Get("model").String(),
|
||||||
|
"content": []interface{}{},
|
||||||
|
"stop_reason": nil,
|
||||||
|
"stop_sequence": nil,
|
||||||
|
"usage": map[string]interface{}{
|
||||||
|
"input_tokens": 0,
|
||||||
|
"output_tokens": 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var contentBlocks []interface{}
|
||||||
|
hasToolCall := false
|
||||||
|
|
||||||
|
if choices := root.Get("choices"); choices.Exists() && choices.IsArray() && len(choices.Array()) > 0 {
|
||||||
|
choice := choices.Array()[0]
|
||||||
|
|
||||||
|
if finishReason := choice.Get("finish_reason"); finishReason.Exists() {
|
||||||
|
response["stop_reason"] = mapOpenAIFinishReasonToAnthropic(finishReason.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if message := choice.Get("message"); message.Exists() {
|
||||||
|
if contentArray := message.Get("content"); contentArray.Exists() && contentArray.IsArray() {
|
||||||
|
var textBuilder strings.Builder
|
||||||
|
var thinkingBuilder strings.Builder
|
||||||
|
|
||||||
|
flushText := func() {
|
||||||
|
if textBuilder.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
contentBlocks = append(contentBlocks, map[string]interface{}{
|
||||||
|
"type": "text",
|
||||||
|
"text": textBuilder.String(),
|
||||||
|
})
|
||||||
|
textBuilder.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
flushThinking := func() {
|
||||||
|
if thinkingBuilder.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
contentBlocks = append(contentBlocks, map[string]interface{}{
|
||||||
|
"type": "thinking",
|
||||||
|
"thinking": thinkingBuilder.String(),
|
||||||
|
})
|
||||||
|
thinkingBuilder.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range contentArray.Array() {
|
||||||
|
typeStr := item.Get("type").String()
|
||||||
|
switch typeStr {
|
||||||
|
case "text":
|
||||||
|
flushThinking()
|
||||||
|
textBuilder.WriteString(item.Get("text").String())
|
||||||
|
case "tool_calls":
|
||||||
|
flushThinking()
|
||||||
|
flushText()
|
||||||
|
toolCalls := item.Get("tool_calls")
|
||||||
|
if toolCalls.IsArray() {
|
||||||
|
toolCalls.ForEach(func(_, tc gjson.Result) bool {
|
||||||
|
hasToolCall = true
|
||||||
|
toolUse := map[string]interface{}{
|
||||||
|
"type": "tool_use",
|
||||||
|
"id": tc.Get("id").String(),
|
||||||
|
"name": tc.Get("function.name").String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
argsStr := util.FixJSON(tc.Get("function.arguments").String())
|
||||||
|
if argsStr != "" {
|
||||||
|
var parsed interface{}
|
||||||
|
if err := json.Unmarshal([]byte(argsStr), &parsed); err == nil {
|
||||||
|
toolUse["input"] = parsed
|
||||||
|
} else {
|
||||||
|
toolUse["input"] = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toolUse["input"] = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentBlocks = append(contentBlocks, toolUse)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case "reasoning":
|
||||||
|
flushText()
|
||||||
|
if thinking := item.Get("text"); thinking.Exists() {
|
||||||
|
thinkingBuilder.WriteString(thinking.String())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
flushThinking()
|
||||||
|
flushText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flushThinking()
|
||||||
|
flushText()
|
||||||
|
}
|
||||||
|
|
||||||
|
if toolCalls := message.Get("tool_calls"); toolCalls.Exists() && toolCalls.IsArray() {
|
||||||
|
toolCalls.ForEach(func(_, toolCall gjson.Result) bool {
|
||||||
|
hasToolCall = true
|
||||||
|
toolUseBlock := map[string]interface{}{
|
||||||
|
"type": "tool_use",
|
||||||
|
"id": toolCall.Get("id").String(),
|
||||||
|
"name": toolCall.Get("function.name").String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
argsStr := toolCall.Get("function.arguments").String()
|
||||||
|
argsStr = util.FixJSON(argsStr)
|
||||||
|
if argsStr != "" {
|
||||||
|
var args interface{}
|
||||||
|
if err := json.Unmarshal([]byte(argsStr), &args); err == nil {
|
||||||
|
toolUseBlock["input"] = args
|
||||||
|
} else {
|
||||||
|
toolUseBlock["input"] = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toolUseBlock["input"] = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentBlocks = append(contentBlocks, toolUseBlock)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response["content"] = contentBlocks
|
||||||
|
|
||||||
|
if respUsage := root.Get("usage"); respUsage.Exists() {
|
||||||
|
usageJSON := `{}`
|
||||||
|
usageJSON, _ = sjson.Set(usageJSON, "input_tokens", respUsage.Get("prompt_tokens").Int())
|
||||||
|
usageJSON, _ = sjson.Set(usageJSON, "output_tokens", respUsage.Get("completion_tokens").Int())
|
||||||
|
parsedUsage := gjson.Parse(usageJSON).Value().(map[string]interface{})
|
||||||
|
response["usage"] = parsedUsage
|
||||||
|
}
|
||||||
|
|
||||||
|
if response["stop_reason"] == nil {
|
||||||
|
if hasToolCall {
|
||||||
|
response["stop_reason"] = "tool_use"
|
||||||
|
} else {
|
||||||
|
response["stop_reason"] = "end_turn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasToolCall {
|
||||||
|
if toolBlocks := response["content"].([]interface{}); len(toolBlocks) > 0 {
|
||||||
|
for _, block := range toolBlocks {
|
||||||
|
if m, ok := block.(map[string]interface{}); ok && m["type"] == "tool_use" {
|
||||||
|
hasToolCall = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hasToolCall {
|
||||||
|
response["stop_reason"] = "tool_use"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
responseJSON, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
return string(responseJSON)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user