mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-02 20:40:52 +08:00
fix(gemini): Removes unsupported extension fields
Removes x-* extension fields from JSON schemas to ensure compatibility with the Gemini API. These fields, while valid in OpenAPI/JSON Schema, are not recognized by the Gemini API and can cause issues. The change recursively walks the schema, identifies these extension fields, and removes them, except when they define properties. Amp-Thread-ID: https://ampcode.com/threads/T-019c0cd1-9e59-722b-83f0-e0582aba6914 Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -431,9 +431,43 @@ func removeUnsupportedKeywords(jsonStr string) string {
|
||||
jsonStr, _ = sjson.Delete(jsonStr, p)
|
||||
}
|
||||
}
|
||||
// Remove x-* extension fields (e.g., x-google-enum-descriptions) that are not supported by Gemini API
|
||||
jsonStr = removeExtensionFields(jsonStr)
|
||||
return jsonStr
|
||||
}
|
||||
|
||||
// removeExtensionFields removes all x-* extension fields from the JSON schema.
|
||||
// These are OpenAPI/JSON Schema extension fields that Google APIs don't recognize.
|
||||
func removeExtensionFields(jsonStr string) string {
|
||||
var paths []string
|
||||
walkForExtensions(gjson.Parse(jsonStr), "", &paths)
|
||||
sortByDepth(paths)
|
||||
for _, p := range paths {
|
||||
jsonStr, _ = sjson.Delete(jsonStr, p)
|
||||
}
|
||||
return jsonStr
|
||||
}
|
||||
|
||||
func walkForExtensions(value gjson.Result, path string, paths *[]string) {
|
||||
if !value.IsObject() && !value.IsArray() {
|
||||
return
|
||||
}
|
||||
|
||||
value.ForEach(func(key, val gjson.Result) bool {
|
||||
keyStr := key.String()
|
||||
safeKey := escapeGJSONPathKey(keyStr)
|
||||
childPath := joinPath(path, safeKey)
|
||||
|
||||
// Only remove x-* extension fields, but protect them if they are property definitions.
|
||||
if strings.HasPrefix(keyStr, "x-") && !isPropertyDefinition(path) {
|
||||
*paths = append(*paths, childPath)
|
||||
}
|
||||
|
||||
walkForExtensions(val, childPath, paths)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func cleanupRequiredFields(jsonStr string) string {
|
||||
for _, p := range findPaths(jsonStr, "required") {
|
||||
parentPath := trimSuffix(p, ".required")
|
||||
|
||||
@@ -869,3 +869,129 @@ func TestCleanJSONSchemaForAntigravity_BooleanEnumToString(t *testing.T) {
|
||||
t.Errorf("Boolean enum values should be converted to string format, got: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveExtensionFields(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "removes x- fields at root",
|
||||
input: `{
|
||||
"type": "object",
|
||||
"x-custom-meta": "value",
|
||||
"properties": {
|
||||
"foo": { "type": "string" }
|
||||
}
|
||||
}`,
|
||||
expected: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": { "type": "string" }
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "removes x- fields in nested properties",
|
||||
input: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string",
|
||||
"x-internal-id": 123
|
||||
}
|
||||
}
|
||||
}`,
|
||||
expected: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "does NOT remove properties named x-",
|
||||
input: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x-data": { "type": "string" },
|
||||
"normal": { "type": "number", "x-meta": "remove" }
|
||||
},
|
||||
"required": ["x-data"]
|
||||
}`,
|
||||
expected: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x-data": { "type": "string" },
|
||||
"normal": { "type": "number" }
|
||||
},
|
||||
"required": ["x-data"]
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "does NOT remove $schema and other meta fields (as requested)",
|
||||
input: `{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "test",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": { "type": "string" }
|
||||
}
|
||||
}`,
|
||||
expected: `{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "test",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": { "type": "string" }
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "handles properties named $schema",
|
||||
input: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"$schema": { "type": "string" }
|
||||
}
|
||||
}`,
|
||||
expected: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"$schema": { "type": "string" }
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "handles escaping in paths",
|
||||
input: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo.bar": {
|
||||
"type": "string",
|
||||
"x-meta": "remove"
|
||||
}
|
||||
},
|
||||
"x-root.meta": "remove"
|
||||
}`,
|
||||
expected: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo.bar": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := removeExtensionFields(tt.input)
|
||||
compareJSON(t, tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user