fix(antigravity): preserve string const constraints in tool schemas

This commit is contained in:
Achord Chan
2026-09-20 10:46:09 +08:00
parent 1a9d49e16f
commit ef51690b5a
2 changed files with 65 additions and 0 deletions
@@ -119,6 +119,27 @@ func cleanJSONSchemaRecursive(value any) any {
// 0. [NEW] 合并 allOf
mergeAllOf(schemaMap)
// Gemini's enum representation supports strings. Preserve string constants
// before the allowlist removes const, including schemas with no explicit type.
if constant, ok := schemaMap["const"].(string); ok {
values := []any{constant}
if existing, ok := schemaMap["enum"].([]any); ok {
// Both constraints apply: retain their intersection, not a wider enum.
values = []any{}
for _, value := range existing {
if text, ok := value.(string); ok && text == constant {
values = append(values, constant)
break
}
}
}
schemaMap["enum"] = values
if _, exists := schemaMap["type"]; !exists {
schemaMap["type"] = "string"
}
delete(schemaMap, "const")
}
// 1. [CRITICAL] 深度递归处理子项
if props, ok := schemaMap["properties"].(map[string]any); ok {
for _, v := range props {
@@ -0,0 +1,44 @@
package antigravity
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
func TestBuildToolsPreservesStringConst(t *testing.T) {
for _, tc := range []struct {
name string
schema string
want string
}{
{"typed", `{"type":"string","const":"browser"}`, `{"type":"string","enum":["browser"]}`},
{"inferred", `{"const":"browser"}`, `{"type":"string","enum":["browser"]}`},
{"empty", `{"type":"string","const":""}`, `{"type":"string","enum":[""]}`},
{"existing enum", `{"type":"string","const":"browser","enum":["browser","shell"]}`, `{"type":"string","enum":["browser"]}`},
{"conflicting enum", `{"type":"string","const":"browser","enum":["shell"]}`, `{"type":"string","enum":[]}`},
{"ordinary enum", `{"type":"string","enum":["browser","shell"]}`, `{"type":"string","enum":["browser","shell"]}`},
{"array items", `{"type":"array","items":{"type":"string","const":"browser"}}`, `{"type":"array","items":{"type":"string","enum":["browser"]}}`},
{"nested property named const", `{"type":"object","properties":{"const":{"const":"browser"}}}`, `{"type":"object","properties":{"const":{"type":"string","enum":["browser"]}}}`},
{"schema metadata", `{"type":"string","const":"browser","$schema":"https://json-schema.org/draft/2020-12/schema","description":"Tool kind"}`, `{"type":"string","enum":["browser"],"description":"Tool kind"}`},
} {
t.Run(tc.name, func(t *testing.T) {
var property map[string]any
require.NoError(t, json.Unmarshal([]byte(tc.schema), &property))
tools := buildTools([]ClaudeTool{{
Name: "dispatch",
InputSchema: map[string]any{
"type": "object",
"properties": map[string]any{"action": property},
},
}})
require.Len(t, tools, 1)
require.Len(t, tools[0].FunctionDeclarations, 1)
properties := tools[0].FunctionDeclarations[0].Parameters["properties"].(map[string]any)
got, err := json.Marshal(properties["action"])
require.NoError(t, err)
require.JSONEq(t, tc.want, string(got))
})
}
}