fix(antigravity): always include text field in thought parts to prevent Google 500

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.
This commit is contained in:
sususu
2026-03-20 18:30:15 +08:00
parent f81acd0760
commit e005208d76
@@ -104,59 +104,59 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
// Always try cached signature first (more reliable than client-provided) // Always try cached signature first (more reliable than client-provided)
// Client may send stale or invalid signatures from different sessions // Client may send stale or invalid signatures from different sessions
signature := "" signature := ""
if thinkingText != "" { if thinkingText != "" {
if cachedSig := cache.GetCachedSignature(modelName, thinkingText); cachedSig != "" { if cachedSig := cache.GetCachedSignature(modelName, thinkingText); cachedSig != "" {
signature = cachedSig signature = cachedSig
// log.Debugf("Using cached signature for thinking block") // log.Debugf("Using cached signature for thinking block")
}
} }
}
// Fallback to client signature only if cache miss and client signature is valid // Fallback to client signature only if cache miss and client signature is valid
if signature == "" { if signature == "" {
signatureResult := contentResult.Get("signature") signatureResult := contentResult.Get("signature")
clientSignature := "" clientSignature := ""
if signatureResult.Exists() && signatureResult.String() != "" { if signatureResult.Exists() && signatureResult.String() != "" {
arrayClientSignatures := strings.SplitN(signatureResult.String(), "#", 2) arrayClientSignatures := strings.SplitN(signatureResult.String(), "#", 2)
if len(arrayClientSignatures) == 2 { if len(arrayClientSignatures) == 2 {
if cache.GetModelGroup(modelName) == arrayClientSignatures[0] { if cache.GetModelGroup(modelName) == arrayClientSignatures[0] {
clientSignature = arrayClientSignatures[1] clientSignature = arrayClientSignatures[1]
}
} }
} }
if cache.HasValidSignature(modelName, clientSignature) {
signature = clientSignature
}
// log.Debugf("Using client-provided signature for thinking block")
} }
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 // Store for subsequent tool_use in the same message
if cache.HasValidSignature(modelName, signature) { if cache.HasValidSignature(modelName, signature) {
currentMessageThinkingSignature = signature currentMessageThinkingSignature = signature
} }
// Skip trailing unsigned thinking blocks on last assistant message // Skip trailing unsigned thinking blocks on last assistant message
isUnsigned := !cache.HasValidSignature(modelName, signature) isUnsigned := !cache.HasValidSignature(modelName, signature)
// If unsigned, skip entirely (don't convert to text) // If unsigned, skip entirely (don't convert to text)
// Claude requires assistant messages to start with thinking blocks when thinking is enabled // Claude requires assistant messages to start with thinking blocks when thinking is enabled
// Converting to text would break this requirement // Converting to text would break this requirement
if isUnsigned { if isUnsigned {
// log.Debugf("Dropping unsigned thinking block (no valid signature)") // log.Debugf("Dropping unsigned thinking block (no valid signature)")
enableThoughtTranslate = false enableThoughtTranslate = false
continue continue
} }
// Valid signature, send as thought block // Valid signature, send as thought block
partJSON := []byte(`{}`) // Always include "text" field — Google Antigravity API requires it
partJSON, _ = sjson.SetBytes(partJSON, "thought", true) // even for redacted thinking where the text is empty.
if thinkingText != "" { partJSON := []byte(`{}`)
partJSON, _ = sjson.SetBytes(partJSON, "text", thinkingText) partJSON, _ = sjson.SetBytes(partJSON, "thought", true)
} partJSON, _ = sjson.SetBytes(partJSON, "text", thinkingText)
if signature != "" { if signature != "" {
partJSON, _ = sjson.SetBytes(partJSON, "thoughtSignature", signature) partJSON, _ = sjson.SetBytes(partJSON, "thoughtSignature", signature)
} }
clientContentJSON, _ = sjson.SetRawBytes(clientContentJSON, "parts.-1", partJSON) clientContentJSON, _ = sjson.SetRawBytes(clientContentJSON, "parts.-1", partJSON)
} else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "text" { } else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "text" {
prompt := contentResult.Get("text").String() prompt := contentResult.Get("text").String()
// Skip empty text parts to avoid Gemini API error: // Skip empty text parts to avoid Gemini API error: