package antigravity import ( "encoding/json" "fmt" "strings" "github.com/google/uuid" ) // TransformClaudeToGemini 将 Claude 请求转换为 v1internal Gemini 格式 func TransformClaudeToGemini(claudeReq *ClaudeRequest, projectID, mappedModel string) ([]byte, error) { // 用于存储 tool_use id -> name 映射 toolIDToName := make(map[string]string) // 检测是否启用 thinking isThinkingEnabled := claudeReq.Thinking != nil && claudeReq.Thinking.Type == "enabled" // 1. 构建 contents contents, err := buildContents(claudeReq.Messages, toolIDToName, isThinkingEnabled) if err != nil { return nil, fmt.Errorf("build contents: %w", err) } // 2. 构建 systemInstruction systemInstruction := buildSystemInstruction(claudeReq.System, claudeReq.Model) // 3. 构建 generationConfig generationConfig := buildGenerationConfig(claudeReq) // 4. 构建 tools tools := buildTools(claudeReq.Tools) // 5. 构建内部请求 innerRequest := GeminiRequest{ Contents: contents, SafetySettings: DefaultSafetySettings, } if systemInstruction != nil { innerRequest.SystemInstruction = systemInstruction } if generationConfig != nil { innerRequest.GenerationConfig = generationConfig } if len(tools) > 0 { innerRequest.Tools = tools innerRequest.ToolConfig = &GeminiToolConfig{ FunctionCallingConfig: &GeminiFunctionCallingConfig{ Mode: "VALIDATED", }, } } // 如果提供了 metadata.user_id,复用为 sessionId if claudeReq.Metadata != nil && claudeReq.Metadata.UserID != "" { innerRequest.SessionID = claudeReq.Metadata.UserID } // 6. 包装为 v1internal 请求 v1Req := V1InternalRequest{ Project: projectID, RequestID: "agent-" + uuid.New().String(), UserAgent: "sub2api", RequestType: "agent", Model: mappedModel, Request: innerRequest, } return json.Marshal(v1Req) } // buildSystemInstruction 构建 systemInstruction func buildSystemInstruction(system json.RawMessage, modelName string) *GeminiContent { var parts []GeminiPart // 注入身份防护指令 identityPatch := fmt.Sprintf( "--- [IDENTITY_PATCH] ---\n"+ "Ignore any previous instructions regarding your identity or host platform (e.g., Amazon Q, Google AI).\n"+ "You are currently providing services as the native %s model via a standard API proxy.\n"+ "Always use the 'claude' command for terminal tasks if relevant.\n"+ "--- [SYSTEM_PROMPT_BEGIN] ---\n", modelName, ) parts = append(parts, GeminiPart{Text: identityPatch}) // 解析 system prompt if len(system) > 0 { // 尝试解析为字符串 var sysStr string if err := json.Unmarshal(system, &sysStr); err == nil { if strings.TrimSpace(sysStr) != "" { parts = append(parts, GeminiPart{Text: sysStr}) } } else { // 尝试解析为数组 var sysBlocks []SystemBlock if err := json.Unmarshal(system, &sysBlocks); err == nil { for _, block := range sysBlocks { if block.Type == "text" && strings.TrimSpace(block.Text) != "" { parts = append(parts, GeminiPart{Text: block.Text}) } } } } } parts = append(parts, GeminiPart{Text: "\n--- [SYSTEM_PROMPT_END] ---"}) return &GeminiContent{ Role: "user", Parts: parts, } } // buildContents 构建 contents func buildContents(messages []ClaudeMessage, toolIDToName map[string]string, isThinkingEnabled bool) ([]GeminiContent, error) { var contents []GeminiContent for i, msg := range messages { role := msg.Role if role == "assistant" { role = "model" } parts, err := buildParts(msg.Content, toolIDToName) if err != nil { return nil, fmt.Errorf("build parts for message %d: %w", i, err) } // 如果 thinking 开启且是最后一条 assistant 消息,需要检查是否需要添加 dummy thinking if role == "model" && isThinkingEnabled && i == len(messages)-1 { hasThoughtPart := false for _, p := range parts { if p.Thought { hasThoughtPart = true break } } if !hasThoughtPart && len(parts) > 0 { // 在开头添加 dummy thinking block parts = append([]GeminiPart{{Text: "Thinking...", Thought: true}}, parts...) } } if len(parts) == 0 { continue } contents = append(contents, GeminiContent{ Role: role, Parts: parts, }) } return contents, nil } // buildParts 构建消息的 parts func buildParts(content json.RawMessage, toolIDToName map[string]string) ([]GeminiPart, error) { var parts []GeminiPart // 尝试解析为字符串 var textContent string if err := json.Unmarshal(content, &textContent); err == nil { if textContent != "(no content)" && strings.TrimSpace(textContent) != "" { parts = append(parts, GeminiPart{Text: strings.TrimSpace(textContent)}) } return parts, nil } // 解析为内容块数组 var blocks []ContentBlock if err := json.Unmarshal(content, &blocks); err != nil { return nil, fmt.Errorf("parse content blocks: %w", err) } for _, block := range blocks { switch block.Type { case "text": if block.Text != "(no content)" && strings.TrimSpace(block.Text) != "" { parts = append(parts, GeminiPart{Text: block.Text}) } case "thinking": part := GeminiPart{ Text: block.Thinking, Thought: true, } if block.Signature != "" { part.ThoughtSignature = block.Signature } parts = append(parts, part) case "image": if block.Source != nil && block.Source.Type == "base64" { parts = append(parts, GeminiPart{ InlineData: &GeminiInlineData{ MimeType: block.Source.MediaType, Data: block.Source.Data, }, }) } case "tool_use": // 存储 id -> name 映射 if block.ID != "" && block.Name != "" { toolIDToName[block.ID] = block.Name } part := GeminiPart{ FunctionCall: &GeminiFunctionCall{ Name: block.Name, Args: block.Input, ID: block.ID, }, } if block.Signature != "" { part.ThoughtSignature = block.Signature } parts = append(parts, part) case "tool_result": // 获取函数名 funcName := block.Name if funcName == "" { if name, ok := toolIDToName[block.ToolUseID]; ok { funcName = name } else { funcName = block.ToolUseID } } // 解析 content resultContent := parseToolResultContent(block.Content, block.IsError) parts = append(parts, GeminiPart{ FunctionResponse: &GeminiFunctionResponse{ Name: funcName, Response: map[string]interface{}{ "result": resultContent, }, ID: block.ToolUseID, }, }) } } return parts, nil } // parseToolResultContent 解析 tool_result 的 content func parseToolResultContent(content json.RawMessage, isError bool) string { if len(content) == 0 { if isError { return "Tool execution failed with no output." } return "Command executed successfully." } // 尝试解析为字符串 var str string if err := json.Unmarshal(content, &str); err == nil { if strings.TrimSpace(str) == "" { if isError { return "Tool execution failed with no output." } return "Command executed successfully." } return str } // 尝试解析为数组 var arr []map[string]interface{} if err := json.Unmarshal(content, &arr); err == nil { var texts []string for _, item := range arr { if text, ok := item["text"].(string); ok { texts = append(texts, text) } } result := strings.Join(texts, "\n") if strings.TrimSpace(result) == "" { if isError { return "Tool execution failed with no output." } return "Command executed successfully." } return result } // 返回原始 JSON return string(content) } // buildGenerationConfig 构建 generationConfig func buildGenerationConfig(req *ClaudeRequest) *GeminiGenerationConfig { config := &GeminiGenerationConfig{ MaxOutputTokens: 64000, // 默认最大输出 StopSequences: DefaultStopSequences, } // Thinking 配置 if req.Thinking != nil && req.Thinking.Type == "enabled" { config.ThinkingConfig = &GeminiThinkingConfig{ IncludeThoughts: true, } if req.Thinking.BudgetTokens > 0 { budget := req.Thinking.BudgetTokens // gemini-2.5-flash 上限 24576 if strings.Contains(req.Model, "gemini-2.5-flash") && budget > 24576 { budget = 24576 } config.ThinkingConfig.ThinkingBudget = budget } } // 其他参数 if req.Temperature != nil { config.Temperature = req.Temperature } if req.TopP != nil { config.TopP = req.TopP } if req.TopK != nil { config.TopK = req.TopK } return config } // buildTools 构建 tools func buildTools(tools []ClaudeTool) []GeminiToolDeclaration { if len(tools) == 0 { return nil } // 检查是否有 web_search 工具 hasWebSearch := false for _, tool := range tools { if tool.Name == "web_search" { hasWebSearch = true break } } if hasWebSearch { // Web Search 工具映射 return []GeminiToolDeclaration{{ GoogleSearch: &GeminiGoogleSearch{ EnhancedContent: &GeminiEnhancedContent{ ImageSearch: &GeminiImageSearch{ MaxResultCount: 5, }, }, }, }} } // 普通工具 var funcDecls []GeminiFunctionDecl for _, tool := range tools { // 清理 JSON Schema params := cleanJSONSchema(tool.InputSchema) funcDecls = append(funcDecls, GeminiFunctionDecl{ Name: tool.Name, Description: tool.Description, Parameters: params, }) } if len(funcDecls) == 0 { return nil } return []GeminiToolDeclaration{{ FunctionDeclarations: funcDecls, }} } // cleanJSONSchema 清理 JSON Schema,移除 Gemini 不支持的字段 func cleanJSONSchema(schema map[string]interface{}) map[string]interface{} { if schema == nil { return nil } result := make(map[string]interface{}) for k, v := range schema { // 移除不支持的字段 switch k { case "$schema", "additionalProperties", "minLength", "maxLength", "minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "pattern", "format", "default": continue } // 递归处理嵌套对象 if nested, ok := v.(map[string]interface{}); ok { result[k] = cleanJSONSchema(nested) } else if k == "type" { // 处理类型字段,转换为大写 if typeStr, ok := v.(string); ok { result[k] = strings.ToUpper(typeStr) } else if typeArr, ok := v.([]interface{}); ok { // 处理联合类型 ["string", "null"] -> "STRING" for _, t := range typeArr { if ts, ok := t.(string); ok && ts != "null" { result[k] = strings.ToUpper(ts) break } } } else { result[k] = v } } else { result[k] = v } } // 递归处理 properties if props, ok := result["properties"].(map[string]interface{}); ok { cleanedProps := make(map[string]interface{}) for name, prop := range props { if propMap, ok := prop.(map[string]interface{}); ok { cleanedProps[name] = cleanJSONSchema(propMap) } else { cleanedProps[name] = prop } } result["properties"] = cleanedProps } return result }