diff --git a/internal/util/gemini_schema.go b/internal/util/gemini_schema.go index 38d3773e..c7cb0f40 100644 --- a/internal/util/gemini_schema.go +++ b/internal/util/gemini_schema.go @@ -19,6 +19,7 @@ func CleanJSONSchemaForAntigravity(jsonStr string) string { // Phase 1: Convert and add hints jsonStr = convertRefsToHints(jsonStr) jsonStr = convertConstToEnum(jsonStr) + jsonStr = convertEnumValuesToStrings(jsonStr) jsonStr = addEnumHints(jsonStr) jsonStr = addAdditionalPropertiesHints(jsonStr) jsonStr = moveConstraintsToDescription(jsonStr) @@ -77,6 +78,33 @@ func convertConstToEnum(jsonStr string) string { return jsonStr } +// convertEnumValuesToStrings ensures all enum values are strings. +// Gemini API requires enum values to be of type string, not numbers or booleans. +func convertEnumValuesToStrings(jsonStr string) string { + for _, p := range findPaths(jsonStr, "enum") { + arr := gjson.Get(jsonStr, p) + if !arr.IsArray() { + continue + } + + var stringVals []string + needsConversion := false + for _, item := range arr.Array() { + // Check if any value is not a string + if item.Type != gjson.String { + needsConversion = true + } + stringVals = append(stringVals, item.String()) + } + + // Only update if we found non-string values + if needsConversion { + jsonStr, _ = sjson.Set(jsonStr, p, stringVals) + } + } + return jsonStr +} + func addEnumHints(jsonStr string) string { for _, p := range findPaths(jsonStr, "enum") { arr := gjson.Get(jsonStr, p) diff --git a/internal/util/gemini_schema_test.go b/internal/util/gemini_schema_test.go index 60335f22..ca77225e 100644 --- a/internal/util/gemini_schema_test.go +++ b/internal/util/gemini_schema_test.go @@ -818,3 +818,54 @@ func TestCleanJSONSchemaForAntigravity_MultipleFormats(t *testing.T) { t.Errorf("date-time format hint should be added, got: %s", result) } } + +func TestCleanJSONSchemaForAntigravity_NumericEnumToString(t *testing.T) { + // Gemini API requires enum values to be strings, not numbers + input := `{ + "type": "object", + "properties": { + "priority": {"type": "integer", "enum": [0, 1, 2]}, + "level": {"type": "number", "enum": [1.5, 2.5, 3.5]}, + "status": {"type": "string", "enum": ["active", "inactive"]} + } + }` + + result := CleanJSONSchemaForAntigravity(input) + + // Numeric enum values should be converted to strings + if strings.Contains(result, `"enum":[0,1,2]`) { + t.Errorf("Integer enum values should be converted to strings, got: %s", result) + } + if strings.Contains(result, `"enum":[1.5,2.5,3.5]`) { + t.Errorf("Float enum values should be converted to strings, got: %s", result) + } + // Should contain string versions + if !strings.Contains(result, `"0"`) || !strings.Contains(result, `"1"`) || !strings.Contains(result, `"2"`) { + t.Errorf("Integer enum values should be converted to string format, got: %s", result) + } + // String enum values should remain unchanged + if !strings.Contains(result, `"active"`) || !strings.Contains(result, `"inactive"`) { + t.Errorf("String enum values should remain unchanged, got: %s", result) + } +} + +func TestCleanJSONSchemaForAntigravity_BooleanEnumToString(t *testing.T) { + // Boolean enum values should also be converted to strings + input := `{ + "type": "object", + "properties": { + "enabled": {"type": "boolean", "enum": [true, false]} + } + }` + + result := CleanJSONSchemaForAntigravity(input) + + // Boolean enum values should be converted to strings + if strings.Contains(result, `"enum":[true,false]`) { + t.Errorf("Boolean enum values should be converted to strings, got: %s", result) + } + // Should contain string versions "true" and "false" + if !strings.Contains(result, `"true"`) || !strings.Contains(result, `"false"`) { + t.Errorf("Boolean enum values should be converted to string format, got: %s", result) + } +}