mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 13:00:52 +08:00
feat: add all protocols request and response translation for Gemini and Gemini CLI compatibility
This commit is contained in:
@@ -11,7 +11,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
client "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
|
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/gjson"
|
||||||
"github.com/tidwall/sjson"
|
"github.com/tidwall/sjson"
|
||||||
)
|
)
|
||||||
@@ -36,18 +35,6 @@ import (
|
|||||||
// - []byte: The transformed request data in Gemini CLI API format
|
// - []byte: The transformed request data in Gemini CLI API format
|
||||||
func ConvertClaudeRequestToCLI(modelName string, inputRawJSON []byte, _ bool) []byte {
|
func ConvertClaudeRequestToCLI(modelName string, inputRawJSON []byte, _ bool) []byte {
|
||||||
rawJSON := bytes.Clone(inputRawJSON)
|
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)
|
rawJSON = bytes.Replace(rawJSON, []byte(`"url":{"type":"string","format":"uri",`), []byte(`"url":{"type":"string",`), -1)
|
||||||
|
|
||||||
// system instruction
|
// system instruction
|
||||||
@@ -99,7 +86,7 @@ func ConvertClaudeRequestToCLI(modelName string, inputRawJSON []byte, _ bool) []
|
|||||||
functionName := contentResult.Get("name").String()
|
functionName := contentResult.Get("name").String()
|
||||||
functionArgs := contentResult.Get("input").String()
|
functionArgs := contentResult.Get("input").String()
|
||||||
var args map[string]any
|
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}})
|
clientContent.Parts = append(clientContent.Parts, client.Part{FunctionCall: &client.FunctionCall{Name: functionName, Args: args}})
|
||||||
}
|
}
|
||||||
} else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "tool_result" {
|
} 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")
|
inputSchemaResult := toolResult.Get("input_schema")
|
||||||
if inputSchemaResult.Exists() && inputSchemaResult.IsObject() {
|
if inputSchemaResult.Exists() && inputSchemaResult.IsObject() {
|
||||||
inputSchema := inputSchemaResult.Raw
|
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.Delete(toolResult.Raw, "input_schema")
|
||||||
tool, _ = sjson.SetRaw(tool, "parameters", inputSchema)
|
tool, _ = sjson.SetRaw(tool, "parametersJsonSchema", inputSchema)
|
||||||
var toolDeclaration any
|
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)
|
tools[0].FunctionDeclarations = append(tools[0].FunctionDeclarations, toolDeclaration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
"github.com/tidwall/sjson"
|
"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
|
return rawJSON
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,21 +26,6 @@ import (
|
|||||||
// - []byte: The transformed request data in Gemini CLI API format
|
// - []byte: The transformed request data in Gemini CLI API format
|
||||||
func ConvertOpenAIRequestToGeminiCLI(modelName string, inputRawJSON []byte, _ bool) []byte {
|
func ConvertOpenAIRequestToGeminiCLI(modelName string, inputRawJSON []byte, _ bool) []byte {
|
||||||
rawJSON := bytes.Clone(inputRawJSON)
|
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
|
// Base envelope
|
||||||
out := []byte(`{"project":"","request":{"contents":[],"generationConfig":{"thinkingConfig":{"include_thoughts":true}}},"model":"gemini-2.5-pro"}`)
|
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" {
|
if t.Get("type").String() == "function" {
|
||||||
fn := t.Get("function")
|
fn := t.Get("function")
|
||||||
if fn.Exists() && fn.IsObject() {
|
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
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
client "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
|
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/gjson"
|
||||||
"github.com/tidwall/sjson"
|
"github.com/tidwall/sjson"
|
||||||
)
|
)
|
||||||
@@ -29,18 +28,6 @@ import (
|
|||||||
// - []byte: The transformed request in Gemini CLI format.
|
// - []byte: The transformed request in Gemini CLI format.
|
||||||
func ConvertClaudeRequestToGemini(modelName string, inputRawJSON []byte, _ bool) []byte {
|
func ConvertClaudeRequestToGemini(modelName string, inputRawJSON []byte, _ bool) []byte {
|
||||||
rawJSON := bytes.Clone(inputRawJSON)
|
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)
|
rawJSON = bytes.Replace(rawJSON, []byte(`"url":{"type":"string","format":"uri",`), []byte(`"url":{"type":"string",`), -1)
|
||||||
|
|
||||||
// system instruction
|
// system instruction
|
||||||
@@ -92,7 +79,7 @@ func ConvertClaudeRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
|
|||||||
functionName := contentResult.Get("name").String()
|
functionName := contentResult.Get("name").String()
|
||||||
functionArgs := contentResult.Get("input").String()
|
functionArgs := contentResult.Get("input").String()
|
||||||
var args map[string]any
|
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}})
|
clientContent.Parts = append(clientContent.Parts, client.Part{FunctionCall: &client.FunctionCall{Name: functionName, Args: args}})
|
||||||
}
|
}
|
||||||
} else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "tool_result" {
|
} else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "tool_result" {
|
||||||
@@ -129,18 +116,10 @@ func ConvertClaudeRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
|
|||||||
inputSchemaResult := toolResult.Get("input_schema")
|
inputSchemaResult := toolResult.Get("input_schema")
|
||||||
if inputSchemaResult.Exists() && inputSchemaResult.IsObject() {
|
if inputSchemaResult.Exists() && inputSchemaResult.IsObject() {
|
||||||
inputSchema := inputSchemaResult.Raw
|
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.Delete(toolResult.Raw, "input_schema")
|
||||||
tool, _ = sjson.SetRaw(tool, "parameters", inputSchema)
|
tool, _ = sjson.SetRaw(tool, "parametersJsonSchema", inputSchema)
|
||||||
var toolDeclaration any
|
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)
|
tools[0].FunctionDeclarations = append(tools[0].FunctionDeclarations, toolDeclaration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ package geminiCLI
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
"github.com/tidwall/sjson"
|
"github.com/tidwall/sjson"
|
||||||
)
|
)
|
||||||
@@ -24,5 +26,24 @@ func ConvertGeminiCLIRequestToGemini(_ string, inputRawJSON []byte, _ bool) []by
|
|||||||
rawJSON, _ = sjson.SetRawBytes(rawJSON, "system_instruction", []byte(gjson.GetBytes(rawJSON, "systemInstruction").Raw))
|
rawJSON, _ = sjson.SetRawBytes(rawJSON, "system_instruction", []byte(gjson.GetBytes(rawJSON, "systemInstruction").Raw))
|
||||||
rawJSON, _ = sjson.DeleteBytes(rawJSON, "systemInstruction")
|
rawJSON, _ = sjson.DeleteBytes(rawJSON, "systemInstruction")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toolsResult := gjson.GetBytes(rawJSON, "tools")
|
||||||
|
if toolsResult.Exists() && toolsResult.IsArray() {
|
||||||
|
toolResults := toolsResult.Array()
|
||||||
|
for i := 0; i < len(toolResults); i++ {
|
||||||
|
functionDeclarationsResult := gjson.GetBytes(rawJSON, fmt.Sprintf("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("tools.%d.function_declarations.%d.parameters", i, j))
|
||||||
|
if parametersResult.Exists() {
|
||||||
|
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))
|
||||||
|
rawJSON = []byte(strJson)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return rawJSON
|
return rawJSON
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
"github.com/tidwall/sjson"
|
"github.com/tidwall/sjson"
|
||||||
)
|
)
|
||||||
@@ -24,6 +25,24 @@ func ConvertGeminiRequestToGemini(_ string, inputRawJSON []byte, _ bool) []byte
|
|||||||
return rawJSON
|
return rawJSON
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toolsResult := gjson.GetBytes(rawJSON, "tools")
|
||||||
|
if toolsResult.Exists() && toolsResult.IsArray() {
|
||||||
|
toolResults := toolsResult.Array()
|
||||||
|
for i := 0; i < len(toolResults); i++ {
|
||||||
|
functionDeclarationsResult := gjson.GetBytes(rawJSON, fmt.Sprintf("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("tools.%d.function_declarations.%d.parameters", i, j))
|
||||||
|
if parametersResult.Exists() {
|
||||||
|
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))
|
||||||
|
rawJSON = []byte(strJson)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Walk contents and fix roles
|
// Walk contents and fix roles
|
||||||
out := rawJSON
|
out := rawJSON
|
||||||
prevRole := ""
|
prevRole := ""
|
||||||
|
|||||||
@@ -26,21 +26,6 @@ import (
|
|||||||
// - []byte: The transformed request data in Gemini API format
|
// - []byte: The transformed request data in Gemini API format
|
||||||
func ConvertOpenAIRequestToGemini(modelName string, inputRawJSON []byte, _ bool) []byte {
|
func ConvertOpenAIRequestToGemini(modelName string, inputRawJSON []byte, _ bool) []byte {
|
||||||
rawJSON := bytes.Clone(inputRawJSON)
|
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
|
// Base envelope
|
||||||
out := []byte(`{"contents":[],"generationConfig":{"thinkingConfig":{"include_thoughts":true}}}`)
|
out := []byte(`{"contents":[],"generationConfig":{"thinkingConfig":{"include_thoughts":true}}}`)
|
||||||
|
|
||||||
@@ -290,22 +275,13 @@ func ConvertOpenAIRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
|
|||||||
if t.Get("type").String() == "function" {
|
if t.Get("type").String() == "function" {
|
||||||
fn := t.Get("function")
|
fn := t.Get("function")
|
||||||
if fn.Exists() && fn.IsObject() {
|
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
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ func ConvertOpenAIResponsesRequestToGemini(modelName string, inputRawJSON []byte
|
|||||||
if outputResult.IsObject() {
|
if outputResult.IsObject() {
|
||||||
functionResponse, _ = sjson.SetRaw(functionResponse, "functionResponse.response.content", outputResult.String())
|
functionResponse, _ = sjson.SetRaw(functionResponse, "functionResponse.response.content", outputResult.String())
|
||||||
} else {
|
} else {
|
||||||
functionResponse, _ = sjson.Set(functionResponse, "functionResponse.response.content", outputResult.String())
|
functionResponse, _ = sjson.Set(functionResponse, "functionResponse.response.content", output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ func ConvertOpenAIResponsesRequestToGemini(modelName string, inputRawJSON []byte
|
|||||||
|
|
||||||
tools.ForEach(func(_, tool gjson.Result) bool {
|
tools.ForEach(func(_, tool gjson.Result) bool {
|
||||||
if tool.Get("type").String() == "function" {
|
if tool.Get("type").String() == "function" {
|
||||||
funcDecl := `{"name":"","description":"","parameters":{}}`
|
funcDecl := `{"name":"","description":"","parametersJsonSchema":{}}`
|
||||||
|
|
||||||
if name := tool.Get("name"); name.Exists() {
|
if name := tool.Get("name"); name.Exists() {
|
||||||
funcDecl, _ = sjson.Set(funcDecl, "name", name.String())
|
funcDecl, _ = sjson.Set(funcDecl, "name", name.String())
|
||||||
@@ -192,7 +192,7 @@ func ConvertOpenAIResponsesRequestToGemini(modelName string, inputRawJSON []byte
|
|||||||
}
|
}
|
||||||
// Set the overall type to OBJECT
|
// Set the overall type to OBJECT
|
||||||
cleaned, _ = sjson.Set(cleaned, "type", "OBJECT")
|
cleaned, _ = sjson.Set(cleaned, "type", "OBJECT")
|
||||||
funcDecl, _ = sjson.SetRaw(funcDecl, "parameters", cleaned)
|
funcDecl, _ = sjson.SetRaw(funcDecl, "parametersJsonSchema", cleaned)
|
||||||
}
|
}
|
||||||
|
|
||||||
geminiTools, _ = sjson.SetRaw(geminiTools, "0.functionDeclarations.-1", funcDecl)
|
geminiTools, _ = sjson.SetRaw(geminiTools, "0.functionDeclarations.-1", funcDecl)
|
||||||
@@ -261,6 +261,5 @@ func ConvertOpenAIResponsesRequestToGemini(modelName string, inputRawJSON []byte
|
|||||||
out, _ = sjson.Set(out, "generationConfig.thinkingConfig.thinkingBudget", -1)
|
out, _ = sjson.Set(out, "generationConfig.thinkingConfig.thinkingBudget", -1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return []byte(out)
|
return []byte(out)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,161 +212,3 @@ func FixJSON(input string) string {
|
|||||||
|
|
||||||
return out.String()
|
return out.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SanitizeSchemaForGemini removes JSON Schema fields that are incompatible with Gemini API
|
|
||||||
// to prevent "Proto field is not repeating, cannot start list" errors.
|
|
||||||
//
|
|
||||||
// Parameters:
|
|
||||||
// - schemaJSON: The JSON schema string to sanitize
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// - string: The sanitized schema string
|
|
||||||
// - error: An error if the operation fails
|
|
||||||
//
|
|
||||||
// This function removes the following incompatible fields:
|
|
||||||
// - additionalProperties: Not supported in Gemini function declarations
|
|
||||||
// - $schema: JSON Schema meta-schema identifier, not needed for API
|
|
||||||
// - allOf/anyOf/oneOf: Union type constructs not supported
|
|
||||||
// - exclusiveMinimum/exclusiveMaximum: Advanced validation constraints
|
|
||||||
// - patternProperties: Advanced property pattern matching
|
|
||||||
// - dependencies: Property dependencies not supported
|
|
||||||
// - type arrays: Converts ["string", "null"] to just "string"
|
|
||||||
func SanitizeSchemaForGemini(schemaJSON string) (string, error) {
|
|
||||||
// Remove top-level incompatible fields
|
|
||||||
fieldsToRemove := []string{
|
|
||||||
"additionalProperties",
|
|
||||||
"$schema",
|
|
||||||
"allOf",
|
|
||||||
"anyOf",
|
|
||||||
"oneOf",
|
|
||||||
"exclusiveMinimum",
|
|
||||||
"exclusiveMaximum",
|
|
||||||
"patternProperties",
|
|
||||||
"dependencies",
|
|
||||||
}
|
|
||||||
|
|
||||||
result := schemaJSON
|
|
||||||
var err error
|
|
||||||
|
|
||||||
for _, field := range fieldsToRemove {
|
|
||||||
result, err = sjson.Delete(result, field)
|
|
||||||
if err != nil {
|
|
||||||
continue // Continue even if deletion fails
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle type arrays by converting them to single types
|
|
||||||
result = sanitizeTypeFields(result)
|
|
||||||
|
|
||||||
// Recursively clean nested objects
|
|
||||||
result = cleanNestedSchemas(result)
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sanitizeTypeFields converts type arrays to single types for Gemini compatibility
|
|
||||||
func sanitizeTypeFields(jsonStr string) string {
|
|
||||||
// Parse the JSON to find all "type" fields
|
|
||||||
parsed := gjson.Parse(jsonStr)
|
|
||||||
result := jsonStr
|
|
||||||
|
|
||||||
// Walk through all paths to find type fields
|
|
||||||
var typeFields []string
|
|
||||||
walkForTypeFields(parsed, "", &typeFields)
|
|
||||||
|
|
||||||
// Process each type field
|
|
||||||
for _, path := range typeFields {
|
|
||||||
typeValue := gjson.Get(result, path)
|
|
||||||
if typeValue.IsArray() {
|
|
||||||
// Convert array to single type (prioritize string, then others)
|
|
||||||
arr := typeValue.Array()
|
|
||||||
if len(arr) > 0 {
|
|
||||||
var preferredType string
|
|
||||||
for _, t := range arr {
|
|
||||||
typeStr := t.String()
|
|
||||||
if typeStr == "string" {
|
|
||||||
preferredType = "string"
|
|
||||||
break
|
|
||||||
} else if typeStr == "number" || typeStr == "integer" {
|
|
||||||
preferredType = typeStr
|
|
||||||
} else if preferredType == "" {
|
|
||||||
preferredType = typeStr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if preferredType != "" {
|
|
||||||
result, _ = sjson.Set(result, path, preferredType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// walkForTypeFields recursively finds all "type" field paths in the JSON
|
|
||||||
func walkForTypeFields(value gjson.Result, path string, paths *[]string) {
|
|
||||||
switch value.Type {
|
|
||||||
case gjson.JSON:
|
|
||||||
value.ForEach(func(key, val gjson.Result) bool {
|
|
||||||
var childPath string
|
|
||||||
if path == "" {
|
|
||||||
childPath = key.String()
|
|
||||||
} else {
|
|
||||||
childPath = path + "." + key.String()
|
|
||||||
}
|
|
||||||
if key.String() == "type" {
|
|
||||||
*paths = append(*paths, childPath)
|
|
||||||
}
|
|
||||||
walkForTypeFields(val, childPath, paths)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanNestedSchemas recursively removes incompatible fields from nested schema objects
|
|
||||||
func cleanNestedSchemas(jsonStr string) string {
|
|
||||||
fieldsToRemove := []string{"allOf", "anyOf", "oneOf", "exclusiveMinimum", "exclusiveMaximum"}
|
|
||||||
|
|
||||||
// Find all nested paths that might contain these fields
|
|
||||||
var pathsToClean []string
|
|
||||||
parsed := gjson.Parse(jsonStr)
|
|
||||||
findNestedSchemaPaths(parsed, "", fieldsToRemove, &pathsToClean)
|
|
||||||
|
|
||||||
result := jsonStr
|
|
||||||
// Remove fields from all found paths
|
|
||||||
for _, path := range pathsToClean {
|
|
||||||
result, _ = sjson.Delete(result, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// findNestedSchemaPaths recursively finds paths containing incompatible schema fields
|
|
||||||
func findNestedSchemaPaths(value gjson.Result, path string, fieldsToFind []string, paths *[]string) {
|
|
||||||
switch value.Type {
|
|
||||||
case gjson.JSON:
|
|
||||||
value.ForEach(func(key, val gjson.Result) bool {
|
|
||||||
var childPath string
|
|
||||||
if path == "" {
|
|
||||||
childPath = key.String()
|
|
||||||
} else {
|
|
||||||
childPath = path + "." + key.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this key is one we want to remove
|
|
||||||
for _, field := range fieldsToFind {
|
|
||||||
if key.String() == field {
|
|
||||||
*paths = append(*paths, childPath)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
findNestedSchemaPaths(val, childPath, fieldsToFind, paths)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user