From e005208d76789cd312b94f7506b1594e29e53506 Mon Sep 17 00:00:00 2001 From: sususu Date: Fri, 20 Mar 2026 18:30:15 +0800 Subject: [PATCH] fix(antigravity): always include text field in thought parts to prevent Google 500 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When Claude sends redacted thinking with empty text, the translator was omitting the "text" field from thought parts. Google Antigravity API requires this field, causing 500 "Unknown Error" responses. Verified: 129/129 error logs with empty thought → 500, 0/97 success logs had empty thought. After fix: 0 new "Unknown Error" 500s. --- .../claude/antigravity_claude_request.go | 88 +++++++++---------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/internal/translator/antigravity/claude/antigravity_claude_request.go b/internal/translator/antigravity/claude/antigravity_claude_request.go index 6b7f8c00dc..598e5d4523 100644 --- a/internal/translator/antigravity/claude/antigravity_claude_request.go +++ b/internal/translator/antigravity/claude/antigravity_claude_request.go @@ -104,59 +104,59 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _ // Always try cached signature first (more reliable than client-provided) // Client may send stale or invalid signatures from different sessions - signature := "" - if thinkingText != "" { - if cachedSig := cache.GetCachedSignature(modelName, thinkingText); cachedSig != "" { - signature = cachedSig - // log.Debugf("Using cached signature for thinking block") - } + signature := "" + if thinkingText != "" { + if cachedSig := cache.GetCachedSignature(modelName, thinkingText); cachedSig != "" { + signature = cachedSig + // log.Debugf("Using cached signature for thinking block") } + } - // Fallback to client signature only if cache miss and client signature is valid - if signature == "" { - signatureResult := contentResult.Get("signature") - clientSignature := "" - if signatureResult.Exists() && signatureResult.String() != "" { - arrayClientSignatures := strings.SplitN(signatureResult.String(), "#", 2) - if len(arrayClientSignatures) == 2 { - if cache.GetModelGroup(modelName) == arrayClientSignatures[0] { - clientSignature = arrayClientSignatures[1] - } + // Fallback to client signature only if cache miss and client signature is valid + if signature == "" { + signatureResult := contentResult.Get("signature") + clientSignature := "" + if signatureResult.Exists() && signatureResult.String() != "" { + arrayClientSignatures := strings.SplitN(signatureResult.String(), "#", 2) + if len(arrayClientSignatures) == 2 { + if cache.GetModelGroup(modelName) == arrayClientSignatures[0] { + clientSignature = arrayClientSignatures[1] } } - if cache.HasValidSignature(modelName, clientSignature) { - signature = clientSignature - } - // log.Debugf("Using client-provided signature for thinking block") } - - // Store for subsequent tool_use in the same message - if cache.HasValidSignature(modelName, signature) { - currentMessageThinkingSignature = signature + if cache.HasValidSignature(modelName, clientSignature) { + signature = clientSignature } + // log.Debugf("Using client-provided signature for thinking block") + } - // Skip trailing unsigned thinking blocks on last assistant message - isUnsigned := !cache.HasValidSignature(modelName, signature) + // Store for subsequent tool_use in the same message + if cache.HasValidSignature(modelName, signature) { + currentMessageThinkingSignature = signature + } - // If unsigned, skip entirely (don't convert to text) - // Claude requires assistant messages to start with thinking blocks when thinking is enabled - // Converting to text would break this requirement - if isUnsigned { - // log.Debugf("Dropping unsigned thinking block (no valid signature)") - enableThoughtTranslate = false - continue - } + // Skip trailing unsigned thinking blocks on last assistant message + isUnsigned := !cache.HasValidSignature(modelName, signature) - // Valid signature, send as thought block - partJSON := []byte(`{}`) - partJSON, _ = sjson.SetBytes(partJSON, "thought", true) - if thinkingText != "" { - partJSON, _ = sjson.SetBytes(partJSON, "text", thinkingText) - } - if signature != "" { - partJSON, _ = sjson.SetBytes(partJSON, "thoughtSignature", signature) - } - clientContentJSON, _ = sjson.SetRawBytes(clientContentJSON, "parts.-1", partJSON) + // If unsigned, skip entirely (don't convert to text) + // Claude requires assistant messages to start with thinking blocks when thinking is enabled + // Converting to text would break this requirement + if isUnsigned { + // log.Debugf("Dropping unsigned thinking block (no valid signature)") + enableThoughtTranslate = false + continue + } + + // Valid signature, send as thought block + // Always include "text" field — Google Antigravity API requires it + // even for redacted thinking where the text is empty. + partJSON := []byte(`{}`) + partJSON, _ = sjson.SetBytes(partJSON, "thought", true) + partJSON, _ = sjson.SetBytes(partJSON, "text", thinkingText) + if signature != "" { + partJSON, _ = sjson.SetBytes(partJSON, "thoughtSignature", signature) + } + clientContentJSON, _ = sjson.SetRawBytes(clientContentJSON, "parts.-1", partJSON) } else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "text" { prompt := contentResult.Get("text").String() // Skip empty text parts to avoid Gemini API error: