mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-19 04:40:52 +08:00
Merge pull request #1311 from router-for-me/fix/gemini-schema
fix(gemini): Removes unsupported extension fields
This commit is contained in:
@@ -4,6 +4,7 @@ package util
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
@@ -431,9 +432,54 @@ func removeUnsupportedKeywords(jsonStr string) string {
|
|||||||
jsonStr, _ = sjson.Delete(jsonStr, p)
|
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
|
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)
|
||||||
|
// walkForExtensions returns paths in a way that deeper paths are added before their ancestors
|
||||||
|
// when they are not deleted wholesale, but since we skip children of deleted x-* nodes,
|
||||||
|
// any collected path is safe to delete. We still use DeleteBytes for efficiency.
|
||||||
|
|
||||||
|
b := []byte(jsonStr)
|
||||||
|
for _, p := range paths {
|
||||||
|
b, _ = sjson.DeleteBytes(b, p)
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func walkForExtensions(value gjson.Result, path string, paths *[]string) {
|
||||||
|
if value.IsArray() {
|
||||||
|
arr := value.Array()
|
||||||
|
for i := len(arr) - 1; i >= 0; i-- {
|
||||||
|
walkForExtensions(arr[i], joinPath(path, strconv.Itoa(i)), paths)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.IsObject() {
|
||||||
|
value.ForEach(func(key, val gjson.Result) bool {
|
||||||
|
keyStr := key.String()
|
||||||
|
safeKey := escapeGJSONPathKey(keyStr)
|
||||||
|
childPath := joinPath(path, safeKey)
|
||||||
|
|
||||||
|
// If it's an extension field, we delete it and don't need to look at its children.
|
||||||
|
if strings.HasPrefix(keyStr, "x-") && !isPropertyDefinition(path) {
|
||||||
|
*paths = append(*paths, childPath)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
walkForExtensions(val, childPath, paths)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func cleanupRequiredFields(jsonStr string) string {
|
func cleanupRequiredFields(jsonStr string) string {
|
||||||
for _, p := range findPaths(jsonStr, "required") {
|
for _, p := range findPaths(jsonStr, "required") {
|
||||||
parentPath := trimSuffix(p, ".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)
|
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