refactor(util/schema): rename and extend Gemini schema cleaning for Antigravity and add empty-schema placeholders

This commit is contained in:
이대희
2025-12-19 10:27:24 +09:00
parent 1bfa75f780
commit e44167d7a4
2 changed files with 302 additions and 47 deletions

View File

@@ -12,10 +12,10 @@ import (
var gjsonPathKeyReplacer = strings.NewReplacer(".", "\\.", "*", "\\*", "?", "\\?") var gjsonPathKeyReplacer = strings.NewReplacer(".", "\\.", "*", "\\*", "?", "\\?")
// CleanJSONSchemaForGemini transforms a JSON schema to be compatible with Gemini/Antigravity API. // CleanJSONSchemaForAntigravity transforms a JSON schema to be compatible with Antigravity API.
// It handles unsupported keywords, type flattening, and schema simplification while preserving // It handles unsupported keywords, type flattening, and schema simplification while preserving
// semantic information as description hints. // semantic information as description hints.
func CleanJSONSchemaForGemini(jsonStr string) string { func CleanJSONSchemaForAntigravity(jsonStr string) string {
// Phase 1: Convert and add hints // Phase 1: Convert and add hints
jsonStr = convertRefsToHints(jsonStr) jsonStr = convertRefsToHints(jsonStr)
jsonStr = convertConstToEnum(jsonStr) jsonStr = convertConstToEnum(jsonStr)
@@ -32,6 +32,9 @@ func CleanJSONSchemaForGemini(jsonStr string) string {
jsonStr = removeUnsupportedKeywords(jsonStr) jsonStr = removeUnsupportedKeywords(jsonStr)
jsonStr = cleanupRequiredFields(jsonStr) jsonStr = cleanupRequiredFields(jsonStr)
// Phase 4: Add placeholder for empty object schemas (Claude VALIDATED mode requirement)
jsonStr = addEmptySchemaPlaceholder(jsonStr)
return jsonStr return jsonStr
} }
@@ -105,7 +108,8 @@ func addAdditionalPropertiesHints(jsonStr string) string {
var unsupportedConstraints = []string{ var unsupportedConstraints = []string{
"minLength", "maxLength", "exclusiveMinimum", "exclusiveMaximum", "minLength", "maxLength", "exclusiveMinimum", "exclusiveMaximum",
"pattern", "minItems", "maxItems", "pattern", "minItems", "maxItems", "format",
"default", "examples", // Claude rejects these in VALIDATED mode
} }
func moveConstraintsToDescription(jsonStr string) string { func moveConstraintsToDescription(jsonStr string) string {
@@ -338,6 +342,52 @@ func cleanupRequiredFields(jsonStr string) string {
return jsonStr return jsonStr
} }
// addEmptySchemaPlaceholder adds a placeholder "reason" property to empty object schemas.
// Claude VALIDATED mode requires at least one property in tool schemas.
func addEmptySchemaPlaceholder(jsonStr string) string {
// Find all "type" fields
paths := findPaths(jsonStr, "type")
// Process from deepest to shallowest (to handle nested objects properly)
sortByDepth(paths)
for _, p := range paths {
typeVal := gjson.Get(jsonStr, p)
if typeVal.String() != "object" {
continue
}
// Get the parent path (the object containing "type")
parentPath := trimSuffix(p, ".type")
// Check if properties exists and is empty or missing
propsPath := joinPath(parentPath, "properties")
propsVal := gjson.Get(jsonStr, propsPath)
needsPlaceholder := false
if !propsVal.Exists() {
// No properties field at all
needsPlaceholder = true
} else if propsVal.IsObject() && len(propsVal.Map()) == 0 {
// Empty properties object
needsPlaceholder = true
}
if needsPlaceholder {
// Add placeholder "reason" property
reasonPath := joinPath(propsPath, "reason")
jsonStr, _ = sjson.Set(jsonStr, reasonPath+".type", "string")
jsonStr, _ = sjson.Set(jsonStr, reasonPath+".description", "Brief explanation of why you are calling this tool")
// Add to required array
reqPath := joinPath(parentPath, "required")
jsonStr, _ = sjson.Set(jsonStr, reqPath, []string{"reason"})
}
}
return jsonStr
}
// --- Helpers --- // --- Helpers ---
func findPaths(jsonStr, field string) []string { func findPaths(jsonStr, field string) []string {

View File

@@ -5,9 +5,11 @@ import (
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
"github.com/tidwall/gjson"
) )
func TestCleanJSONSchemaForGemini_ConstToEnum(t *testing.T) { func TestCleanJSONSchemaForAntigravity_ConstToEnum(t *testing.T) {
input := `{ input := `{
"type": "object", "type": "object",
"properties": { "properties": {
@@ -28,11 +30,11 @@ func TestCleanJSONSchemaForGemini_ConstToEnum(t *testing.T) {
} }
}` }`
result := CleanJSONSchemaForGemini(input) result := CleanJSONSchemaForAntigravity(input)
compareJSON(t, expected, result) compareJSON(t, expected, result)
} }
func TestCleanJSONSchemaForGemini_TypeFlattening_Nullable(t *testing.T) { func TestCleanJSONSchemaForAntigravity_TypeFlattening_Nullable(t *testing.T) {
input := `{ input := `{
"type": "object", "type": "object",
"properties": { "properties": {
@@ -60,11 +62,11 @@ func TestCleanJSONSchemaForGemini_TypeFlattening_Nullable(t *testing.T) {
"required": ["other"] "required": ["other"]
}` }`
result := CleanJSONSchemaForGemini(input) result := CleanJSONSchemaForAntigravity(input)
compareJSON(t, expected, result) compareJSON(t, expected, result)
} }
func TestCleanJSONSchemaForGemini_ConstraintsToDescription(t *testing.T) { func TestCleanJSONSchemaForAntigravity_ConstraintsToDescription(t *testing.T) {
input := `{ input := `{
"type": "object", "type": "object",
"properties": { "properties": {
@@ -81,7 +83,7 @@ func TestCleanJSONSchemaForGemini_ConstraintsToDescription(t *testing.T) {
} }
}` }`
result := CleanJSONSchemaForGemini(input) result := CleanJSONSchemaForAntigravity(input)
// minItems should be REMOVED and moved to description // minItems should be REMOVED and moved to description
if strings.Contains(result, `"minItems"`) { if strings.Contains(result, `"minItems"`) {
@@ -100,7 +102,7 @@ func TestCleanJSONSchemaForGemini_ConstraintsToDescription(t *testing.T) {
} }
} }
func TestCleanJSONSchemaForGemini_AnyOfFlattening_SmartSelection(t *testing.T) { func TestCleanJSONSchemaForAntigravity_AnyOfFlattening_SmartSelection(t *testing.T) {
input := `{ input := `{
"type": "object", "type": "object",
"properties": { "properties": {
@@ -131,11 +133,11 @@ func TestCleanJSONSchemaForGemini_AnyOfFlattening_SmartSelection(t *testing.T) {
} }
}` }`
result := CleanJSONSchemaForGemini(input) result := CleanJSONSchemaForAntigravity(input)
compareJSON(t, expected, result) compareJSON(t, expected, result)
} }
func TestCleanJSONSchemaForGemini_OneOfFlattening(t *testing.T) { func TestCleanJSONSchemaForAntigravity_OneOfFlattening(t *testing.T) {
input := `{ input := `{
"type": "object", "type": "object",
"properties": { "properties": {
@@ -158,11 +160,11 @@ func TestCleanJSONSchemaForGemini_OneOfFlattening(t *testing.T) {
} }
}` }`
result := CleanJSONSchemaForGemini(input) result := CleanJSONSchemaForAntigravity(input)
compareJSON(t, expected, result) compareJSON(t, expected, result)
} }
func TestCleanJSONSchemaForGemini_AllOfMerging(t *testing.T) { func TestCleanJSONSchemaForAntigravity_AllOfMerging(t *testing.T) {
input := `{ input := `{
"type": "object", "type": "object",
"allOf": [ "allOf": [
@@ -190,11 +192,11 @@ func TestCleanJSONSchemaForGemini_AllOfMerging(t *testing.T) {
"required": ["a", "b"] "required": ["a", "b"]
}` }`
result := CleanJSONSchemaForGemini(input) result := CleanJSONSchemaForAntigravity(input)
compareJSON(t, expected, result) compareJSON(t, expected, result)
} }
func TestCleanJSONSchemaForGemini_RefHandling(t *testing.T) { func TestCleanJSONSchemaForAntigravity_RefHandling(t *testing.T) {
input := `{ input := `{
"definitions": { "definitions": {
"User": { "User": {
@@ -210,21 +212,29 @@ func TestCleanJSONSchemaForGemini_RefHandling(t *testing.T) {
} }
}` }`
// After $ref is converted to placeholder object, empty schema placeholder is also added
expected := `{ expected := `{
"type": "object", "type": "object",
"properties": { "properties": {
"customer": { "customer": {
"type": "object", "type": "object",
"description": "See: User" "description": "See: User",
"properties": {
"reason": {
"type": "string",
"description": "Brief explanation of why you are calling this tool"
}
},
"required": ["reason"]
} }
} }
}` }`
result := CleanJSONSchemaForGemini(input) result := CleanJSONSchemaForAntigravity(input)
compareJSON(t, expected, result) compareJSON(t, expected, result)
} }
func TestCleanJSONSchemaForGemini_RefHandling_DescriptionEscaping(t *testing.T) { func TestCleanJSONSchemaForAntigravity_RefHandling_DescriptionEscaping(t *testing.T) {
input := `{ input := `{
"definitions": { "definitions": {
"User": { "User": {
@@ -243,21 +253,29 @@ func TestCleanJSONSchemaForGemini_RefHandling_DescriptionEscaping(t *testing.T)
} }
}` }`
// After $ref is converted, empty schema placeholder is also added
expected := `{ expected := `{
"type": "object", "type": "object",
"properties": { "properties": {
"customer": { "customer": {
"type": "object", "type": "object",
"description": "He said \"hi\"\\nsecond line (See: User)" "description": "He said \"hi\"\\nsecond line (See: User)",
"properties": {
"reason": {
"type": "string",
"description": "Brief explanation of why you are calling this tool"
}
},
"required": ["reason"]
} }
} }
}` }`
result := CleanJSONSchemaForGemini(input) result := CleanJSONSchemaForAntigravity(input)
compareJSON(t, expected, result) compareJSON(t, expected, result)
} }
func TestCleanJSONSchemaForGemini_CyclicRefDefaults(t *testing.T) { func TestCleanJSONSchemaForAntigravity_CyclicRefDefaults(t *testing.T) {
input := `{ input := `{
"definitions": { "definitions": {
"Node": { "Node": {
@@ -270,7 +288,7 @@ func TestCleanJSONSchemaForGemini_CyclicRefDefaults(t *testing.T) {
"$ref": "#/definitions/Node" "$ref": "#/definitions/Node"
}` }`
result := CleanJSONSchemaForGemini(input) result := CleanJSONSchemaForAntigravity(input)
var resMap map[string]interface{} var resMap map[string]interface{}
json.Unmarshal([]byte(result), &resMap) json.Unmarshal([]byte(result), &resMap)
@@ -285,7 +303,7 @@ func TestCleanJSONSchemaForGemini_CyclicRefDefaults(t *testing.T) {
} }
} }
func TestCleanJSONSchemaForGemini_RequiredCleanup(t *testing.T) { func TestCleanJSONSchemaForAntigravity_RequiredCleanup(t *testing.T) {
input := `{ input := `{
"type": "object", "type": "object",
"properties": { "properties": {
@@ -304,11 +322,11 @@ func TestCleanJSONSchemaForGemini_RequiredCleanup(t *testing.T) {
"required": ["a", "b"] "required": ["a", "b"]
}` }`
result := CleanJSONSchemaForGemini(input) result := CleanJSONSchemaForAntigravity(input)
compareJSON(t, expected, result) compareJSON(t, expected, result)
} }
func TestCleanJSONSchemaForGemini_AllOfMerging_DotKeys(t *testing.T) { func TestCleanJSONSchemaForAntigravity_AllOfMerging_DotKeys(t *testing.T) {
input := `{ input := `{
"type": "object", "type": "object",
"allOf": [ "allOf": [
@@ -336,11 +354,11 @@ func TestCleanJSONSchemaForGemini_AllOfMerging_DotKeys(t *testing.T) {
"required": ["my.param", "b"] "required": ["my.param", "b"]
}` }`
result := CleanJSONSchemaForGemini(input) result := CleanJSONSchemaForAntigravity(input)
compareJSON(t, expected, result) compareJSON(t, expected, result)
} }
func TestCleanJSONSchemaForGemini_PropertyNameCollision(t *testing.T) { func TestCleanJSONSchemaForAntigravity_PropertyNameCollision(t *testing.T) {
// A tool has an argument named "pattern" - should NOT be treated as a constraint // A tool has an argument named "pattern" - should NOT be treated as a constraint
input := `{ input := `{
"type": "object", "type": "object",
@@ -364,7 +382,7 @@ func TestCleanJSONSchemaForGemini_PropertyNameCollision(t *testing.T) {
"required": ["pattern"] "required": ["pattern"]
}` }`
result := CleanJSONSchemaForGemini(input) result := CleanJSONSchemaForAntigravity(input)
compareJSON(t, expected, result) compareJSON(t, expected, result)
var resMap map[string]interface{} var resMap map[string]interface{}
@@ -375,7 +393,7 @@ func TestCleanJSONSchemaForGemini_PropertyNameCollision(t *testing.T) {
} }
} }
func TestCleanJSONSchemaForGemini_DotKeys(t *testing.T) { func TestCleanJSONSchemaForAntigravity_DotKeys(t *testing.T) {
input := `{ input := `{
"type": "object", "type": "object",
"properties": { "properties": {
@@ -389,7 +407,7 @@ func TestCleanJSONSchemaForGemini_DotKeys(t *testing.T) {
} }
}` }`
result := CleanJSONSchemaForGemini(input) result := CleanJSONSchemaForAntigravity(input)
var resMap map[string]interface{} var resMap map[string]interface{}
if err := json.Unmarshal([]byte(result), &resMap); err != nil { if err := json.Unmarshal([]byte(result), &resMap); err != nil {
@@ -414,7 +432,7 @@ func TestCleanJSONSchemaForGemini_DotKeys(t *testing.T) {
} }
} }
func TestCleanJSONSchemaForGemini_AnyOfAlternativeHints(t *testing.T) { func TestCleanJSONSchemaForAntigravity_AnyOfAlternativeHints(t *testing.T) {
input := `{ input := `{
"type": "object", "type": "object",
"properties": { "properties": {
@@ -428,7 +446,7 @@ func TestCleanJSONSchemaForGemini_AnyOfAlternativeHints(t *testing.T) {
} }
}` }`
result := CleanJSONSchemaForGemini(input) result := CleanJSONSchemaForAntigravity(input)
if !strings.Contains(result, "Accepts:") { if !strings.Contains(result, "Accepts:") {
t.Errorf("Expected alternative types hint, got: %s", result) t.Errorf("Expected alternative types hint, got: %s", result)
@@ -438,7 +456,7 @@ func TestCleanJSONSchemaForGemini_AnyOfAlternativeHints(t *testing.T) {
} }
} }
func TestCleanJSONSchemaForGemini_NullableHint(t *testing.T) { func TestCleanJSONSchemaForAntigravity_NullableHint(t *testing.T) {
input := `{ input := `{
"type": "object", "type": "object",
"properties": { "properties": {
@@ -450,7 +468,7 @@ func TestCleanJSONSchemaForGemini_NullableHint(t *testing.T) {
"required": ["name"] "required": ["name"]
}` }`
result := CleanJSONSchemaForGemini(input) result := CleanJSONSchemaForAntigravity(input)
if !strings.Contains(result, "(nullable)") { if !strings.Contains(result, "(nullable)") {
t.Errorf("Expected nullable hint, got: %s", result) t.Errorf("Expected nullable hint, got: %s", result)
@@ -460,7 +478,7 @@ func TestCleanJSONSchemaForGemini_NullableHint(t *testing.T) {
} }
} }
func TestCleanJSONSchemaForGemini_TypeFlattening_Nullable_DotKey(t *testing.T) { func TestCleanJSONSchemaForAntigravity_TypeFlattening_Nullable_DotKey(t *testing.T) {
input := `{ input := `{
"type": "object", "type": "object",
"properties": { "properties": {
@@ -488,11 +506,11 @@ func TestCleanJSONSchemaForGemini_TypeFlattening_Nullable_DotKey(t *testing.T) {
"required": ["other"] "required": ["other"]
}` }`
result := CleanJSONSchemaForGemini(input) result := CleanJSONSchemaForAntigravity(input)
compareJSON(t, expected, result) compareJSON(t, expected, result)
} }
func TestCleanJSONSchemaForGemini_EnumHint(t *testing.T) { func TestCleanJSONSchemaForAntigravity_EnumHint(t *testing.T) {
input := `{ input := `{
"type": "object", "type": "object",
"properties": { "properties": {
@@ -504,7 +522,7 @@ func TestCleanJSONSchemaForGemini_EnumHint(t *testing.T) {
} }
}` }`
result := CleanJSONSchemaForGemini(input) result := CleanJSONSchemaForAntigravity(input)
if !strings.Contains(result, "Allowed:") { if !strings.Contains(result, "Allowed:") {
t.Errorf("Expected enum values hint, got: %s", result) t.Errorf("Expected enum values hint, got: %s", result)
@@ -514,7 +532,7 @@ func TestCleanJSONSchemaForGemini_EnumHint(t *testing.T) {
} }
} }
func TestCleanJSONSchemaForGemini_AdditionalPropertiesHint(t *testing.T) { func TestCleanJSONSchemaForAntigravity_AdditionalPropertiesHint(t *testing.T) {
input := `{ input := `{
"type": "object", "type": "object",
"properties": { "properties": {
@@ -523,14 +541,14 @@ func TestCleanJSONSchemaForGemini_AdditionalPropertiesHint(t *testing.T) {
"additionalProperties": false "additionalProperties": false
}` }`
result := CleanJSONSchemaForGemini(input) result := CleanJSONSchemaForAntigravity(input)
if !strings.Contains(result, "No extra properties allowed") { if !strings.Contains(result, "No extra properties allowed") {
t.Errorf("Expected additionalProperties hint, got: %s", result) t.Errorf("Expected additionalProperties hint, got: %s", result)
} }
} }
func TestCleanJSONSchemaForGemini_AnyOfFlattening_PreservesDescription(t *testing.T) { func TestCleanJSONSchemaForAntigravity_AnyOfFlattening_PreservesDescription(t *testing.T) {
input := `{ input := `{
"type": "object", "type": "object",
"properties": { "properties": {
@@ -554,11 +572,11 @@ func TestCleanJSONSchemaForGemini_AnyOfFlattening_PreservesDescription(t *testin
} }
}` }`
result := CleanJSONSchemaForGemini(input) result := CleanJSONSchemaForAntigravity(input)
compareJSON(t, expected, result) compareJSON(t, expected, result)
} }
func TestCleanJSONSchemaForGemini_SingleEnumNoHint(t *testing.T) { func TestCleanJSONSchemaForAntigravity_SingleEnumNoHint(t *testing.T) {
input := `{ input := `{
"type": "object", "type": "object",
"properties": { "properties": {
@@ -569,14 +587,14 @@ func TestCleanJSONSchemaForGemini_SingleEnumNoHint(t *testing.T) {
} }
}` }`
result := CleanJSONSchemaForGemini(input) result := CleanJSONSchemaForAntigravity(input)
if strings.Contains(result, "Allowed:") { if strings.Contains(result, "Allowed:") {
t.Errorf("Single value enum should not add Allowed hint, got: %s", result) t.Errorf("Single value enum should not add Allowed hint, got: %s", result)
} }
} }
func TestCleanJSONSchemaForGemini_MultipleNonNullTypes(t *testing.T) { func TestCleanJSONSchemaForAntigravity_MultipleNonNullTypes(t *testing.T) {
input := `{ input := `{
"type": "object", "type": "object",
"properties": { "properties": {
@@ -586,7 +604,7 @@ func TestCleanJSONSchemaForGemini_MultipleNonNullTypes(t *testing.T) {
} }
}` }`
result := CleanJSONSchemaForGemini(input) result := CleanJSONSchemaForAntigravity(input)
if !strings.Contains(result, "Accepts:") { if !strings.Contains(result, "Accepts:") {
t.Errorf("Expected multiple types hint, got: %s", result) t.Errorf("Expected multiple types hint, got: %s", result)
@@ -611,3 +629,190 @@ func compareJSON(t *testing.T, expectedJSON, actualJSON string) {
t.Errorf("JSON mismatch:\nExpected:\n%s\n\nActual:\n%s", string(expBytes), string(actBytes)) t.Errorf("JSON mismatch:\nExpected:\n%s\n\nActual:\n%s", string(expBytes), string(actBytes))
} }
} }
// ============================================================================
// P0-1: Empty Schema Placeholder Tests
// ============================================================================
func TestCleanJSONSchemaForAntigravity_EmptySchemaPlaceholder(t *testing.T) {
// Empty object schema with no properties should get a placeholder
input := `{
"type": "object"
}`
result := CleanJSONSchemaForAntigravity(input)
// Should have placeholder property added
if !strings.Contains(result, `"reason"`) {
t.Errorf("Empty schema should have 'reason' placeholder property, got: %s", result)
}
if !strings.Contains(result, `"required"`) {
t.Errorf("Empty schema should have 'required' with 'reason', got: %s", result)
}
}
func TestCleanJSONSchemaForAntigravity_EmptyPropertiesPlaceholder(t *testing.T) {
// Object with empty properties object
input := `{
"type": "object",
"properties": {}
}`
result := CleanJSONSchemaForAntigravity(input)
// Should have placeholder property added
if !strings.Contains(result, `"reason"`) {
t.Errorf("Empty properties should have 'reason' placeholder, got: %s", result)
}
}
func TestCleanJSONSchemaForAntigravity_NonEmptySchemaUnchanged(t *testing.T) {
// Schema with properties should NOT get placeholder
input := `{
"type": "object",
"properties": {
"name": {"type": "string"}
},
"required": ["name"]
}`
result := CleanJSONSchemaForAntigravity(input)
// Should NOT have placeholder property
if strings.Contains(result, `"reason"`) {
t.Errorf("Non-empty schema should NOT have 'reason' placeholder, got: %s", result)
}
// Original properties should be preserved
if !strings.Contains(result, `"name"`) {
t.Errorf("Original property 'name' should be preserved, got: %s", result)
}
}
func TestCleanJSONSchemaForAntigravity_NestedEmptySchema(t *testing.T) {
// Nested empty object in items should also get placeholder
input := `{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object"
}
}
}
}`
result := CleanJSONSchemaForAntigravity(input)
// Nested empty object should also get placeholder
// Check that the nested object has a reason property
parsed := gjson.Parse(result)
nestedProps := parsed.Get("properties.items.items.properties")
if !nestedProps.Exists() || !nestedProps.Get("reason").Exists() {
t.Errorf("Nested empty object should have 'reason' placeholder, got: %s", result)
}
}
func TestCleanJSONSchemaForAntigravity_EmptySchemaWithDescription(t *testing.T) {
// Empty schema with description should preserve description and add placeholder
input := `{
"type": "object",
"description": "An empty object"
}`
result := CleanJSONSchemaForAntigravity(input)
// Should have both description and placeholder
if !strings.Contains(result, `"An empty object"`) {
t.Errorf("Description should be preserved, got: %s", result)
}
if !strings.Contains(result, `"reason"`) {
t.Errorf("Empty schema should have 'reason' placeholder, got: %s", result)
}
}
// ============================================================================
// P0-2: Format field handling (ad-hoc patch removal)
// ============================================================================
func TestCleanJSONSchemaForAntigravity_FormatFieldRemoval(t *testing.T) {
// format:"uri" should be removed and added as hint
input := `{
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "A URL"
}
}
}`
result := CleanJSONSchemaForAntigravity(input)
// format should be removed
if strings.Contains(result, `"format"`) {
t.Errorf("format field should be removed, got: %s", result)
}
// hint should be added to description
if !strings.Contains(result, "format: uri") {
t.Errorf("format hint should be added to description, got: %s", result)
}
// original description should be preserved
if !strings.Contains(result, "A URL") {
t.Errorf("Original description should be preserved, got: %s", result)
}
}
func TestCleanJSONSchemaForAntigravity_FormatFieldNoDescription(t *testing.T) {
// format without description should create description with hint
input := `{
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email"
}
}
}`
result := CleanJSONSchemaForAntigravity(input)
// format should be removed
if strings.Contains(result, `"format"`) {
t.Errorf("format field should be removed, got: %s", result)
}
// hint should be added
if !strings.Contains(result, "format: email") {
t.Errorf("format hint should be added, got: %s", result)
}
}
func TestCleanJSONSchemaForAntigravity_MultipleFormats(t *testing.T) {
// Multiple format fields should all be handled
input := `{
"type": "object",
"properties": {
"url": {"type": "string", "format": "uri"},
"email": {"type": "string", "format": "email"},
"date": {"type": "string", "format": "date-time"}
}
}`
result := CleanJSONSchemaForAntigravity(input)
// All format fields should be removed
if strings.Contains(result, `"format"`) {
t.Errorf("All format fields should be removed, got: %s", result)
}
// All hints should be added
if !strings.Contains(result, "format: uri") {
t.Errorf("uri format hint should be added, got: %s", result)
}
if !strings.Contains(result, "format: email") {
t.Errorf("email format hint should be added, got: %s", result)
}
if !strings.Contains(result, "format: date-time") {
t.Errorf("date-time format hint should be added, got: %s", result)
}
}