Merge pull request #1131 from sowar1987/fix/gemini-malformed-function-call
Fix Gemini tool calling for Antigravity (malformed_function_call)
This commit is contained in:
+53
-52
@@ -5,6 +5,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const testModelName = "claude-sonnet-4-5"
|
||||||
|
|
||||||
func TestCacheSignature_BasicStorageAndRetrieval(t *testing.T) {
|
func TestCacheSignature_BasicStorageAndRetrieval(t *testing.T) {
|
||||||
ClearSignatureCache("")
|
ClearSignatureCache("")
|
||||||
|
|
||||||
@@ -12,10 +14,10 @@ func TestCacheSignature_BasicStorageAndRetrieval(t *testing.T) {
|
|||||||
signature := "abc123validSignature1234567890123456789012345678901234567890"
|
signature := "abc123validSignature1234567890123456789012345678901234567890"
|
||||||
|
|
||||||
// Store signature
|
// Store signature
|
||||||
CacheSignature("test-model", text, signature)
|
CacheSignature(testModelName, text, signature)
|
||||||
|
|
||||||
// Retrieve signature
|
// Retrieve signature
|
||||||
retrieved := GetCachedSignature("test-model", text)
|
retrieved := GetCachedSignature(testModelName, text)
|
||||||
if retrieved != signature {
|
if retrieved != signature {
|
||||||
t.Errorf("Expected signature '%s', got '%s'", signature, retrieved)
|
t.Errorf("Expected signature '%s', got '%s'", signature, retrieved)
|
||||||
}
|
}
|
||||||
@@ -28,28 +30,29 @@ func TestCacheSignature_DifferentModelGroups(t *testing.T) {
|
|||||||
sig1 := "signature1_1234567890123456789012345678901234567890123456"
|
sig1 := "signature1_1234567890123456789012345678901234567890123456"
|
||||||
sig2 := "signature2_1234567890123456789012345678901234567890123456"
|
sig2 := "signature2_1234567890123456789012345678901234567890123456"
|
||||||
|
|
||||||
CacheSignature("claude-sonnet-4-5-thinking", text, sig1)
|
geminiModel := "gemini-3-pro-preview"
|
||||||
CacheSignature("gpt-4o", text, sig2)
|
CacheSignature(testModelName, text, sig1)
|
||||||
|
CacheSignature(geminiModel, text, sig2)
|
||||||
|
|
||||||
if GetCachedSignature("claude-sonnet-4-5-thinking", text) != sig1 {
|
if GetCachedSignature(testModelName, text) != sig1 {
|
||||||
t.Error("Claude signature mismatch")
|
t.Error("Claude signature mismatch")
|
||||||
}
|
}
|
||||||
if GetCachedSignature("gpt-4o", text) != sig2 {
|
if GetCachedSignature(geminiModel, text) != sig2 {
|
||||||
t.Error("GPT signature mismatch")
|
t.Error("Gemini signature mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheSignature_NotFound(t *testing.T) {
|
func TestCacheSignature_NotFound(t *testing.T) {
|
||||||
ClearSignatureCache("")
|
ClearSignatureCache("")
|
||||||
|
|
||||||
// Non-existent cache entry
|
// Non-existent session
|
||||||
if got := GetCachedSignature("test-model", "some text"); got != "" {
|
if got := GetCachedSignature(testModelName, "some text"); got != "" {
|
||||||
t.Errorf("Expected empty string for missing entry, got '%s'", got)
|
t.Errorf("Expected empty string for nonexistent session, got '%s'", got)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Existing cache but different text
|
// Existing session but different text
|
||||||
CacheSignature("test-model", "text-a", "sigA12345678901234567890123456789012345678901234567890")
|
CacheSignature(testModelName, "text-a", "sigA12345678901234567890123456789012345678901234567890")
|
||||||
if got := GetCachedSignature("test-model", "text-b"); got != "" {
|
if got := GetCachedSignature(testModelName, "text-b"); got != "" {
|
||||||
t.Errorf("Expected empty string for different text, got '%s'", got)
|
t.Errorf("Expected empty string for different text, got '%s'", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,11 +61,11 @@ func TestCacheSignature_EmptyInputs(t *testing.T) {
|
|||||||
ClearSignatureCache("")
|
ClearSignatureCache("")
|
||||||
|
|
||||||
// All empty/invalid inputs should be no-ops
|
// All empty/invalid inputs should be no-ops
|
||||||
CacheSignature("test-model", "", "sig12345678901234567890123456789012345678901234567890")
|
CacheSignature(testModelName, "", "sig12345678901234567890123456789012345678901234567890")
|
||||||
CacheSignature("test-model", "text", "")
|
CacheSignature(testModelName, "text", "")
|
||||||
CacheSignature("test-model", "text", "short") // Too short
|
CacheSignature(testModelName, "text", "short") // Too short
|
||||||
|
|
||||||
if got := GetCachedSignature("test-model", "text"); got != "" {
|
if got := GetCachedSignature(testModelName, "text"); got != "" {
|
||||||
t.Errorf("Expected empty after invalid cache attempts, got '%s'", got)
|
t.Errorf("Expected empty after invalid cache attempts, got '%s'", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,9 +76,9 @@ func TestCacheSignature_ShortSignatureRejected(t *testing.T) {
|
|||||||
text := "Some text"
|
text := "Some text"
|
||||||
shortSig := "abc123" // Less than 50 chars
|
shortSig := "abc123" // Less than 50 chars
|
||||||
|
|
||||||
CacheSignature("test-model", text, shortSig)
|
CacheSignature(testModelName, text, shortSig)
|
||||||
|
|
||||||
if got := GetCachedSignature("test-model", text); got != "" {
|
if got := GetCachedSignature(testModelName, text); got != "" {
|
||||||
t.Errorf("Short signature should be rejected, got '%s'", got)
|
t.Errorf("Short signature should be rejected, got '%s'", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,18 +86,14 @@ func TestCacheSignature_ShortSignatureRejected(t *testing.T) {
|
|||||||
func TestClearSignatureCache_ModelGroup(t *testing.T) {
|
func TestClearSignatureCache_ModelGroup(t *testing.T) {
|
||||||
ClearSignatureCache("")
|
ClearSignatureCache("")
|
||||||
|
|
||||||
sigClaude := "validSig1234567890123456789012345678901234567890123456"
|
sig := "validSig1234567890123456789012345678901234567890123456"
|
||||||
sigGpt := "validSig9876543210987654321098765432109876543210987654"
|
CacheSignature(testModelName, "text", sig)
|
||||||
CacheSignature("claude-sonnet-4-5-thinking", "text", sigClaude)
|
CacheSignature(testModelName, "text-2", sig)
|
||||||
CacheSignature("gpt-4o", "text", sigGpt)
|
|
||||||
|
|
||||||
ClearSignatureCache("claude-sonnet-4-5-thinking")
|
ClearSignatureCache("session-1")
|
||||||
|
|
||||||
if got := GetCachedSignature("claude-sonnet-4-5-thinking", "text"); got != "" {
|
if got := GetCachedSignature(testModelName, "text"); got != sig {
|
||||||
t.Error("Claude cache should be cleared")
|
t.Error("signature should remain when clearing unknown session")
|
||||||
}
|
|
||||||
if got := GetCachedSignature("gpt-4o", "text"); got != sigGpt {
|
|
||||||
t.Error("GPT cache should still exist")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,35 +101,37 @@ func TestClearSignatureCache_AllSessions(t *testing.T) {
|
|||||||
ClearSignatureCache("")
|
ClearSignatureCache("")
|
||||||
|
|
||||||
sig := "validSig1234567890123456789012345678901234567890123456"
|
sig := "validSig1234567890123456789012345678901234567890123456"
|
||||||
CacheSignature("test-model", "text", sig)
|
CacheSignature(testModelName, "text", sig)
|
||||||
CacheSignature("test-model", "text", sig)
|
CacheSignature(testModelName, "text-2", sig)
|
||||||
|
|
||||||
ClearSignatureCache("")
|
ClearSignatureCache("")
|
||||||
|
|
||||||
if got := GetCachedSignature("test-model", "text"); got != "" {
|
if got := GetCachedSignature(testModelName, "text"); got != "" {
|
||||||
t.Error("cache should be cleared")
|
t.Error("text should be cleared")
|
||||||
}
|
}
|
||||||
if got := GetCachedSignature("test-model", "text"); got != "" {
|
if got := GetCachedSignature(testModelName, "text-2"); got != "" {
|
||||||
t.Error("cache should be cleared")
|
t.Error("text-2 should be cleared")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHasValidSignature(t *testing.T) {
|
func TestHasValidSignature(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
modelName string
|
||||||
signature string
|
signature string
|
||||||
expected bool
|
expected bool
|
||||||
}{
|
}{
|
||||||
{"valid long signature", "abc123validSignature1234567890123456789012345678901234567890", true},
|
{"valid long signature", testModelName, "abc123validSignature1234567890123456789012345678901234567890", true},
|
||||||
{"exactly 50 chars", "12345678901234567890123456789012345678901234567890", true},
|
{"exactly 50 chars", testModelName, "12345678901234567890123456789012345678901234567890", true},
|
||||||
{"49 chars - invalid", "1234567890123456789012345678901234567890123456789", false},
|
{"49 chars - invalid", testModelName, "1234567890123456789012345678901234567890123456789", false},
|
||||||
{"empty string", "", false},
|
{"empty string", testModelName, "", false},
|
||||||
{"short signature", "abc", false},
|
{"short signature", testModelName, "abc", false},
|
||||||
|
{"gemini sentinel", "gemini-3-pro-preview", "skip_thought_signature_validator", true},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
result := HasValidSignature("claude-sonnet-4-5-thinking", tt.signature)
|
result := HasValidSignature(tt.modelName, tt.signature)
|
||||||
if result != tt.expected {
|
if result != tt.expected {
|
||||||
t.Errorf("HasValidSignature(%q) = %v, expected %v", tt.signature, result, tt.expected)
|
t.Errorf("HasValidSignature(%q) = %v, expected %v", tt.signature, result, tt.expected)
|
||||||
}
|
}
|
||||||
@@ -147,13 +148,13 @@ func TestCacheSignature_TextHashCollisionResistance(t *testing.T) {
|
|||||||
sig1 := "signature1_1234567890123456789012345678901234567890123456"
|
sig1 := "signature1_1234567890123456789012345678901234567890123456"
|
||||||
sig2 := "signature2_1234567890123456789012345678901234567890123456"
|
sig2 := "signature2_1234567890123456789012345678901234567890123456"
|
||||||
|
|
||||||
CacheSignature("test-model", text1, sig1)
|
CacheSignature(testModelName, text1, sig1)
|
||||||
CacheSignature("test-model", text2, sig2)
|
CacheSignature(testModelName, text2, sig2)
|
||||||
|
|
||||||
if GetCachedSignature("test-model", text1) != sig1 {
|
if GetCachedSignature(testModelName, text1) != sig1 {
|
||||||
t.Error("text1 signature mismatch")
|
t.Error("text1 signature mismatch")
|
||||||
}
|
}
|
||||||
if GetCachedSignature("test-model", text2) != sig2 {
|
if GetCachedSignature(testModelName, text2) != sig2 {
|
||||||
t.Error("text2 signature mismatch")
|
t.Error("text2 signature mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,9 +165,9 @@ func TestCacheSignature_UnicodeText(t *testing.T) {
|
|||||||
text := "한글 텍스트와 이모지 🎉 그리고 特殊文字"
|
text := "한글 텍스트와 이모지 🎉 그리고 特殊文字"
|
||||||
sig := "unicodeSig123456789012345678901234567890123456789012345"
|
sig := "unicodeSig123456789012345678901234567890123456789012345"
|
||||||
|
|
||||||
CacheSignature("test-model", text, sig)
|
CacheSignature(testModelName, text, sig)
|
||||||
|
|
||||||
if got := GetCachedSignature("test-model", text); got != sig {
|
if got := GetCachedSignature(testModelName, text); got != sig {
|
||||||
t.Errorf("Unicode text signature retrieval failed, got '%s'", got)
|
t.Errorf("Unicode text signature retrieval failed, got '%s'", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,10 +179,10 @@ func TestCacheSignature_Overwrite(t *testing.T) {
|
|||||||
sig1 := "firstSignature12345678901234567890123456789012345678901"
|
sig1 := "firstSignature12345678901234567890123456789012345678901"
|
||||||
sig2 := "secondSignature1234567890123456789012345678901234567890"
|
sig2 := "secondSignature1234567890123456789012345678901234567890"
|
||||||
|
|
||||||
CacheSignature("test-model", text, sig1)
|
CacheSignature(testModelName, text, sig1)
|
||||||
CacheSignature("test-model", text, sig2) // Overwrite
|
CacheSignature(testModelName, text, sig2) // Overwrite
|
||||||
|
|
||||||
if got := GetCachedSignature("test-model", text); got != sig2 {
|
if got := GetCachedSignature(testModelName, text); got != sig2 {
|
||||||
t.Errorf("Expected overwritten signature '%s', got '%s'", sig2, got)
|
t.Errorf("Expected overwritten signature '%s', got '%s'", sig2, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,10 +197,10 @@ func TestCacheSignature_ExpirationLogic(t *testing.T) {
|
|||||||
text := "text"
|
text := "text"
|
||||||
sig := "validSig1234567890123456789012345678901234567890123456"
|
sig := "validSig1234567890123456789012345678901234567890123456"
|
||||||
|
|
||||||
CacheSignature("test-model", text, sig)
|
CacheSignature(testModelName, text, sig)
|
||||||
|
|
||||||
// Fresh entry should be retrievable
|
// Fresh entry should be retrievable
|
||||||
if got := GetCachedSignature("test-model", text); got != sig {
|
if got := GetCachedSignature(testModelName, text); got != sig {
|
||||||
t.Errorf("Fresh entry should be retrievable, got '%s'", got)
|
t.Errorf("Fresh entry should be retrievable, got '%s'", got)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1214,6 +1214,17 @@ func (e *AntigravityExecutor) buildRequest(ctx context.Context, auth *cliproxyau
|
|||||||
// const->enum conversion, and flattening of types/anyOf.
|
// const->enum conversion, and flattening of types/anyOf.
|
||||||
strJSON = util.CleanJSONSchemaForAntigravity(strJSON)
|
strJSON = util.CleanJSONSchemaForAntigravity(strJSON)
|
||||||
|
|
||||||
|
payload = []byte(strJSON)
|
||||||
|
} else {
|
||||||
|
strJSON := string(payload)
|
||||||
|
paths := make([]string, 0)
|
||||||
|
util.Walk(gjson.Parse(strJSON), "", "parametersJsonSchema", &paths)
|
||||||
|
for _, p := range paths {
|
||||||
|
strJSON, _ = util.RenameKey(strJSON, p, p[:len(p)-len("parametersJsonSchema")]+"parameters")
|
||||||
|
}
|
||||||
|
// Clean tool schemas for Gemini to remove unsupported JSON Schema keywords
|
||||||
|
// without adding empty-schema placeholders.
|
||||||
|
strJSON = util.CleanJSONSchemaForGemini(strJSON)
|
||||||
payload = []byte(strJSON)
|
payload = []byte(strJSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1405,7 +1416,13 @@ func geminiToAntigravity(modelName string, payload []byte, projectID string) []b
|
|||||||
template, _ = sjson.Set(template, "request.sessionId", generateStableSessionID(payload))
|
template, _ = sjson.Set(template, "request.sessionId", generateStableSessionID(payload))
|
||||||
|
|
||||||
template, _ = sjson.Delete(template, "request.safetySettings")
|
template, _ = sjson.Delete(template, "request.safetySettings")
|
||||||
// template, _ = sjson.Set(template, "request.toolConfig.functionCallingConfig.mode", "VALIDATED")
|
if toolConfig := gjson.Get(template, "toolConfig"); toolConfig.Exists() && !gjson.Get(template, "request.toolConfig").Exists() {
|
||||||
|
template, _ = sjson.SetRaw(template, "request.toolConfig", toolConfig.Raw)
|
||||||
|
template, _ = sjson.Delete(template, "toolConfig")
|
||||||
|
}
|
||||||
|
if strings.Contains(modelName, "claude") {
|
||||||
|
template, _ = sjson.Set(template, "request.toolConfig.functionCallingConfig.mode", "VALIDATED")
|
||||||
|
}
|
||||||
|
|
||||||
if strings.Contains(modelName, "claude") || strings.Contains(modelName, "gemini-3-pro-high") {
|
if strings.Contains(modelName, "claude") || strings.Contains(modelName, "gemini-3-pro-high") {
|
||||||
gjson.Get(template, "request.tools").ForEach(func(key, tool gjson.Result) bool {
|
gjson.Get(template, "request.tools").ForEach(func(key, tool gjson.Result) bool {
|
||||||
|
|||||||
@@ -12,10 +12,99 @@ import (
|
|||||||
|
|
||||||
var gjsonPathKeyReplacer = strings.NewReplacer(".", "\\.", "*", "\\*", "?", "\\?")
|
var gjsonPathKeyReplacer = strings.NewReplacer(".", "\\.", "*", "\\*", "?", "\\?")
|
||||||
|
|
||||||
|
const placeholderReasonDescription = "Brief explanation of why you are calling this tool"
|
||||||
|
|
||||||
// CleanJSONSchemaForAntigravity transforms a JSON schema to be compatible with 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 CleanJSONSchemaForAntigravity(jsonStr string) string {
|
func CleanJSONSchemaForAntigravity(jsonStr string) string {
|
||||||
|
return cleanJSONSchema(jsonStr, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeKeywords(jsonStr string, keywords []string) string {
|
||||||
|
for _, key := range keywords {
|
||||||
|
for _, p := range findPaths(jsonStr, key) {
|
||||||
|
if isPropertyDefinition(trimSuffix(p, "."+key)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
jsonStr, _ = sjson.Delete(jsonStr, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jsonStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// removePlaceholderFields removes placeholder-only properties ("_" and "reason") and their required entries.
|
||||||
|
func removePlaceholderFields(jsonStr string) string {
|
||||||
|
// Remove "_" placeholder properties.
|
||||||
|
paths := findPaths(jsonStr, "_")
|
||||||
|
sortByDepth(paths)
|
||||||
|
for _, p := range paths {
|
||||||
|
if !strings.HasSuffix(p, ".properties._") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
jsonStr, _ = sjson.Delete(jsonStr, p)
|
||||||
|
parentPath := trimSuffix(p, ".properties._")
|
||||||
|
reqPath := joinPath(parentPath, "required")
|
||||||
|
req := gjson.Get(jsonStr, reqPath)
|
||||||
|
if req.IsArray() {
|
||||||
|
var filtered []string
|
||||||
|
for _, r := range req.Array() {
|
||||||
|
if r.String() != "_" {
|
||||||
|
filtered = append(filtered, r.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(filtered) == 0 {
|
||||||
|
jsonStr, _ = sjson.Delete(jsonStr, reqPath)
|
||||||
|
} else {
|
||||||
|
jsonStr, _ = sjson.Set(jsonStr, reqPath, filtered)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove placeholder-only "reason" objects.
|
||||||
|
reasonPaths := findPaths(jsonStr, "reason")
|
||||||
|
sortByDepth(reasonPaths)
|
||||||
|
for _, p := range reasonPaths {
|
||||||
|
if !strings.HasSuffix(p, ".properties.reason") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parentPath := trimSuffix(p, ".properties.reason")
|
||||||
|
props := gjson.Get(jsonStr, joinPath(parentPath, "properties"))
|
||||||
|
if !props.IsObject() || len(props.Map()) != 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
desc := gjson.Get(jsonStr, p+".description").String()
|
||||||
|
if desc != placeholderReasonDescription {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
jsonStr, _ = sjson.Delete(jsonStr, p)
|
||||||
|
reqPath := joinPath(parentPath, "required")
|
||||||
|
req := gjson.Get(jsonStr, reqPath)
|
||||||
|
if req.IsArray() {
|
||||||
|
var filtered []string
|
||||||
|
for _, r := range req.Array() {
|
||||||
|
if r.String() != "reason" {
|
||||||
|
filtered = append(filtered, r.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(filtered) == 0 {
|
||||||
|
jsonStr, _ = sjson.Delete(jsonStr, reqPath)
|
||||||
|
} else {
|
||||||
|
jsonStr, _ = sjson.Set(jsonStr, reqPath, filtered)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanJSONSchemaForGemini transforms a JSON schema to be compatible with Gemini tool calling.
|
||||||
|
// It removes unsupported keywords and simplifies schemas, without adding empty-schema placeholders.
|
||||||
|
func CleanJSONSchemaForGemini(jsonStr string) string {
|
||||||
|
return cleanJSONSchema(jsonStr, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanJSONSchema(jsonStr string, addPlaceholder bool) 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)
|
||||||
@@ -31,10 +120,16 @@ func CleanJSONSchemaForAntigravity(jsonStr string) string {
|
|||||||
|
|
||||||
// Phase 3: Cleanup
|
// Phase 3: Cleanup
|
||||||
jsonStr = removeUnsupportedKeywords(jsonStr)
|
jsonStr = removeUnsupportedKeywords(jsonStr)
|
||||||
|
if !addPlaceholder {
|
||||||
|
// Gemini schema cleanup: remove nullable/title and placeholder-only fields.
|
||||||
|
jsonStr = removeKeywords(jsonStr, []string{"nullable", "title"})
|
||||||
|
jsonStr = removePlaceholderFields(jsonStr)
|
||||||
|
}
|
||||||
jsonStr = cleanupRequiredFields(jsonStr)
|
jsonStr = cleanupRequiredFields(jsonStr)
|
||||||
|
|
||||||
// Phase 4: Add placeholder for empty object schemas (Claude VALIDATED mode requirement)
|
// Phase 4: Add placeholder for empty object schemas (Claude VALIDATED mode requirement)
|
||||||
jsonStr = addEmptySchemaPlaceholder(jsonStr)
|
if addPlaceholder {
|
||||||
|
jsonStr = addEmptySchemaPlaceholder(jsonStr)
|
||||||
|
}
|
||||||
|
|
||||||
return jsonStr
|
return jsonStr
|
||||||
}
|
}
|
||||||
@@ -409,7 +504,7 @@ func addEmptySchemaPlaceholder(jsonStr string) string {
|
|||||||
// Add placeholder "reason" property
|
// Add placeholder "reason" property
|
||||||
reasonPath := joinPath(propsPath, "reason")
|
reasonPath := joinPath(propsPath, "reason")
|
||||||
jsonStr, _ = sjson.Set(jsonStr, reasonPath+".type", "string")
|
jsonStr, _ = sjson.Set(jsonStr, reasonPath+".type", "string")
|
||||||
jsonStr, _ = sjson.Set(jsonStr, reasonPath+".description", "Brief explanation of why you are calling this tool")
|
jsonStr, _ = sjson.Set(jsonStr, reasonPath+".description", placeholderReasonDescription)
|
||||||
|
|
||||||
// Add to required array
|
// Add to required array
|
||||||
jsonStr, _ = sjson.Set(jsonStr, reqPath, []string{"reason"})
|
jsonStr, _ = sjson.Set(jsonStr, reqPath, []string{"reason"})
|
||||||
|
|||||||
Reference in New Issue
Block a user