refactor(translator): replace client.Content structs with JSON-based content generation for more efficient handling of Claude requests

This commit is contained in:
Luis Pater
2025-12-17 16:39:32 +08:00
parent 4afe1f42ca
commit 9f2c278ee6

View File

@@ -7,10 +7,8 @@ package claude
import ( import (
"bytes" "bytes"
"encoding/json"
"strings" "strings"
client "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
"github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common" "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common"
"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"
@@ -42,27 +40,30 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
rawJSON = bytes.Replace(rawJSON, []byte(`"url":{"type":"string","format":"uri",`), []byte(`"url":{"type":"string",`), -1) rawJSON = bytes.Replace(rawJSON, []byte(`"url":{"type":"string","format":"uri",`), []byte(`"url":{"type":"string",`), -1)
// system instruction // system instruction
var systemInstruction *client.Content systemInstructionJSON := ""
hasSystemInstruction := false
systemResult := gjson.GetBytes(rawJSON, "system") systemResult := gjson.GetBytes(rawJSON, "system")
if systemResult.IsArray() { if systemResult.IsArray() {
systemResults := systemResult.Array() systemResults := systemResult.Array()
systemInstruction = &client.Content{Role: "user", Parts: []client.Part{}} systemInstructionJSON = `{"role":"user","parts":[]}`
for i := 0; i < len(systemResults); i++ { for i := 0; i < len(systemResults); i++ {
systemPromptResult := systemResults[i] systemPromptResult := systemResults[i]
systemTypePromptResult := systemPromptResult.Get("type") systemTypePromptResult := systemPromptResult.Get("type")
if systemTypePromptResult.Type == gjson.String && systemTypePromptResult.String() == "text" { if systemTypePromptResult.Type == gjson.String && systemTypePromptResult.String() == "text" {
systemPrompt := systemPromptResult.Get("text").String() systemPrompt := systemPromptResult.Get("text").String()
systemPart := client.Part{Text: systemPrompt} partJSON := `{}`
systemInstruction.Parts = append(systemInstruction.Parts, systemPart) if systemPrompt != "" {
partJSON, _ = sjson.Set(partJSON, "text", systemPrompt)
}
systemInstructionJSON, _ = sjson.SetRaw(systemInstructionJSON, "parts.-1", partJSON)
hasSystemInstruction = true
} }
} }
if len(systemInstruction.Parts) == 0 {
systemInstruction = nil
}
} }
// contents // contents
contents := make([]client.Content, 0) contentsJSON := "[]"
hasContents := false
messagesResult := gjson.GetBytes(rawJSON, "messages") messagesResult := gjson.GetBytes(rawJSON, "messages")
if messagesResult.IsArray() { if messagesResult.IsArray() {
messageResults := messagesResult.Array() messageResults := messagesResult.Array()
@@ -76,7 +77,8 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
if role == "assistant" { if role == "assistant" {
role = "model" role = "model"
} }
clientContent := client.Content{Role: role, Parts: []client.Part{}} clientContentJSON := `{"role":"","parts":[]}`
clientContentJSON, _ = sjson.Set(clientContentJSON, "role", role)
contentsResult := messageResult.Get("content") contentsResult := messageResult.Get("content")
if contentsResult.IsArray() { if contentsResult.IsArray() {
contentResults := contentsResult.Array() contentResults := contentsResult.Array()
@@ -90,25 +92,39 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
if signatureResult.Exists() { if signatureResult.Exists() {
signature = signatureResult.String() signature = signatureResult.String()
} }
clientContent.Parts = append(clientContent.Parts, client.Part{Text: prompt, Thought: true, ThoughtSignature: signature}) partJSON := `{}`
partJSON, _ = sjson.Set(partJSON, "thought", true)
if prompt != "" {
partJSON, _ = sjson.Set(partJSON, "text", prompt)
}
if signature != "" {
partJSON, _ = sjson.Set(partJSON, "thoughtSignature", signature)
}
clientContentJSON, _ = sjson.SetRaw(clientContentJSON, "parts.-1", partJSON)
} else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "text" { } else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "text" {
prompt := contentResult.Get("text").String() prompt := contentResult.Get("text").String()
clientContent.Parts = append(clientContent.Parts, client.Part{Text: prompt}) partJSON := `{}`
if prompt != "" {
partJSON, _ = sjson.Set(partJSON, "text", prompt)
}
clientContentJSON, _ = sjson.SetRaw(clientContentJSON, "parts.-1", partJSON)
} else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "tool_use" { } else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "tool_use" {
functionName := contentResult.Get("name").String() functionName := contentResult.Get("name").String()
functionArgs := contentResult.Get("input").String() functionArgs := contentResult.Get("input").String()
functionID := contentResult.Get("id").String() functionID := contentResult.Get("id").String()
var args map[string]any if gjson.Valid(functionArgs) {
if err := json.Unmarshal([]byte(functionArgs), &args); err == nil { argsResult := gjson.Parse(functionArgs)
if strings.Contains(modelName, "claude") { if argsResult.IsObject() {
clientContent.Parts = append(clientContent.Parts, client.Part{ partJSON := `{}`
FunctionCall: &client.FunctionCall{ID: functionID, Name: functionName, Args: args}, if !strings.Contains(modelName, "claude") {
}) partJSON, _ = sjson.Set(partJSON, "thoughtSignature", geminiCLIClaudeThoughtSignature)
} else { }
clientContent.Parts = append(clientContent.Parts, client.Part{ if functionID != "" {
FunctionCall: &client.FunctionCall{ID: functionID, Name: functionName, Args: args}, partJSON, _ = sjson.Set(partJSON, "functionCall.id", functionID)
ThoughtSignature: geminiCLIClaudeThoughtSignature, }
}) partJSON, _ = sjson.Set(partJSON, "functionCall.name", functionName)
partJSON, _ = sjson.SetRaw(partJSON, "functionCall.args", argsResult.Raw)
clientContentJSON, _ = sjson.SetRaw(clientContentJSON, "parts.-1", partJSON)
} }
} }
} else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "tool_result" { } else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "tool_result" {
@@ -121,41 +137,70 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
} }
functionResponseResult := contentResult.Get("content") functionResponseResult := contentResult.Get("content")
functionResponseJSON := `{}`
functionResponseJSON, _ = sjson.Set(functionResponseJSON, "id", toolCallID)
functionResponseJSON, _ = sjson.Set(functionResponseJSON, "name", funcName)
responseData := "" responseData := ""
if functionResponseResult.Type == gjson.String { if functionResponseResult.Type == gjson.String {
responseData = functionResponseResult.String() responseData = functionResponseResult.String()
functionResponseJSON, _ = sjson.Set(functionResponseJSON, "response.result", responseData)
} else if functionResponseResult.IsArray() {
frResults := functionResponseResult.Array()
if len(frResults) == 1 {
functionResponseJSON, _ = sjson.SetRaw(functionResponseJSON, "response.result", frResults[0].Raw)
} else {
functionResponseJSON, _ = sjson.SetRaw(functionResponseJSON, "response.result", functionResponseResult.Raw)
}
} else if functionResponseResult.IsObject() {
functionResponseJSON, _ = sjson.SetRaw(functionResponseJSON, "response.result", functionResponseResult.Raw)
} else { } else {
responseData = contentResult.Get("content").Raw functionResponseJSON, _ = sjson.SetRaw(functionResponseJSON, "response.result", functionResponseResult.Raw)
} }
functionResponse := client.FunctionResponse{ID: toolCallID, Name: funcName, Response: map[string]interface{}{"result": responseData}} partJSON := `{}`
clientContent.Parts = append(clientContent.Parts, client.Part{FunctionResponse: &functionResponse}) partJSON, _ = sjson.SetRaw(partJSON, "functionResponse", functionResponseJSON)
clientContentJSON, _ = sjson.SetRaw(clientContentJSON, "parts.-1", partJSON)
} }
} else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "image" { } else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "image" {
sourceResult := contentResult.Get("source") sourceResult := contentResult.Get("source")
if sourceResult.Get("type").String() == "base64" { if sourceResult.Get("type").String() == "base64" {
inlineData := &client.InlineData{ inlineDataJSON := `{}`
MimeType: sourceResult.Get("media_type").String(), if mimeType := sourceResult.Get("media_type").String(); mimeType != "" {
Data: sourceResult.Get("data").String(), inlineDataJSON, _ = sjson.Set(inlineDataJSON, "mime_type", mimeType)
} }
clientContent.Parts = append(clientContent.Parts, client.Part{InlineData: inlineData}) if data := sourceResult.Get("data").String(); data != "" {
inlineDataJSON, _ = sjson.Set(inlineDataJSON, "data", data)
}
partJSON := `{}`
partJSON, _ = sjson.SetRaw(partJSON, "inlineData", inlineDataJSON)
clientContentJSON, _ = sjson.SetRaw(clientContentJSON, "parts.-1", partJSON)
} }
} }
} }
contents = append(contents, clientContent) contentsJSON, _ = sjson.SetRaw(contentsJSON, "-1", clientContentJSON)
hasContents = true
} else if contentsResult.Type == gjson.String { } else if contentsResult.Type == gjson.String {
prompt := contentsResult.String() prompt := contentsResult.String()
contents = append(contents, client.Content{Role: role, Parts: []client.Part{{Text: prompt}}}) partJSON := `{}`
if prompt != "" {
partJSON, _ = sjson.Set(partJSON, "text", prompt)
}
clientContentJSON, _ = sjson.SetRaw(clientContentJSON, "parts.-1", partJSON)
contentsJSON, _ = sjson.SetRaw(contentsJSON, "-1", clientContentJSON)
hasContents = true
} }
} }
} }
// tools // tools
var tools []client.ToolDeclaration toolsJSON := ""
toolDeclCount := 0
toolsResult := gjson.GetBytes(rawJSON, "tools") toolsResult := gjson.GetBytes(rawJSON, "tools")
if toolsResult.IsArray() { if toolsResult.IsArray() {
tools = make([]client.ToolDeclaration, 1) toolsJSON = `[{"functionDeclarations":[]}]`
tools[0].FunctionDeclarations = make([]any, 0)
toolsResults := toolsResult.Array() toolsResults := toolsResult.Array()
for i := 0; i < len(toolsResults); i++ { for i := 0; i < len(toolsResults); i++ {
toolResult := toolsResults[i] toolResult := toolsResults[i]
@@ -166,30 +211,23 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
tool, _ = sjson.SetRaw(tool, "parametersJsonSchema", inputSchema) tool, _ = sjson.SetRaw(tool, "parametersJsonSchema", inputSchema)
tool, _ = sjson.Delete(tool, "strict") tool, _ = sjson.Delete(tool, "strict")
tool, _ = sjson.Delete(tool, "input_examples") tool, _ = sjson.Delete(tool, "input_examples")
var toolDeclaration any toolsJSON, _ = sjson.SetRaw(toolsJSON, "0.functionDeclarations.-1", tool)
if err := json.Unmarshal([]byte(tool), &toolDeclaration); err == nil { toolDeclCount++
tools[0].FunctionDeclarations = append(tools[0].FunctionDeclarations, toolDeclaration)
}
} }
} }
} else {
tools = make([]client.ToolDeclaration, 0)
} }
// Build output Gemini CLI request JSON // Build output Gemini CLI request JSON
out := `{"model":"","request":{"contents":[]}}` out := `{"model":"","request":{"contents":[]}}`
out, _ = sjson.Set(out, "model", modelName) out, _ = sjson.Set(out, "model", modelName)
if systemInstruction != nil { if hasSystemInstruction {
b, _ := json.Marshal(systemInstruction) out, _ = sjson.SetRaw(out, "request.systemInstruction", systemInstructionJSON)
out, _ = sjson.SetRaw(out, "request.systemInstruction", string(b))
} }
if len(contents) > 0 { if hasContents {
b, _ := json.Marshal(contents) out, _ = sjson.SetRaw(out, "request.contents", contentsJSON)
out, _ = sjson.SetRaw(out, "request.contents", string(b))
} }
if len(tools) > 0 && len(tools[0].FunctionDeclarations) > 0 { if toolDeclCount > 0 {
b, _ := json.Marshal(tools) out, _ = sjson.SetRaw(out, "request.tools", toolsJSON)
out, _ = sjson.SetRaw(out, "request.tools", string(b))
} }
// Map Anthropic thinking -> Gemini thinkingBudget/include_thoughts when type==enabled // Map Anthropic thinking -> Gemini thinkingBudget/include_thoughts when type==enabled