History

Fixed: #129 #123 #102 #97

Luis Pater committed 1 year agov6.2.17

Commit 4477c721 parent 0d89a2212 files · +72 −262Browse files

Unified · Split

12 files
  1. internal/translator/gemini-cli/claude/gemini-cli_claude_request.go +3 −24
  2. internal/translator/gemini-cli/gemini/gemini-cli_gemini_request.go +19 −0
  3. internal/translator/gemini-cli/openai/chat-completions/gemini-cli_openai_request.go +2 −26
  4. internal/translator/gemini-cli/openai/chat-completions/gemini-cli_openai_response.go +0 −0
  5. internal/translator/gemini-cli/openai/responses/gemini-cli_openai-responses_request.go +0 −0
  6. internal/translator/gemini-cli/openai/responses/gemini-cli_openai-responses_response.go +0 −0
  7. internal/translator/gemini/claude/gemini_claude_request.go +3 −24
  8. internal/translator/gemini/gemini-cli/gemini_gemini-cli_request.go +21 −0
  9. internal/translator/gemini/gemini/gemini_gemini_request.go +19 −0
  10. internal/translator/gemini/openai/chat-completions/gemini_openai_request.go +2 −26
  11. internal/translator/gemini/openai/responses/gemini_openai-responses_request.go +3 −4
  12. internal/util/translator.go +0 −158
internal/translator/gemini-cli/claude/gemini-cli_claude_request.go+3−24
@@ -11,7 +11,6 @@ import ( · 10 unmodified lines
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 ( · 18 unmodified lines
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) [] · 45 unmodified lines
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) [] · 30 unmodified lines
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+19−0
@@ -10,6 +10,7 @@ import ( · 9 unmodified lines
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 · 62 unmodified lines
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/gemini-cli_openai_request.gorenamed from internal/translator/gemini-cli/openai/chat-completions/cli_openai_request.go+2−26
@@ -26,21 +26,6 @@ import ( · 25 unmodified lines
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 · 218 unmodified lines
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/gemini-cli_openai_response.gorenamed from internal/translator/gemini-cli/openai/chat-completions/cli_openai_response.go+0−0

File renamed without changes.

internal/translator/gemini-cli/openai/responses/gemini-cli_openai-responses_request.gorenamed from internal/translator/gemini-cli/openai/responses/cli_openai-responses_request.go+0−0

File renamed without changes.

internal/translator/gemini-cli/openai/responses/gemini-cli_openai-responses_response.gorenamed from internal/translator/gemini-cli/openai/responses/cli_openai-responses_response.go+0−0

File renamed without changes.

internal/translator/gemini/claude/gemini_claude_request.go+3−24
@@ -11,7 +11,6 @@ import ( · 10 unmodified lines
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 ( · 11 unmodified lines
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) · 45 unmodified lines
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) · 30 unmodified lines
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+21−0
@@ -7,7 +7,9 @@ package geminiCLI · 6 unmodified lines
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 · 10 unmodified lines
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+19−0
@@ -7,6 +7,7 @@ import ( · 6 unmodified lines
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 · 11 unmodified lines
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+2−26
@@ -26,21 +26,6 @@ import ( · 25 unmodified lines
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) · 243 unmodified lines
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
internal/translator/gemini/openai/responses/gemini_openai-responses_request.go+3−4
@@ -150,7 +150,7 @@ func ConvertOpenAIResponsesRequestToGemini(modelName string, inputRawJSON []byte · 149 unmodified lines
150150 if outputResult.IsObject() {
151151 functionResponse, _ = sjson.SetRaw(functionResponse, "functionResponse.response.content", outputResult.String())
152152 } else {
153− functionResponse, _ = sjson.Set(functionResponse, "functionResponse.response.content", outputResult.String())
153+ functionResponse, _ = sjson.Set(functionResponse, "functionResponse.response.content", output)
154154 }
155155 }
156156
@@ -168,7 +168,7 @@ func ConvertOpenAIResponsesRequestToGemini(modelName string, inputRawJSON []byte · 11 unmodified lines
168168
169169 tools.ForEach(func(_, tool gjson.Result) bool {
170170 if tool.Get("type").String() == "function" {
171− funcDecl := `{"name":"","description":"","parameters":{}}`
171+ funcDecl := `{"name":"","description":"","parametersJsonSchema":{}}`
172172
173173 if name := tool.Get("name"); name.Exists() {
174174 funcDecl, _ = sjson.Set(funcDecl, "name", name.String())
@@ -192,7 +192,7 @@ func ConvertOpenAIResponsesRequestToGemini(modelName string, inputRawJSON []byte · 17 unmodified lines
192192 }
193193 // Set the overall type to OBJECT
194194 cleaned, _ = sjson.Set(cleaned, "type", "OBJECT")
195− funcDecl, _ = sjson.SetRaw(funcDecl, "parameters", cleaned)
195+ funcDecl, _ = sjson.SetRaw(funcDecl, "parametersJsonSchema", cleaned)
196196 }
197197
198198 geminiTools, _ = sjson.SetRaw(geminiTools, "0.functionDeclarations.-1", funcDecl)
@@ -261,6 +261,5 @@ func ConvertOpenAIResponsesRequestToGemini(modelName string, inputRawJSON []byte · 62 unmodified lines
261261 out, _ = sjson.Set(out, "generationConfig.thinkingConfig.thinkingBudget", -1)
262262 }
263263 }
264−
265264 return []byte(out)
266265 }
internal/util/translator.go+0−158
@@ -212,161 +212,3 @@ func FixJSON(input string) string { · 211 unmodified lines
212212
213213 return out.String()
214214 }
215−
216−// SanitizeSchemaForGemini removes JSON Schema fields that are incompatible with Gemini API
217−// to prevent "Proto field is not repeating, cannot start list" errors.
218−//
219−// Parameters:
220−// - schemaJSON: The JSON schema string to sanitize
221−//
222−// Returns:
223−// - string: The sanitized schema string
224−// - error: An error if the operation fails
225−//
226−// This function removes the following incompatible fields:
227−// - additionalProperties: Not supported in Gemini function declarations
228−// - $schema: JSON Schema meta-schema identifier, not needed for API
229−// - allOf/anyOf/oneOf: Union type constructs not supported
230−// - exclusiveMinimum/exclusiveMaximum: Advanced validation constraints
231−// - patternProperties: Advanced property pattern matching
232−// - dependencies: Property dependencies not supported
233−// - type arrays: Converts ["string", "null"] to just "string"
234−func SanitizeSchemaForGemini(schemaJSON string) (string, error) {
235− // Remove top-level incompatible fields
236− fieldsToRemove := []string{
237− "additionalProperties",
238− "$schema",
239− "allOf",
240− "anyOf",
241− "oneOf",
242− "exclusiveMinimum",
243− "exclusiveMaximum",
244− "patternProperties",
245− "dependencies",
246− }
247−
248− result := schemaJSON
249− var err error
250−
251− for _, field := range fieldsToRemove {
252− result, err = sjson.Delete(result, field)
253− if err != nil {
254− continue // Continue even if deletion fails
255− }
256− }
257−
258− // Handle type arrays by converting them to single types
259− result = sanitizeTypeFields(result)
260−
261− // Recursively clean nested objects
262− result = cleanNestedSchemas(result)
263−
264− return result, nil
265−}
266−
267−// sanitizeTypeFields converts type arrays to single types for Gemini compatibility
268−func sanitizeTypeFields(jsonStr string) string {
269− // Parse the JSON to find all "type" fields
270− parsed := gjson.Parse(jsonStr)
271− result := jsonStr
272−
273− // Walk through all paths to find type fields
274− var typeFields []string
275− walkForTypeFields(parsed, "", &typeFields)
276−
277− // Process each type field
278− for _, path := range typeFields {
279− typeValue := gjson.Get(result, path)
280− if typeValue.IsArray() {
281− // Convert array to single type (prioritize string, then others)
282− arr := typeValue.Array()
283− if len(arr) > 0 {
284− var preferredType string
285− for _, t := range arr {
286− typeStr := t.String()
287− if typeStr == "string" {
288− preferredType = "string"
289− break
290− } else if typeStr == "number" || typeStr == "integer" {
291− preferredType = typeStr
292− } else if preferredType == "" {
293− preferredType = typeStr
294− }
295− }
296− if preferredType != "" {
297− result, _ = sjson.Set(result, path, preferredType)
298− }
299− }
300− }
301− }
302−
303− return result
304−}
305−
306−// walkForTypeFields recursively finds all "type" field paths in the JSON
307−func walkForTypeFields(value gjson.Result, path string, paths *[]string) {
308− switch value.Type {
309− case gjson.JSON:
310− value.ForEach(func(key, val gjson.Result) bool {
311− var childPath string
312− if path == "" {
313− childPath = key.String()
314− } else {
315− childPath = path + "." + key.String()
316− }
317− if key.String() == "type" {
318− *paths = append(*paths, childPath)
319− }
320− walkForTypeFields(val, childPath, paths)
321− return true
322− })
323− default:
324−
325− }
326−}
327−
328−// cleanNestedSchemas recursively removes incompatible fields from nested schema objects
329−func cleanNestedSchemas(jsonStr string) string {
330− fieldsToRemove := []string{"allOf", "anyOf", "oneOf", "exclusiveMinimum", "exclusiveMaximum"}
331−
332− // Find all nested paths that might contain these fields
333− var pathsToClean []string
334− parsed := gjson.Parse(jsonStr)
335− findNestedSchemaPaths(parsed, "", fieldsToRemove, &pathsToClean)
336−
337− result := jsonStr
338− // Remove fields from all found paths
339− for _, path := range pathsToClean {
340− result, _ = sjson.Delete(result, path)
341− }
342−
343− return result
344−}
345−
346−// findNestedSchemaPaths recursively finds paths containing incompatible schema fields
347−func findNestedSchemaPaths(value gjson.Result, path string, fieldsToFind []string, paths *[]string) {
348− switch value.Type {
349− case gjson.JSON:
350− value.ForEach(func(key, val gjson.Result) bool {
351− var childPath string
352− if path == "" {
353− childPath = key.String()
354− } else {
355− childPath = path + "." + key.String()
356− }
357−
358− // Check if this key is one we want to remove
359− for _, field := range fieldsToFind {
360− if key.String() == field {
361− *paths = append(*paths, childPath)
362− break
363− }
364− }
365−
366− findNestedSchemaPaths(val, childPath, fieldsToFind, paths)
367− return true
368− })
369− default:
370−
371− }
372−}