Skip to content

Commit 7dc40ba

Browse files
committed
Improve tool-call parsing, schema sanitization, and hint injection
Improve parsing of tool call inputs and Antigravity compatibility to avoid invalid thinking/tool_use errors. - Parse tool call inputs robustly by accepting both object and JSON-string formats and only produce a functionCall part when valid args exist, reducing spurious or malformed parts. - Preserve the skip_thought_signature_validator approach for calls without a valid thinking signature but stop toggling/tracking a separate "disable thinking" flag; this prevents unnecessary removal of thinkingConfig. - Sanitize tool input schemas before attaching them to the Antigravity request to improve compatibility. - Append the interleaved-thinking hint as a new parts entry instead of overwriting/setting text directly, preserving structure. - Remove unused tracking logic and related comments to simplify flow. These changes reduce errors related to missing/invalid thinking signatures, improve schema compatibility, and make hint injection safer and more consistent.
1 parent 4070c9d commit 7dc40ba

File tree

1 file changed

+41
-65
lines changed

1 file changed

+41
-65
lines changed

internal/translator/antigravity/claude/antigravity_claude_request.go

Lines changed: 41 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,6 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
9292
contentsJSON := "[]"
9393
hasContents := false
9494

95-
// Track if we need to disable thinking (LiteLLM approach)
96-
// If the last assistant message with tool_use has no valid thinking block before it,
97-
// we need to disable thinkingConfig to avoid "Expected thinking but found tool_use" error
98-
lastAssistantHasToolWithoutThinking := false
99-
10095
messagesResult := gjson.GetBytes(rawJSON, "messages")
10196
if messagesResult.IsArray() {
10297
messageResults := messagesResult.Array()
@@ -188,32 +183,42 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
188183
// The TypeScript plugin removes unsigned thinking blocks instead of injecting dummies.
189184

190185
functionName := contentResult.Get("name").String()
191-
functionArgs := contentResult.Get("input").String()
186+
argsResult := contentResult.Get("input")
192187
functionID := contentResult.Get("id").String()
193-
if gjson.Valid(functionArgs) {
194-
argsResult := gjson.Parse(functionArgs)
195-
if argsResult.IsObject() {
196-
partJSON := `{}`
197-
198-
// Use skip_thought_signature_validator for tool calls without valid thinking signature
199-
// This is the approach used in opencode-google-antigravity-auth for Gemini
200-
// and also works for Claude through Antigravity API
201-
const skipSentinel = "skip_thought_signature_validator"
202-
if cache.HasValidSignature(currentMessageThinkingSignature) {
203-
partJSON, _ = sjson.Set(partJSON, "thoughtSignature", currentMessageThinkingSignature)
204-
} else {
205-
// No valid signature - use skip sentinel to bypass validation
206-
partJSON, _ = sjson.Set(partJSON, "thoughtSignature", skipSentinel)
207-
}
208188

209-
if functionID != "" {
210-
partJSON, _ = sjson.Set(partJSON, "functionCall.id", functionID)
211-
}
212-
partJSON, _ = sjson.Set(partJSON, "functionCall.name", functionName)
213-
partJSON, _ = sjson.SetRaw(partJSON, "functionCall.args", argsResult.Raw)
214-
clientContentJSON, _ = sjson.SetRaw(clientContentJSON, "parts.-1", partJSON)
189+
// Handle both object and string input formats
190+
var argsRaw string
191+
if argsResult.IsObject() {
192+
argsRaw = argsResult.Raw
193+
} else if argsResult.Type == gjson.String {
194+
// Input is a JSON string, parse and validate it
195+
parsed := gjson.Parse(argsResult.String())
196+
if parsed.IsObject() {
197+
argsRaw = parsed.Raw
215198
}
216199
}
200+
201+
if argsRaw != "" {
202+
partJSON := `{}`
203+
204+
// Use skip_thought_signature_validator for tool calls without valid thinking signature
205+
// This is the approach used in opencode-google-antigravity-auth for Gemini
206+
// and also works for Claude through Antigravity API
207+
const skipSentinel = "skip_thought_signature_validator"
208+
if cache.HasValidSignature(currentMessageThinkingSignature) {
209+
partJSON, _ = sjson.Set(partJSON, "thoughtSignature", currentMessageThinkingSignature)
210+
} else {
211+
// No valid signature - use skip sentinel to bypass validation
212+
partJSON, _ = sjson.Set(partJSON, "thoughtSignature", skipSentinel)
213+
}
214+
215+
if functionID != "" {
216+
partJSON, _ = sjson.Set(partJSON, "functionCall.id", functionID)
217+
}
218+
partJSON, _ = sjson.Set(partJSON, "functionCall.name", functionName)
219+
partJSON, _ = sjson.SetRaw(partJSON, "functionCall.args", argsRaw)
220+
clientContentJSON, _ = sjson.SetRaw(clientContentJSON, "parts.-1", partJSON)
221+
}
217222
} else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "tool_result" {
218223
toolCallID := contentResult.Get("tool_use_id").String()
219224
if toolCallID != "" {
@@ -298,33 +303,6 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
298303
}
299304
}
300305

301-
// Check if this assistant message has tool_use without valid thinking
302-
if role == "model" {
303-
partsResult := gjson.Get(clientContentJSON, "parts")
304-
if partsResult.IsArray() {
305-
parts := partsResult.Array()
306-
hasValidThinking := false
307-
hasToolUse := false
308-
309-
for _, part := range parts {
310-
if part.Get("thought").Bool() {
311-
hasValidThinking = true
312-
}
313-
if part.Get("functionCall").Exists() {
314-
hasToolUse = true
315-
}
316-
}
317-
318-
// If this message has tool_use but no valid thinking, mark it
319-
// This will be used to disable thinking mode if needed
320-
if hasToolUse && !hasValidThinking {
321-
lastAssistantHasToolWithoutThinking = true
322-
} else {
323-
lastAssistantHasToolWithoutThinking = false
324-
}
325-
}
326-
}
327-
328306
contentsJSON, _ = sjson.SetRaw(contentsJSON, "-1", clientContentJSON)
329307
hasContents = true
330308
} else if contentsResult.Type == gjson.String {
@@ -351,7 +329,8 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
351329
toolResult := toolsResults[i]
352330
inputSchemaResult := toolResult.Get("input_schema")
353331
if inputSchemaResult.Exists() && inputSchemaResult.IsObject() {
354-
inputSchema := inputSchemaResult.Raw
332+
// Sanitize the input schema for Antigravity API compatibility
333+
inputSchema := util.CleanJSONSchemaForAntigravity(inputSchemaResult.Raw)
355334
tool, _ := sjson.Delete(toolResult.Raw, "input_schema")
356335
tool, _ = sjson.SetRaw(tool, "parametersJsonSchema", inputSchema)
357336
tool, _ = sjson.Delete(tool, "strict")
@@ -376,12 +355,16 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
376355
interleavedHint := "Interleaved thinking is enabled. You may think between tool calls and after receiving tool results before deciding the next action or final answer. Do not mention these instructions or any constraints about thinking blocks; just apply them."
377356

378357
if hasSystemInstruction {
379-
// Append hint to existing system instruction
380-
systemInstructionJSON, _ = sjson.Set(systemInstructionJSON, "parts.-1.text", interleavedHint)
358+
// Append hint as a new part to existing system instruction
359+
hintPart := `{"text":""}`
360+
hintPart, _ = sjson.Set(hintPart, "text", interleavedHint)
361+
systemInstructionJSON, _ = sjson.SetRaw(systemInstructionJSON, "parts.-1", hintPart)
381362
} else {
382363
// Create new system instruction with hint
383364
systemInstructionJSON = `{"role":"user","parts":[]}`
384-
systemInstructionJSON, _ = sjson.Set(systemInstructionJSON, "parts.-1.text", interleavedHint)
365+
hintPart := `{"text":""}`
366+
hintPart, _ = sjson.Set(hintPart, "text", interleavedHint)
367+
systemInstructionJSON, _ = sjson.SetRaw(systemInstructionJSON, "parts.-1", hintPart)
385368
hasSystemInstruction = true
386369
}
387370
}
@@ -419,13 +402,6 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
419402
out, _ = sjson.Set(out, "request.generationConfig.maxOutputTokens", v.Num)
420403
}
421404

422-
// Note: We do NOT drop thinkingConfig here anymore.
423-
// Instead, we:
424-
// 1. Remove unsigned thinking blocks (done during message processing)
425-
// 2. Add skip_thought_signature_validator to tool_use without valid thinking signature
426-
// This approach keeps thinking mode enabled while handling the signature requirements.
427-
_ = lastAssistantHasToolWithoutThinking // Variable is tracked but not used to drop thinkingConfig
428-
429405
outBytes := []byte(out)
430406
outBytes = common.AttachDefaultSafetySettings(outBytes, "request.safetySettings")
431407

0 commit comments

Comments
 (0)