270 lines
6.5 KiB
Go
270 lines
6.5 KiB
Go
package antigravity
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
)
|
|
|
|
// TransformGeminiToClaude 将 Gemini 响应转换为 Claude 格式(非流式)
|
|
func TransformGeminiToClaude(geminiResp []byte, originalModel string) ([]byte, *ClaudeUsage, error) {
|
|
// 解包 v1internal 响应
|
|
var v1Resp V1InternalResponse
|
|
if err := json.Unmarshal(geminiResp, &v1Resp); err != nil {
|
|
// 尝试直接解析为 GeminiResponse
|
|
var directResp GeminiResponse
|
|
if err2 := json.Unmarshal(geminiResp, &directResp); err2 != nil {
|
|
return nil, nil, fmt.Errorf("parse gemini response: %w", err)
|
|
}
|
|
v1Resp.Response = directResp
|
|
v1Resp.ResponseID = directResp.ResponseID
|
|
v1Resp.ModelVersion = directResp.ModelVersion
|
|
}
|
|
|
|
// 使用处理器转换
|
|
processor := NewNonStreamingProcessor()
|
|
claudeResp := processor.Process(&v1Resp.Response, v1Resp.ResponseID, originalModel)
|
|
|
|
// 序列化
|
|
respBytes, err := json.Marshal(claudeResp)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("marshal claude response: %w", err)
|
|
}
|
|
|
|
return respBytes, &claudeResp.Usage, nil
|
|
}
|
|
|
|
// NonStreamingProcessor 非流式响应处理器
|
|
type NonStreamingProcessor struct {
|
|
contentBlocks []ClaudeContentItem
|
|
textBuilder string
|
|
thinkingBuilder string
|
|
thinkingSignature string
|
|
trailingSignature string
|
|
hasToolCall bool
|
|
}
|
|
|
|
// NewNonStreamingProcessor 创建非流式响应处理器
|
|
func NewNonStreamingProcessor() *NonStreamingProcessor {
|
|
return &NonStreamingProcessor{
|
|
contentBlocks: make([]ClaudeContentItem, 0),
|
|
}
|
|
}
|
|
|
|
// Process 处理 Gemini 响应
|
|
func (p *NonStreamingProcessor) Process(geminiResp *GeminiResponse, responseID, originalModel string) *ClaudeResponse {
|
|
// 获取 parts
|
|
var parts []GeminiPart
|
|
if len(geminiResp.Candidates) > 0 && geminiResp.Candidates[0].Content != nil {
|
|
parts = geminiResp.Candidates[0].Content.Parts
|
|
}
|
|
|
|
// 处理所有 parts
|
|
for _, part := range parts {
|
|
p.processPart(&part)
|
|
}
|
|
|
|
// 刷新剩余内容
|
|
p.flushThinking()
|
|
p.flushText()
|
|
|
|
// 处理 trailingSignature
|
|
if p.trailingSignature != "" {
|
|
p.contentBlocks = append(p.contentBlocks, ClaudeContentItem{
|
|
Type: "thinking",
|
|
Thinking: "",
|
|
Signature: p.trailingSignature,
|
|
})
|
|
}
|
|
|
|
// 构建响应
|
|
return p.buildResponse(geminiResp, responseID, originalModel)
|
|
}
|
|
|
|
// processPart 处理单个 part
|
|
func (p *NonStreamingProcessor) processPart(part *GeminiPart) {
|
|
signature := part.ThoughtSignature
|
|
|
|
// 1. FunctionCall 处理
|
|
if part.FunctionCall != nil {
|
|
p.flushThinking()
|
|
p.flushText()
|
|
|
|
// 处理 trailingSignature
|
|
if p.trailingSignature != "" {
|
|
p.contentBlocks = append(p.contentBlocks, ClaudeContentItem{
|
|
Type: "thinking",
|
|
Thinking: "",
|
|
Signature: p.trailingSignature,
|
|
})
|
|
p.trailingSignature = ""
|
|
}
|
|
|
|
p.hasToolCall = true
|
|
|
|
// 生成 tool_use id
|
|
toolID := part.FunctionCall.ID
|
|
if toolID == "" {
|
|
toolID = fmt.Sprintf("%s-%s", part.FunctionCall.Name, generateRandomID())
|
|
}
|
|
|
|
item := ClaudeContentItem{
|
|
Type: "tool_use",
|
|
ID: toolID,
|
|
Name: part.FunctionCall.Name,
|
|
Input: part.FunctionCall.Args,
|
|
}
|
|
|
|
if signature != "" {
|
|
item.Signature = signature
|
|
}
|
|
|
|
p.contentBlocks = append(p.contentBlocks, item)
|
|
return
|
|
}
|
|
|
|
// 2. Text 处理
|
|
if part.Text != "" || part.Thought {
|
|
if part.Thought {
|
|
// Thinking part
|
|
p.flushText()
|
|
|
|
// 处理 trailingSignature
|
|
if p.trailingSignature != "" {
|
|
p.flushThinking()
|
|
p.contentBlocks = append(p.contentBlocks, ClaudeContentItem{
|
|
Type: "thinking",
|
|
Thinking: "",
|
|
Signature: p.trailingSignature,
|
|
})
|
|
p.trailingSignature = ""
|
|
}
|
|
|
|
p.thinkingBuilder += part.Text
|
|
if signature != "" {
|
|
p.thinkingSignature = signature
|
|
}
|
|
} else {
|
|
// 普通 Text
|
|
if part.Text == "" {
|
|
// 空 text 带签名 - 暂存
|
|
if signature != "" {
|
|
p.trailingSignature = signature
|
|
}
|
|
return
|
|
}
|
|
|
|
p.flushThinking()
|
|
|
|
// 处理之前的 trailingSignature
|
|
if p.trailingSignature != "" {
|
|
p.flushText()
|
|
p.contentBlocks = append(p.contentBlocks, ClaudeContentItem{
|
|
Type: "thinking",
|
|
Thinking: "",
|
|
Signature: p.trailingSignature,
|
|
})
|
|
p.trailingSignature = ""
|
|
}
|
|
|
|
p.textBuilder += part.Text
|
|
|
|
// 非空 text 带签名 - 立即刷新并输出空 thinking 块
|
|
if signature != "" {
|
|
p.flushText()
|
|
p.contentBlocks = append(p.contentBlocks, ClaudeContentItem{
|
|
Type: "thinking",
|
|
Thinking: "",
|
|
Signature: signature,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3. InlineData (Image) 处理
|
|
if part.InlineData != nil && part.InlineData.Data != "" {
|
|
p.flushThinking()
|
|
markdownImg := fmt.Sprintf("",
|
|
part.InlineData.MimeType, part.InlineData.Data)
|
|
p.textBuilder += markdownImg
|
|
p.flushText()
|
|
}
|
|
}
|
|
|
|
// flushText 刷新 text builder
|
|
func (p *NonStreamingProcessor) flushText() {
|
|
if p.textBuilder == "" {
|
|
return
|
|
}
|
|
|
|
p.contentBlocks = append(p.contentBlocks, ClaudeContentItem{
|
|
Type: "text",
|
|
Text: p.textBuilder,
|
|
})
|
|
p.textBuilder = ""
|
|
}
|
|
|
|
// flushThinking 刷新 thinking builder
|
|
func (p *NonStreamingProcessor) flushThinking() {
|
|
if p.thinkingBuilder == "" && p.thinkingSignature == "" {
|
|
return
|
|
}
|
|
|
|
p.contentBlocks = append(p.contentBlocks, ClaudeContentItem{
|
|
Type: "thinking",
|
|
Thinking: p.thinkingBuilder,
|
|
Signature: p.thinkingSignature,
|
|
})
|
|
p.thinkingBuilder = ""
|
|
p.thinkingSignature = ""
|
|
}
|
|
|
|
// buildResponse 构建最终响应
|
|
func (p *NonStreamingProcessor) buildResponse(geminiResp *GeminiResponse, responseID, originalModel string) *ClaudeResponse {
|
|
var finishReason string
|
|
if len(geminiResp.Candidates) > 0 {
|
|
finishReason = geminiResp.Candidates[0].FinishReason
|
|
}
|
|
|
|
stopReason := "end_turn"
|
|
if p.hasToolCall {
|
|
stopReason = "tool_use"
|
|
} else if finishReason == "MAX_TOKENS" {
|
|
stopReason = "max_tokens"
|
|
}
|
|
|
|
usage := ClaudeUsage{}
|
|
if geminiResp.UsageMetadata != nil {
|
|
usage.InputTokens = geminiResp.UsageMetadata.PromptTokenCount
|
|
usage.OutputTokens = geminiResp.UsageMetadata.CandidatesTokenCount
|
|
}
|
|
|
|
// 生成响应 ID
|
|
respID := responseID
|
|
if respID == "" {
|
|
respID = geminiResp.ResponseID
|
|
}
|
|
if respID == "" {
|
|
respID = "msg_" + generateRandomID()
|
|
}
|
|
|
|
return &ClaudeResponse{
|
|
ID: respID,
|
|
Type: "message",
|
|
Role: "assistant",
|
|
Model: originalModel,
|
|
Content: p.contentBlocks,
|
|
StopReason: stopReason,
|
|
Usage: usage,
|
|
}
|
|
}
|
|
|
|
// generateRandomID 生成随机 ID
|
|
func generateRandomID() string {
|
|
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
result := make([]byte, 12)
|
|
for i := range result {
|
|
result[i] = chars[i%len(chars)]
|
|
}
|
|
return string(result)
|
|
}
|