feat: add all protocols request and response translation for Gemini and Gemini CLI compatibility
This commit is contained in:
Luis Pater
2025-10-17 02:11:29 +08:00
parent 0d89a22aa0
commit 4477c729a4
12 changed files with 72 additions and 262 deletions

View File

@@ -11,7 +11,6 @@ import (
"strings"
client "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
@@ -36,18 +35,6 @@ import (
// - []byte: The transformed request data in Gemini CLI API format
func ConvertClaudeRequestToCLI(modelName string, inputRawJSON []byte, _ bool) []byte {
rawJSON := bytes.Clone(inputRawJSON)
var pathsToDelete []string
root := gjson.ParseBytes(rawJSON)
util.Walk(root, "", "additionalProperties", &pathsToDelete)
util.Walk(root, "", "$schema", &pathsToDelete)
var err error
for _, p := range pathsToDelete {
rawJSON, err = sjson.DeleteBytes(rawJSON, p)
if err != nil {
continue
}
}
rawJSON = bytes.Replace(rawJSON, []byte(`"url":{"type":"string","format":"uri",`), []byte(`"url":{"type":"string",`), -1)
// system instruction
@@ -99,7 +86,7 @@ func ConvertClaudeRequestToCLI(modelName string, inputRawJSON []byte, _ bool) []
functionName := contentResult.Get("name").String()
functionArgs := contentResult.Get("input").String()
var args map[string]any
if err = json.Unmarshal([]byte(functionArgs), &args); err == nil {
if err := json.Unmarshal([]byte(functionArgs), &args); err == nil {
clientContent.Parts = append(clientContent.Parts, client.Part{FunctionCall: &client.FunctionCall{Name: functionName, Args: args}})
}
} else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "tool_result" {
@@ -136,18 +123,10 @@ func ConvertClaudeRequestToCLI(modelName string, inputRawJSON []byte, _ bool) []
inputSchemaResult := toolResult.Get("input_schema")
if inputSchemaResult.Exists() && inputSchemaResult.IsObject() {
inputSchema := inputSchemaResult.Raw
// Use comprehensive schema sanitization for Gemini API compatibility
if sanitizedSchema, sanitizeErr := util.SanitizeSchemaForGemini(inputSchema); sanitizeErr == nil {
inputSchema = sanitizedSchema
} else {
// Fallback to basic cleanup if sanitization fails
inputSchema, _ = sjson.Delete(inputSchema, "additionalProperties")
inputSchema, _ = sjson.Delete(inputSchema, "$schema")
}
tool, _ := sjson.Delete(toolResult.Raw, "input_schema")
tool, _ = sjson.SetRaw(tool, "parameters", inputSchema)
tool, _ = sjson.SetRaw(tool, "parametersJsonSchema", inputSchema)
var toolDeclaration any
if err = json.Unmarshal([]byte(tool), &toolDeclaration); err == nil {
if err := json.Unmarshal([]byte(tool), &toolDeclaration); err == nil {
tools[0].FunctionDeclarations = append(tools[0].FunctionDeclarations, toolDeclaration)
}
}

View File

@@ -10,6 +10,7 @@ import (
"encoding/json"
"fmt"
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
@@ -78,6 +79,24 @@ func ConvertGeminiRequestToGeminiCLI(_ string, inputRawJSON []byte, _ bool) []by
})
}
toolsResult := gjson.GetBytes(rawJSON, "request.tools")
if toolsResult.Exists() && toolsResult.IsArray() {
toolResults := toolsResult.Array()
for i := 0; i < len(toolResults); i++ {
functionDeclarationsResult := gjson.GetBytes(rawJSON, fmt.Sprintf("request.tools.%d.function_declarations", i))
if functionDeclarationsResult.Exists() && functionDeclarationsResult.IsArray() {
functionDeclarationsResults := functionDeclarationsResult.Array()
for j := 0; j < len(functionDeclarationsResults); j++ {
parametersResult := gjson.GetBytes(rawJSON, fmt.Sprintf("request.tools.%d.function_declarations.%d.parameters", i, j))
if parametersResult.Exists() {
strJson, _ := util.RenameKey(string(rawJSON), fmt.Sprintf("request.tools.%d.function_declarations.%d.parameters", i, j), fmt.Sprintf("request.tools.%d.function_declarations.%d.parametersJsonSchema", i, j))
rawJSON = []byte(strJson)
}
}
}
}
}
return rawJSON
}

View File

@@ -26,21 +26,6 @@ import (
// - []byte: The transformed request data in Gemini CLI API format
func ConvertOpenAIRequestToGeminiCLI(modelName string, inputRawJSON []byte, _ bool) []byte {
rawJSON := bytes.Clone(inputRawJSON)
var pathsToDelete []string
root := gjson.ParseBytes(rawJSON)
util.Walk(root, "", "additionalProperties", &pathsToDelete)
util.Walk(root, "", "$schema", &pathsToDelete)
util.Walk(root, "", "ref", &pathsToDelete)
util.Walk(root, "", "strict", &pathsToDelete)
var err error
for _, p := range pathsToDelete {
rawJSON, err = sjson.DeleteBytes(rawJSON, p)
if err != nil {
continue
}
}
// Base envelope
out := []byte(`{"project":"","request":{"contents":[],"generationConfig":{"thinkingConfig":{"include_thoughts":true}}},"model":"gemini-2.5-pro"}`)
@@ -265,22 +250,13 @@ func ConvertOpenAIRequestToGeminiCLI(modelName string, inputRawJSON []byte, _ bo
if t.Get("type").String() == "function" {
fn := t.Get("function")
if fn.Exists() && fn.IsObject() {
out, _ = sjson.SetRawBytes(out, fdPath+".-1", []byte(fn.Raw))
parametersJsonSchema, _ := util.RenameKey(fn.Raw, "parameters", "parametersJsonSchema")
out, _ = sjson.SetRawBytes(out, fdPath+".-1", []byte(parametersJsonSchema))
}
}
}
}
var pathsToType []string
root = gjson.ParseBytes(out)
util.Walk(root, "", "type", &pathsToType)
for _, p := range pathsToType {
typeResult := gjson.GetBytes(out, p)
if strings.ToLower(typeResult.String()) == "select" {
out, _ = sjson.SetBytes(out, p, "STRING")
}
}
return out
}