fix(antigravity): remove thinking sanitation
This commit is contained in:
@@ -730,9 +730,6 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context,
|
|||||||
proxyURL = account.Proxy.URL()
|
proxyURL = account.Proxy.URL()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanitize thinking blocks (clean cache_control and flatten history thinking)
|
|
||||||
sanitizeThinkingBlocks(&claudeReq)
|
|
||||||
|
|
||||||
// 获取转换选项
|
// 获取转换选项
|
||||||
// Antigravity 上游要求必须包含身份提示词,否则会返回 429
|
// Antigravity 上游要求必须包含身份提示词,否则会返回 429
|
||||||
transformOpts := s.getClaudeTransformOptions(ctx)
|
transformOpts := s.getClaudeTransformOptions(ctx)
|
||||||
@@ -744,9 +741,6 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context,
|
|||||||
return nil, fmt.Errorf("transform request: %w", err)
|
return nil, fmt.Errorf("transform request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safety net: ensure no cache_control leaked into Gemini request
|
|
||||||
geminiBody = cleanCacheControlFromGeminiJSON(geminiBody)
|
|
||||||
|
|
||||||
// Antigravity 上游只支持流式请求,统一使用 streamGenerateContent
|
// Antigravity 上游只支持流式请求,统一使用 streamGenerateContent
|
||||||
// 如果客户端请求非流式,在响应处理阶段会收集完整流式响应后转换返回
|
// 如果客户端请求非流式,在响应处理阶段会收集完整流式响应后转换返回
|
||||||
action := "streamGenerateContent"
|
action := "streamGenerateContent"
|
||||||
@@ -887,143 +881,6 @@ func extractAntigravityErrorMessage(body []byte) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanCacheControlFromGeminiJSON removes cache_control from Gemini JSON (emergency fix)
|
|
||||||
// This should not be needed if transformation is correct, but serves as a safety net
|
|
||||||
func cleanCacheControlFromGeminiJSON(body []byte) []byte {
|
|
||||||
// Try a more robust approach: parse and clean
|
|
||||||
var data map[string]any
|
|
||||||
if err := json.Unmarshal(body, &data); err != nil {
|
|
||||||
log.Printf("[Antigravity] Failed to parse Gemini JSON for cache_control cleaning: %v", err)
|
|
||||||
return body
|
|
||||||
}
|
|
||||||
|
|
||||||
cleaned := removeCacheControlFromAny(data)
|
|
||||||
if !cleaned {
|
|
||||||
return body
|
|
||||||
}
|
|
||||||
|
|
||||||
if result, err := json.Marshal(data); err == nil {
|
|
||||||
log.Printf("[Antigravity] Successfully cleaned cache_control from Gemini JSON")
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
return body
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeCacheControlFromAny recursively removes cache_control fields
|
|
||||||
func removeCacheControlFromAny(v any) bool {
|
|
||||||
cleaned := false
|
|
||||||
|
|
||||||
switch val := v.(type) {
|
|
||||||
case map[string]any:
|
|
||||||
for k, child := range val {
|
|
||||||
if k == "cache_control" {
|
|
||||||
delete(val, k)
|
|
||||||
cleaned = true
|
|
||||||
} else if removeCacheControlFromAny(child) {
|
|
||||||
cleaned = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []any:
|
|
||||||
for _, item := range val {
|
|
||||||
if removeCacheControlFromAny(item) {
|
|
||||||
cleaned = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cleaned
|
|
||||||
}
|
|
||||||
|
|
||||||
// sanitizeThinkingBlocks cleans cache_control and flattens history thinking blocks
|
|
||||||
// Thinking blocks do NOT support cache_control field (Anthropic API/Vertex AI requirement)
|
|
||||||
// Additionally, history thinking blocks are flattened to text to avoid upstream validation errors
|
|
||||||
func sanitizeThinkingBlocks(req *antigravity.ClaudeRequest) {
|
|
||||||
if req == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("[Antigravity] sanitizeThinkingBlocks: processing request with %d messages", len(req.Messages))
|
|
||||||
|
|
||||||
// Clean system blocks
|
|
||||||
if len(req.System) > 0 {
|
|
||||||
var systemBlocks []map[string]any
|
|
||||||
if err := json.Unmarshal(req.System, &systemBlocks); err == nil {
|
|
||||||
for i := range systemBlocks {
|
|
||||||
if blockType, _ := systemBlocks[i]["type"].(string); blockType == "thinking" || systemBlocks[i]["thinking"] != nil {
|
|
||||||
if removeCacheControlFromAny(systemBlocks[i]) {
|
|
||||||
log.Printf("[Antigravity] Deep cleaned cache_control from thinking block in system[%d]", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Marshal back
|
|
||||||
if cleaned, err := json.Marshal(systemBlocks); err == nil {
|
|
||||||
req.System = cleaned
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean message content blocks and flatten history
|
|
||||||
lastMsgIdx := len(req.Messages) - 1
|
|
||||||
for msgIdx := range req.Messages {
|
|
||||||
raw := req.Messages[msgIdx].Content
|
|
||||||
if len(raw) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to parse as blocks array
|
|
||||||
var blocks []map[string]any
|
|
||||||
if err := json.Unmarshal(raw, &blocks); err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
cleaned := false
|
|
||||||
for blockIdx := range blocks {
|
|
||||||
blockType, _ := blocks[blockIdx]["type"].(string)
|
|
||||||
|
|
||||||
// Check for thinking blocks (typed or untyped)
|
|
||||||
if blockType == "thinking" || blocks[blockIdx]["thinking"] != nil {
|
|
||||||
// 1. Clean cache_control
|
|
||||||
if removeCacheControlFromAny(blocks[blockIdx]) {
|
|
||||||
log.Printf("[Antigravity] Deep cleaned cache_control from thinking block in messages[%d].content[%d]", msgIdx, blockIdx)
|
|
||||||
cleaned = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Flatten to text if it's a history message (not the last one)
|
|
||||||
if msgIdx < lastMsgIdx {
|
|
||||||
log.Printf("[Antigravity] Flattening history thinking block to text at messages[%d].content[%d]", msgIdx, blockIdx)
|
|
||||||
|
|
||||||
// Extract thinking content
|
|
||||||
var textContent string
|
|
||||||
if t, ok := blocks[blockIdx]["thinking"].(string); ok {
|
|
||||||
textContent = t
|
|
||||||
} else {
|
|
||||||
// Fallback for non-string content (marshal it)
|
|
||||||
if b, err := json.Marshal(blocks[blockIdx]["thinking"]); err == nil {
|
|
||||||
textContent = string(b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to text block
|
|
||||||
blocks[blockIdx]["type"] = "text"
|
|
||||||
blocks[blockIdx]["text"] = textContent
|
|
||||||
delete(blocks[blockIdx], "thinking")
|
|
||||||
delete(blocks[blockIdx], "signature")
|
|
||||||
delete(blocks[blockIdx], "cache_control") // Ensure it's gone
|
|
||||||
cleaned = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal back if modified
|
|
||||||
if cleaned {
|
|
||||||
if marshaled, err := json.Marshal(blocks); err == nil {
|
|
||||||
req.Messages[msgIdx].Content = marshaled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// stripThinkingFromClaudeRequest converts thinking blocks to text blocks in a Claude Messages request.
|
// stripThinkingFromClaudeRequest converts thinking blocks to text blocks in a Claude Messages request.
|
||||||
// This preserves the thinking content while avoiding signature validation errors.
|
// This preserves the thinking content while avoiding signature validation errors.
|
||||||
// Note: redacted_thinking blocks are removed because they cannot be converted to text.
|
// Note: redacted_thinking blocks are removed because they cannot be converted to text.
|
||||||
|
|||||||
Reference in New Issue
Block a user