diff --git a/backend/internal/pkg/antigravity/response_transformer.go b/backend/internal/pkg/antigravity/response_transformer.go index a605fee2..eb16f09d 100644 --- a/backend/internal/pkg/antigravity/response_transformer.go +++ b/backend/internal/pkg/antigravity/response_transformer.go @@ -20,6 +20,15 @@ func TransformGeminiToClaude(geminiResp []byte, originalModel string) ([]byte, * v1Resp.Response = directResp v1Resp.ResponseID = directResp.ResponseID v1Resp.ModelVersion = directResp.ModelVersion + } else if len(v1Resp.Response.Candidates) == 0 { + // 第一次解析成功但 candidates 为空,说明是直接的 GeminiResponse 格式 + var directResp GeminiResponse + if err2 := json.Unmarshal(geminiResp, &directResp); err2 != nil { + return nil, nil, fmt.Errorf("parse gemini response as direct: %w", err2) + } + v1Resp.Response = directResp + v1Resp.ResponseID = directResp.ResponseID + v1Resp.ModelVersion = directResp.ModelVersion } // 使用处理器转换 @@ -174,16 +183,20 @@ func (p *NonStreamingProcessor) processPart(part *GeminiPart) { p.trailingSignature = "" } - p.textBuilder += part.Text - - // 非空 text 带签名 - 立即刷新并输出空 thinking 块 + // 非空 text 带签名 - 特殊处理:先输出 text,再输出空 thinking 块 if signature != "" { - p.flushText() + p.contentBlocks = append(p.contentBlocks, ClaudeContentItem{ + Type: "text", + Text: part.Text, + }) p.contentBlocks = append(p.contentBlocks, ClaudeContentItem{ Type: "thinking", Thinking: "", Signature: signature, }) + } else { + // 普通 text (无签名) - 累积到 builder + p.textBuilder += part.Text } } } diff --git a/backend/internal/service/antigravity_gateway_service.go b/backend/internal/service/antigravity_gateway_service.go index b951f634..3b847bcb 100644 --- a/backend/internal/service/antigravity_gateway_service.go +++ b/backend/internal/service/antigravity_gateway_service.go @@ -1985,6 +1985,58 @@ func getOrCreateGeminiParts(response map[string]any) (result map[string]any, exi return result, existingParts, setParts } +// mergeCollectedPartsToResponse 将收集的所有 parts 合并到 Gemini 响应中 +// 这个函数会合并所有类型的 parts:text、thinking、functionCall、inlineData 等 +// 保持原始顺序,只合并连续的普通 text parts +func mergeCollectedPartsToResponse(response map[string]any, collectedParts []map[string]any) map[string]any { + if len(collectedParts) == 0 { + return response + } + + result, _, setParts := getOrCreateGeminiParts(response) + + // 合并策略: + // 1. 保持原始顺序 + // 2. 连续的普通 text parts 合并为一个 + // 3. thinking、functionCall、inlineData 等保持原样 + var mergedParts []any + var textBuffer strings.Builder + + flushTextBuffer := func() { + if textBuffer.Len() > 0 { + mergedParts = append(mergedParts, map[string]any{ + "text": textBuffer.String(), + }) + textBuffer.Reset() + } + } + + for _, part := range collectedParts { + // 检查是否是普通 text part + if text, ok := part["text"].(string); ok { + // 检查是否有 thought 标记 + if thought, _ := part["thought"].(bool); thought { + // thinking part,先刷新 text buffer,然后保留原样 + flushTextBuffer() + mergedParts = append(mergedParts, part) + } else { + // 普通 text,累积到 buffer + _, _ = textBuffer.WriteString(text) + } + } else { + // 非 text part(functionCall、inlineData 等),先刷新 text buffer,然后保留原样 + flushTextBuffer() + mergedParts = append(mergedParts, part) + } + } + + // 刷新剩余的 text + flushTextBuffer() + + setParts(mergedParts) + return result +} + // mergeImagePartsToResponse 将收集到的图片 parts 合并到 Gemini 响应中 func mergeImagePartsToResponse(response map[string]any, imageParts []map[string]any) map[string]any { if len(imageParts) == 0 { @@ -2168,6 +2220,7 @@ func (s *AntigravityGatewayService) handleClaudeStreamToNonStreaming(c *gin.Cont var firstTokenMs *int var last map[string]any var lastWithParts map[string]any + var collectedParts []map[string]any // 收集所有 parts(包括 text、thinking、functionCall、inlineData 等) type scanEvent struct { line string @@ -2262,9 +2315,12 @@ func (s *AntigravityGatewayService) handleClaudeStreamToNonStreaming(c *gin.Cont last = parsed - // 保留最后一个有 parts 的响应 + // 保留最后一个有 parts 的响应,并收集所有 parts if parts := extractGeminiParts(parsed); len(parts) > 0 { lastWithParts = parsed + + // 收集所有 parts(text、thinking、functionCall、inlineData 等) + collectedParts = append(collectedParts, parts...) } case <-intervalCh: @@ -2287,6 +2343,11 @@ returnResponse: return nil, s.writeClaudeError(c, http.StatusBadGateway, "upstream_error", "Empty response from upstream") } + // 将收集的所有 parts 合并到最终响应中 + if len(collectedParts) > 0 { + finalResponse = mergeCollectedPartsToResponse(finalResponse, collectedParts) + } + // 序列化为 JSON(Gemini 格式) geminiBody, err := json.Marshal(finalResponse) if err != nil {