@@ -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