diff --git a/internal/translator/antigravity/claude/antigravity_claude_request.go b/internal/translator/antigravity/claude/antigravity_claude_request.go index fdc0f93e..fdfdf469 100644 --- a/internal/translator/antigravity/claude/antigravity_claude_request.go +++ b/internal/translator/antigravity/claude/antigravity_claude_request.go @@ -118,7 +118,8 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _ contentResult := contentResults[j] contentTypeResult := contentResult.Get("type") if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "thinking" { - thinkingText := contentResult.Get("thinking").String() + // Use GetThinkingText to handle wrapped thinking objects + thinkingText := util.GetThinkingText(contentResult) signatureResult := contentResult.Get("signature") signature := "" if signatureResult.Exists() && signatureResult.String() != "" { diff --git a/internal/util/thinking_text.go b/internal/util/thinking_text.go new file mode 100644 index 00000000..c36d202d --- /dev/null +++ b/internal/util/thinking_text.go @@ -0,0 +1,87 @@ +package util + +import ( + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" +) + +// GetThinkingText extracts the thinking text from a content part. +// Handles various formats: +// - Simple string: { "thinking": "text" } or { "text": "text" } +// - Wrapped object: { "thinking": { "text": "text", "cache_control": {...} } } +// - Gemini-style: { "thought": true, "text": "text" } +// Returns the extracted text string. +func GetThinkingText(part gjson.Result) string { + // Try direct text field first (Gemini-style) + if text := part.Get("text"); text.Exists() && text.Type == gjson.String { + return text.String() + } + + // Try thinking field + thinkingField := part.Get("thinking") + if !thinkingField.Exists() { + return "" + } + + // thinking is a string + if thinkingField.Type == gjson.String { + return thinkingField.String() + } + + // thinking is an object with inner text/thinking + if thinkingField.IsObject() { + if inner := thinkingField.Get("text"); inner.Exists() && inner.Type == gjson.String { + return inner.String() + } + if inner := thinkingField.Get("thinking"); inner.Exists() && inner.Type == gjson.String { + return inner.String() + } + } + + return "" +} + +// GetThinkingTextFromJSON extracts thinking text from a raw JSON string. +func GetThinkingTextFromJSON(jsonStr string) string { + return GetThinkingText(gjson.Parse(jsonStr)) +} + +// SanitizeThinkingPart normalizes a thinking part to a canonical form. +// Strips cache_control and other non-essential fields. +// Returns the sanitized part as JSON string. +func SanitizeThinkingPart(part gjson.Result) string { + // Gemini-style: { thought: true, text, thoughtSignature } + if part.Get("thought").Bool() { + result := `{"thought":true}` + if text := GetThinkingText(part); text != "" { + result, _ = sjson.Set(result, "text", text) + } + if sig := part.Get("thoughtSignature"); sig.Exists() && sig.Type == gjson.String { + result, _ = sjson.Set(result, "thoughtSignature", sig.String()) + } + return result + } + + // Anthropic-style: { type: "thinking", thinking, signature } + if part.Get("type").String() == "thinking" || part.Get("thinking").Exists() { + result := `{"type":"thinking"}` + if text := GetThinkingText(part); text != "" { + result, _ = sjson.Set(result, "thinking", text) + } + if sig := part.Get("signature"); sig.Exists() && sig.Type == gjson.String { + result, _ = sjson.Set(result, "signature", sig.String()) + } + return result + } + + // Not a thinking part, return as-is but strip cache_control + return StripCacheControl(part.Raw) +} + +// StripCacheControl removes cache_control and providerOptions from a JSON object. +func StripCacheControl(jsonStr string) string { + result := jsonStr + result, _ = sjson.Delete(result, "cache_control") + result, _ = sjson.Delete(result, "providerOptions") + return result +} diff --git a/litellm b/litellm new file mode 160000 index 00000000..0c48826c --- /dev/null +++ b/litellm @@ -0,0 +1 @@ +Subproject commit 0c48826cdc14a30953f55173c1eecdbfc859952d diff --git a/opencode-antigravity-auth b/opencode-antigravity-auth new file mode 160000 index 00000000..261a91f2 --- /dev/null +++ b/opencode-antigravity-auth @@ -0,0 +1 @@ +Subproject commit 261a91f21bd3bc1660168eb2b82301a6cf372e58 diff --git a/opencode-google-antigravity-auth b/opencode-google-antigravity-auth new file mode 160000 index 00000000..9f9493c7 --- /dev/null +++ b/opencode-google-antigravity-auth @@ -0,0 +1 @@ +Subproject commit 9f9493c730cbf0f17429e107a5bde00794752175