Skip to content

Commit 4477c72

Browse files
committed
feat: add all protocols request and response translation for Gemini and Gemini CLI compatibility
1 parent 0d89a22 commit 4477c72

File tree

12 files changed

+72
-262
lines changed

12 files changed

+72
-262
lines changed

internal/translator/gemini-cli/claude/gemini-cli_claude_request.go

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"strings"
1212

1313
client "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
14-
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
1514
"github.com/tidwall/gjson"
1615
"github.com/tidwall/sjson"
1716
)
@@ -36,18 +35,6 @@ import (
3635
// - []byte: The transformed request data in Gemini CLI API format
3736
func ConvertClaudeRequestToCLI(modelName string, inputRawJSON []byte, _ bool) []byte {
3837
rawJSON := bytes.Clone(inputRawJSON)
39-
var pathsToDelete []string
40-
root := gjson.ParseBytes(rawJSON)
41-
util.Walk(root, "", "additionalProperties", &pathsToDelete)
42-
util.Walk(root, "", "$schema", &pathsToDelete)
43-
44-
var err error
45-
for _, p := range pathsToDelete {
46-
rawJSON, err = sjson.DeleteBytes(rawJSON, p)
47-
if err != nil {
48-
continue
49-
}
50-
}
5138
rawJSON = bytes.Replace(rawJSON, []byte(`"url":{"type":"string","format":"uri",`), []byte(`"url":{"type":"string",`), -1)
5239

5340
// system instruction
@@ -99,7 +86,7 @@ func ConvertClaudeRequestToCLI(modelName string, inputRawJSON []byte, _ bool) []
9986
functionName := contentResult.Get("name").String()
10087
functionArgs := contentResult.Get("input").String()
10188
var args map[string]any
102-
if err = json.Unmarshal([]byte(functionArgs), &args); err == nil {
89+
if err := json.Unmarshal([]byte(functionArgs), &args); err == nil {
10390
clientContent.Parts = append(clientContent.Parts, client.Part{FunctionCall: &client.FunctionCall{Name: functionName, Args: args}})
10491
}
10592
} else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "tool_result" {
@@ -136,18 +123,10 @@ func ConvertClaudeRequestToCLI(modelName string, inputRawJSON []byte, _ bool) []
136123
inputSchemaResult := toolResult.Get("input_schema")
137124
if inputSchemaResult.Exists() && inputSchemaResult.IsObject() {
138125
inputSchema := inputSchemaResult.Raw
139-
// Use comprehensive schema sanitization for Gemini API compatibility
140-
if sanitizedSchema, sanitizeErr := util.SanitizeSchemaForGemini(inputSchema); sanitizeErr == nil {
141-
inputSchema = sanitizedSchema
142-
} else {
143-
// Fallback to basic cleanup if sanitization fails
144-
inputSchema, _ = sjson.Delete(inputSchema, "additionalProperties")
145-
inputSchema, _ = sjson.Delete(inputSchema, "$schema")
146-
}
147126
tool, _ := sjson.Delete(toolResult.Raw, "input_schema")
148-
tool, _ = sjson.SetRaw(tool, "parameters", inputSchema)
127+
tool, _ = sjson.SetRaw(tool, "parametersJsonSchema", inputSchema)
149128
var toolDeclaration any
150-
if err = json.Unmarshal([]byte(tool), &toolDeclaration); err == nil {
129+
if err := json.Unmarshal([]byte(tool), &toolDeclaration); err == nil {
151130
tools[0].FunctionDeclarations = append(tools[0].FunctionDeclarations, toolDeclaration)
152131
}
153132
}

internal/translator/gemini-cli/gemini/gemini-cli_gemini_request.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"encoding/json"
1111
"fmt"
1212

13+
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
1314
log "github.com/sirupsen/logrus"
1415
"github.com/tidwall/gjson"
1516
"github.com/tidwall/sjson"
@@ -78,6 +79,24 @@ func ConvertGeminiRequestToGeminiCLI(_ string, inputRawJSON []byte, _ bool) []by
7879
})
7980
}
8081

82+
toolsResult := gjson.GetBytes(rawJSON, "request.tools")
83+
if toolsResult.Exists() && toolsResult.IsArray() {
84+
toolResults := toolsResult.Array()
85+
for i := 0; i < len(toolResults); i++ {
86+
functionDeclarationsResult := gjson.GetBytes(rawJSON, fmt.Sprintf("request.tools.%d.function_declarations", i))
87+
if functionDeclarationsResult.Exists() && functionDeclarationsResult.IsArray() {
88+
functionDeclarationsResults := functionDeclarationsResult.Array()
89+
for j := 0; j < len(functionDeclarationsResults); j++ {
90+
parametersResult := gjson.GetBytes(rawJSON, fmt.Sprintf("request.tools.%d.function_declarations.%d.parameters", i, j))
91+
if parametersResult.Exists() {
92+
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))
93+
rawJSON = []byte(strJson)
94+
}
95+
}
96+
}
97+
}
98+
}
99+
81100
return rawJSON
82101
}
83102

internal/translator/gemini-cli/openai/chat-completions/cli_openai_request.go renamed to internal/translator/gemini-cli/openai/chat-completions/gemini-cli_openai_request.go

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,6 @@ import (
2626
// - []byte: The transformed request data in Gemini CLI API format
2727
func ConvertOpenAIRequestToGeminiCLI(modelName string, inputRawJSON []byte, _ bool) []byte {
2828
rawJSON := bytes.Clone(inputRawJSON)
29-
var pathsToDelete []string
30-
root := gjson.ParseBytes(rawJSON)
31-
util.Walk(root, "", "additionalProperties", &pathsToDelete)
32-
util.Walk(root, "", "$schema", &pathsToDelete)
33-
util.Walk(root, "", "ref", &pathsToDelete)
34-
util.Walk(root, "", "strict", &pathsToDelete)
35-
36-
var err error
37-
for _, p := range pathsToDelete {
38-
rawJSON, err = sjson.DeleteBytes(rawJSON, p)
39-
if err != nil {
40-
continue
41-
}
42-
}
43-
4429
// Base envelope
4530
out := []byte(`{"project":"","request":{"contents":[],"generationConfig":{"thinkingConfig":{"include_thoughts":true}}},"model":"gemini-2.5-pro"}`)
4631

@@ -265,22 +250,13 @@ func ConvertOpenAIRequestToGeminiCLI(modelName string, inputRawJSON []byte, _ bo
265250
if t.Get("type").String() == "function" {
266251
fn := t.Get("function")
267252
if fn.Exists() && fn.IsObject() {
268-
out, _ = sjson.SetRawBytes(out, fdPath+".-1", []byte(fn.Raw))
253+
parametersJsonSchema, _ := util.RenameKey(fn.Raw, "parameters", "parametersJsonSchema")
254+
out, _ = sjson.SetRawBytes(out, fdPath+".-1", []byte(parametersJsonSchema))
269255
}
270256
}
271257
}
272258
}
273259

274-
var pathsToType []string
275-
root = gjson.ParseBytes(out)
276-
util.Walk(root, "", "type", &pathsToType)
277-
for _, p := range pathsToType {
278-
typeResult := gjson.GetBytes(out, p)
279-
if strings.ToLower(typeResult.String()) == "select" {
280-
out, _ = sjson.SetBytes(out, p, "STRING")
281-
}
282-
}
283-
284260
return out
285261
}
286262

internal/translator/gemini-cli/openai/chat-completions/cli_openai_response.go renamed to internal/translator/gemini-cli/openai/chat-completions/gemini-cli_openai_response.go

File renamed without changes.

internal/translator/gemini-cli/openai/responses/cli_openai-responses_request.go renamed to internal/translator/gemini-cli/openai/responses/gemini-cli_openai-responses_request.go

File renamed without changes.

internal/translator/gemini-cli/openai/responses/cli_openai-responses_response.go renamed to internal/translator/gemini-cli/openai/responses/gemini-cli_openai-responses_response.go

File renamed without changes.

internal/translator/gemini/claude/gemini_claude_request.go

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"strings"
1212

1313
client "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
14-
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
1514
"github.com/tidwall/gjson"
1615
"github.com/tidwall/sjson"
1716
)
@@ -29,18 +28,6 @@ import (
2928
// - []byte: The transformed request in Gemini CLI format.
3029
func ConvertClaudeRequestToGemini(modelName string, inputRawJSON []byte, _ bool) []byte {
3130
rawJSON := bytes.Clone(inputRawJSON)
32-
var pathsToDelete []string
33-
root := gjson.ParseBytes(rawJSON)
34-
util.Walk(root, "", "additionalProperties", &pathsToDelete)
35-
util.Walk(root, "", "$schema", &pathsToDelete)
36-
37-
var err error
38-
for _, p := range pathsToDelete {
39-
rawJSON, err = sjson.DeleteBytes(rawJSON, p)
40-
if err != nil {
41-
continue
42-
}
43-
}
4431
rawJSON = bytes.Replace(rawJSON, []byte(`"url":{"type":"string","format":"uri",`), []byte(`"url":{"type":"string",`), -1)
4532

4633
// system instruction
@@ -92,7 +79,7 @@ func ConvertClaudeRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
9279
functionName := contentResult.Get("name").String()
9380
functionArgs := contentResult.Get("input").String()
9481
var args map[string]any
95-
if err = json.Unmarshal([]byte(functionArgs), &args); err == nil {
82+
if err := json.Unmarshal([]byte(functionArgs), &args); err == nil {
9683
clientContent.Parts = append(clientContent.Parts, client.Part{FunctionCall: &client.FunctionCall{Name: functionName, Args: args}})
9784
}
9885
} else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "tool_result" {
@@ -129,18 +116,10 @@ func ConvertClaudeRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
129116
inputSchemaResult := toolResult.Get("input_schema")
130117
if inputSchemaResult.Exists() && inputSchemaResult.IsObject() {
131118
inputSchema := inputSchemaResult.Raw
132-
// Use comprehensive schema sanitization for Gemini API compatibility
133-
if sanitizedSchema, sanitizeErr := util.SanitizeSchemaForGemini(inputSchema); sanitizeErr == nil {
134-
inputSchema = sanitizedSchema
135-
} else {
136-
// Fallback to basic cleanup if sanitization fails
137-
inputSchema, _ = sjson.Delete(inputSchema, "additionalProperties")
138-
inputSchema, _ = sjson.Delete(inputSchema, "$schema")
139-
}
140119
tool, _ := sjson.Delete(toolResult.Raw, "input_schema")
141-
tool, _ = sjson.SetRaw(tool, "parameters", inputSchema)
120+
tool, _ = sjson.SetRaw(tool, "parametersJsonSchema", inputSchema)
142121
var toolDeclaration any
143-
if err = json.Unmarshal([]byte(tool), &toolDeclaration); err == nil {
122+
if err := json.Unmarshal([]byte(tool), &toolDeclaration); err == nil {
144123
tools[0].FunctionDeclarations = append(tools[0].FunctionDeclarations, toolDeclaration)
145124
}
146125
}

internal/translator/gemini/gemini-cli/gemini_gemini-cli_request.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ package geminiCLI
77

88
import (
99
"bytes"
10+
"fmt"
1011

12+
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
1113
"github.com/tidwall/gjson"
1214
"github.com/tidwall/sjson"
1315
)
@@ -24,5 +26,24 @@ func ConvertGeminiCLIRequestToGemini(_ string, inputRawJSON []byte, _ bool) []by
2426
rawJSON, _ = sjson.SetRawBytes(rawJSON, "system_instruction", []byte(gjson.GetBytes(rawJSON, "systemInstruction").Raw))
2527
rawJSON, _ = sjson.DeleteBytes(rawJSON, "systemInstruction")
2628
}
29+
30+
toolsResult := gjson.GetBytes(rawJSON, "tools")
31+
if toolsResult.Exists() && toolsResult.IsArray() {
32+
toolResults := toolsResult.Array()
33+
for i := 0; i < len(toolResults); i++ {
34+
functionDeclarationsResult := gjson.GetBytes(rawJSON, fmt.Sprintf("tools.%d.function_declarations", i))
35+
if functionDeclarationsResult.Exists() && functionDeclarationsResult.IsArray() {
36+
functionDeclarationsResults := functionDeclarationsResult.Array()
37+
for j := 0; j < len(functionDeclarationsResults); j++ {
38+
parametersResult := gjson.GetBytes(rawJSON, fmt.Sprintf("tools.%d.function_declarations.%d.parameters", i, j))
39+
if parametersResult.Exists() {
40+
strJson, _ := util.RenameKey(string(rawJSON), fmt.Sprintf("tools.%d.function_declarations.%d.parameters", i, j), fmt.Sprintf("tools.%d.function_declarations.%d.parametersJsonSchema", i, j))
41+
rawJSON = []byte(strJson)
42+
}
43+
}
44+
}
45+
}
46+
}
47+
2748
return rawJSON
2849
}

internal/translator/gemini/gemini/gemini_gemini_request.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"bytes"
88
"fmt"
99

10+
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
1011
"github.com/tidwall/gjson"
1112
"github.com/tidwall/sjson"
1213
)
@@ -24,6 +25,24 @@ func ConvertGeminiRequestToGemini(_ string, inputRawJSON []byte, _ bool) []byte
2425
return rawJSON
2526
}
2627

28+
toolsResult := gjson.GetBytes(rawJSON, "tools")
29+
if toolsResult.Exists() && toolsResult.IsArray() {
30+
toolResults := toolsResult.Array()
31+
for i := 0; i < len(toolResults); i++ {
32+
functionDeclarationsResult := gjson.GetBytes(rawJSON, fmt.Sprintf("tools.%d.function_declarations", i))
33+
if functionDeclarationsResult.Exists() && functionDeclarationsResult.IsArray() {
34+
functionDeclarationsResults := functionDeclarationsResult.Array()
35+
for j := 0; j < len(functionDeclarationsResults); j++ {
36+
parametersResult := gjson.GetBytes(rawJSON, fmt.Sprintf("tools.%d.function_declarations.%d.parameters", i, j))
37+
if parametersResult.Exists() {
38+
strJson, _ := util.RenameKey(string(rawJSON), fmt.Sprintf("tools.%d.function_declarations.%d.parameters", i, j), fmt.Sprintf("tools.%d.function_declarations.%d.parametersJsonSchema", i, j))
39+
rawJSON = []byte(strJson)
40+
}
41+
}
42+
}
43+
}
44+
}
45+
2746
// Walk contents and fix roles
2847
out := rawJSON
2948
prevRole := ""

internal/translator/gemini/openai/chat-completions/gemini_openai_request.go

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,6 @@ import (
2626
// - []byte: The transformed request data in Gemini API format
2727
func ConvertOpenAIRequestToGemini(modelName string, inputRawJSON []byte, _ bool) []byte {
2828
rawJSON := bytes.Clone(inputRawJSON)
29-
var pathsToDelete []string
30-
root := gjson.ParseBytes(rawJSON)
31-
util.Walk(root, "", "additionalProperties", &pathsToDelete)
32-
util.Walk(root, "", "$schema", &pathsToDelete)
33-
util.Walk(root, "", "ref", &pathsToDelete)
34-
util.Walk(root, "", "strict", &pathsToDelete)
35-
36-
var err error
37-
for _, p := range pathsToDelete {
38-
rawJSON, err = sjson.DeleteBytes(rawJSON, p)
39-
if err != nil {
40-
continue
41-
}
42-
}
43-
4429
// Base envelope
4530
out := []byte(`{"contents":[],"generationConfig":{"thinkingConfig":{"include_thoughts":true}}}`)
4631

@@ -290,22 +275,13 @@ func ConvertOpenAIRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
290275
if t.Get("type").String() == "function" {
291276
fn := t.Get("function")
292277
if fn.Exists() && fn.IsObject() {
293-
out, _ = sjson.SetRawBytes(out, fdPath+".-1", []byte(fn.Raw))
278+
parametersJsonSchema, _ := util.RenameKey(fn.Raw, "parameters", "parametersJsonSchema")
279+
out, _ = sjson.SetRawBytes(out, fdPath+".-1", []byte(parametersJsonSchema))
294280
}
295281
}
296282
}
297283
}
298284

299-
var pathsToType []string
300-
root = gjson.ParseBytes(out)
301-
util.Walk(root, "", "type", &pathsToType)
302-
for _, p := range pathsToType {
303-
typeResult := gjson.GetBytes(out, p)
304-
if strings.ToLower(typeResult.String()) == "select" {
305-
out, _ = sjson.SetBytes(out, p, "STRING")
306-
}
307-
}
308-
309285
return out
310286
}
311287

0 commit comments

Comments
 (0)