UNPKG

route-claudecode

Version:

Advanced routing and transformation system for Claude Code outputs to multiple AI providers

347 lines 13 kB
"use strict"; /** * Gemini Transformer - 完整实现 * 基于项目记忆中的最佳实践,包含工具调用和内容驱动的stop_reason判断 * Project owner: Jason Zhang */ Object.defineProperty(exports, "__esModule", { value: true }); exports.GeminiTransformer = void 0; exports.transformAnthropicToGemini = transformAnthropicToGemini; exports.transformGeminiToAnthropic = transformGeminiToAnthropic; const logger_1 = require("@/utils/logger"); /** * Gemini Transformer - 处理Anthropic与Gemini API格式转换 */ class GeminiTransformer { /** * 转换Anthropic请求为Gemini格式 */ transformAnthropicToGemini(request) { const requestId = request.metadata?.requestId || 'unknown'; try { logger_1.logger.debug('Starting Anthropic to Gemini transformation', { requestId, model: request.model, messageCount: request.messages?.length, hasTools: !!request.tools?.length, hasSystem: !!request.metadata?.system }); // 构建基础请求 const geminiRequest = { contents: this.convertMessages(request.messages, request.metadata?.system), generationConfig: { temperature: request.temperature, maxOutputTokens: request.max_tokens || 131072 } }; // 处理工具 if (request.tools && request.tools.length > 0) { const { tools, toolConfig } = this.buildToolsAndConfig(request.tools, request.metadata?.tool_choice); geminiRequest.tools = tools; geminiRequest.toolConfig = toolConfig; logger_1.logger.debug('Added tools to Gemini request with dynamic toolConfig', { requestId, toolCount: tools.length, functionCount: tools[0]?.functionDeclarations?.length, toolConfig: toolConfig.functionCallingConfig }); } // 添加安全设置 geminiRequest.safetySettings = [ { category: 'HARM_CATEGORY_HARASSMENT', threshold: 'BLOCK_NONE' }, { category: 'HARM_CATEGORY_HATE_SPEECH', threshold: 'BLOCK_NONE' }, { category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT', threshold: 'BLOCK_NONE' }, { category: 'HARM_CATEGORY_DANGEROUS_CONTENT', threshold: 'BLOCK_NONE' } ]; const metadata = { requestId, originalFormat: 'anthropic', transformed: true, toolsEnabled: !!geminiRequest.tools?.length, timestamp: Date.now() }; logger_1.logger.debug('Completed Anthropic to Gemini transformation', { requestId, hasContents: !!geminiRequest.contents?.length, hasTools: !!geminiRequest.tools?.length, hasToolConfig: !!geminiRequest.toolConfig, generationConfig: geminiRequest.generationConfig }); return { geminiRequest, metadata }; } catch (error) { logger_1.logger.error('Error transforming Anthropic to Gemini', { requestId, error: error.message, stack: error.stack }); throw error; } } /** * 转换Gemini响应为Anthropic格式 */ transformGeminiToAnthropic(response, originalModel, requestId) { try { logger_1.logger.debug('Starting Gemini to Anthropic response transformation', { requestId, candidateCount: response.candidates?.length, hasUsage: !!response.usageMetadata }); if (!response.candidates || response.candidates.length === 0) { throw new Error('No candidates in Gemini response'); } const candidate = response.candidates[0]; const content = this.convertResponseContent(candidate.content, requestId); // 🎯 关键修复:内容驱动的stop_reason判断(基于OpenAI成功模式) const stopReason = this.determineStopReason(content, candidate.finishReason); const anthropicResponse = { id: requestId, type: 'message', role: 'assistant', content: content, model: originalModel, stop_reason: stopReason, usage: response.usageMetadata ? { input_tokens: response.usageMetadata.promptTokenCount, output_tokens: response.usageMetadata.candidatesTokenCount } : undefined }; logger_1.logger.debug('Completed Gemini to Anthropic transformation', { requestId, contentBlockCount: content.length, stopReason, hasToolUse: content.some(block => block.type === 'tool_use'), hasUsage: !!anthropicResponse.usage }); return anthropicResponse; } catch (error) { logger_1.logger.error('Error transforming Gemini to Anthropic', { requestId, error: error.message, candidateCount: response.candidates?.length }); throw error; } } /** * 转换消息格式 */ convertMessages(messages, systemMessage) { const contents = []; // 添加系统消息(转换为第一个用户消息) if (systemMessage) { const systemText = Array.isArray(systemMessage) ? systemMessage.map(s => s.text || JSON.stringify(s)).join('\n') : systemMessage; contents.push({ role: 'user', parts: [{ text: `System: ${systemText}` }] }); } // 转换对话消息 for (const message of messages) { const role = message.role === 'assistant' ? 'model' : 'user'; const parts = this.convertMessageContent(message.content); if (parts.length > 0) { contents.push({ role, parts }); } } return contents; } /** * 转换消息内容 */ convertMessageContent(content) { if (typeof content === 'string') { return [{ text: content }]; } if (Array.isArray(content)) { const parts = []; for (const block of content) { if (block.type === 'text') { parts.push({ text: block.text }); } else if (block.type === 'tool_use') { parts.push({ functionCall: { name: block.name, args: block.input || {} } }); } else if (block.type === 'tool_result') { parts.push({ functionResponse: { name: block.tool_use_id, response: { name: block.tool_use_id, content: block.content } } }); } } return parts; } return [{ text: JSON.stringify(content) }]; } /** * 构建工具和配置 */ buildToolsAndConfig(tools, toolChoice) { // 转换工具定义 const functionDeclarations = tools.map(tool => { // 🔧 修复:支持双格式工具(OpenAI和Anthropic) const name = tool.name || tool.function?.name; const description = tool.description || tool.function?.description; const parameters = tool.input_schema || tool.parameters || tool.function?.parameters || {}; if (!name || !description) { throw new Error(`Invalid tool format: missing name or description in ${JSON.stringify(tool)}`); } return { name, description, parameters }; }); const geminiTools = [{ functionDeclarations }]; // 构建工具配置 const allowedFunctionNames = functionDeclarations.map(func => func.name); const toolConfig = this.buildToolConfig(toolChoice, allowedFunctionNames); return { tools: geminiTools, toolConfig }; } /** * 构建工具配置(基于demo3的智能模式选择) */ buildToolConfig(toolChoice, allowedFunctionNames) { if (!toolChoice) { return { functionCallingConfig: { mode: 'AUTO', allowedFunctionNames: allowedFunctionNames } }; } // 处理字符串格式的tool_choice if (typeof toolChoice === 'string') { if (toolChoice === 'auto') { return { functionCallingConfig: { mode: 'AUTO', allowedFunctionNames: allowedFunctionNames } }; } else if (toolChoice === 'none') { return { functionCallingConfig: { mode: 'NONE' } }; } else { // 指定特定工具名 return { functionCallingConfig: { mode: 'ANY', allowedFunctionNames: [toolChoice] } }; } } // 处理对象格式的tool_choice if (typeof toolChoice === 'object') { if (toolChoice.type === 'auto') { return { functionCallingConfig: { mode: 'AUTO', allowedFunctionNames: allowedFunctionNames } }; } else if (toolChoice.type === 'tool' && toolChoice.name) { return { functionCallingConfig: { mode: 'ANY', allowedFunctionNames: [toolChoice.name] } }; } } // 默认使用AUTO模式 return { functionCallingConfig: { mode: 'AUTO', allowedFunctionNames: allowedFunctionNames } }; } /** * 转换响应内容 */ convertResponseContent(content, requestId) { const blocks = []; if (!content || !content.parts) { return [{ type: 'text', text: '' }]; } for (const part of content.parts) { if (part.text) { blocks.push({ type: 'text', text: part.text }); } else if (part.functionCall) { blocks.push({ type: 'tool_use', id: `tool_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, name: part.functionCall.name, input: part.functionCall.args || {} }); } } return blocks.length > 0 ? blocks : [{ type: 'text', text: '' }]; } /** * 🎯 关键方法:内容驱动的stop_reason判断(基于OpenAI成功模式) */ determineStopReason(content, finishReason) { // 优先基于转换后的content判断,而非原始finishReason const hasToolUse = content.some(block => block.type === 'tool_use'); if (hasToolUse) { return 'tool_use'; } // 根据Gemini的finishReason映射 switch (finishReason) { case 'STOP': return 'end_turn'; case 'MAX_TOKENS': return 'max_tokens'; case 'SAFETY': return 'stop_sequence'; default: return 'end_turn'; } } } exports.GeminiTransformer = GeminiTransformer; /** * 便捷函数:转换Anthropic请求为Gemini格式 */ function transformAnthropicToGemini(request) { const transformer = new GeminiTransformer(); return transformer.transformAnthropicToGemini(request); } /** * 便捷函数:转换Gemini响应为Anthropic格式 */ function transformGeminiToAnthropic(response, originalModel, requestId) { const transformer = new GeminiTransformer(); return transformer.transformGeminiToAnthropic(response, originalModel, requestId); } // Types are exported from @/types //# sourceMappingURL=gemini.js.map