UNPKG

route-claudecode

Version:

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

409 lines 16.7 kB
"use strict"; /** * 统一OpenAI非流式处理基类 (重构版) * 项目所有者: Jason Zhang * * Provider统一使用非流式调用API,然后根据需求模拟流式响应 * 遵循零硬编码、零Fallback、零静默失败原则 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.StreamingSimulator = exports.OpenAIAPIHandler = void 0; exports.createAPIHandler = createAPIHandler; exports.createStreamingHandler = createStreamingHandler; const logger_1 = require("@/utils/logger"); const response_validation_1 = require("./response-validation"); const openai_1 = require("@/transformers/openai"); /** * 统一的OpenAI API处理类 - 只做非流式调用 * 所有Provider都通过此类进行统一的非流式API调用 */ class OpenAIAPIHandler { config; transformer; constructor(config) { this.config = config; this.transformer = config.transformer || (0, openai_1.createOpenAITransformer)(); } /** * 统一的非流式API调用 - 所有工具转换在transformer中处理 */ async callAPI(request) { const requestId = request.metadata?.requestId || 'unknown'; try { // 🔄 使用transformer转换请求(统一逻辑) const openaiRequest = this.transformer.transformBaseRequestToOpenAI(request); logger_1.logger.debug('Sending non-streaming request to OpenAI API', { model: openaiRequest.model, hasTools: !!(openaiRequest.tools && openaiRequest.tools.length > 0), messageCount: openaiRequest.messages.length, requestId, provider: this.config.providerName }, requestId, 'api-handler'); // 🎯 纯粹的非流式OpenAI API调用 const rawResponse = await this.config.openaiClient.chat.completions.create(openaiRequest); // 🔍 [CRITICAL-DEBUG] 检查OpenAI SDK返回的数据类型和结构 console.log('🔍 [CRITICAL-DEBUG] OpenAI SDK raw response analysis:', { requestId, rawResponseIsObject: typeof rawResponse === 'object', rawResponseConstructor: rawResponse?.constructor?.name, rawResponseProto: Object.getPrototypeOf(rawResponse)?.constructor?.name, hasOwnChoices: Object.hasOwnProperty.call(rawResponse || {}, 'choices'), choicesDescriptor: Object.getOwnPropertyDescriptor(rawResponse || {}, 'choices'), rawResponseString: JSON.stringify(rawResponse), directChoicesAccess: rawResponse?.choices }); // 🔍 [SDK-DEBUG] 记录OpenAI SDK原始响应 console.log('🔍 [SDK-DEBUG] Raw OpenAI SDK response:', { requestId, hasRawResponse: !!rawResponse, rawResponseType: typeof rawResponse, rawResponseKeys: rawResponse ? Object.keys(rawResponse) : null, hasChoices: !!rawResponse?.choices, choicesType: typeof rawResponse?.choices, choicesLength: rawResponse?.choices?.length || 0, rawResponseId: rawResponse?.id, rawResponseObject: rawResponse?.object }); if (!rawResponse?.choices) { console.log('🚨 [SDK-DEBUG] RAW RESPONSE MISSING CHOICES!', { requestId, fullRawResponse: JSON.stringify(rawResponse, null, 2) }); } // 🔧 CRITICAL FIX: 在transformer之前应用格式兼容性修复 const response = await this.applyResponseFormatFix(rawResponse, request); // 🔍 [FORMAT-FIX-DEBUG] 记录格式修复后的响应 console.log('🔍 [FORMAT-FIX-DEBUG] Response after format fix:', { requestId, hasResponse: !!response, responseType: typeof response, responseKeys: response ? Object.keys(response) : null, hasChoices: !!response?.choices, choicesLength: response?.choices?.length || 0, wasFixed: rawResponse !== response }); if (!response?.choices) { console.log('🚨 [FORMAT-FIX-DEBUG] RESPONSE MISSING CHOICES AFTER FORMAT FIX!', { requestId, fullResponse: JSON.stringify(response, null, 2) }); } // 🔄 使用transformer转换响应(统一逻辑,包含所有工具转换) const baseResponse = this.transformer.transformOpenAIResponseToBase(response, request); // 🚨 统一响应验证,防止静默失败 (0, response_validation_1.validateNonStreamingResponse)(baseResponse, requestId, this.config.providerName); logger_1.logger.debug('Non-streaming API call completed successfully', { stopReason: baseResponse.stop_reason, hasTools: baseResponse.content.some((c) => c.type === 'tool_use'), contentBlocks: baseResponse.content.length, requestId, provider: this.config.providerName }, requestId, 'api-handler'); return baseResponse; } catch (error) { // 检查是否是超时错误 const isTimeoutError = this.isTimeoutError(error); const errorMessage = error instanceof Error ? error.message : String(error); logger_1.logger.error('OpenAI API call failed', { error: errorMessage, isTimeout: isTimeoutError, provider: this.config.providerName, model: request.model, requestId }, requestId, 'api-handler'); // 如果是超时错误,抛出明确的超时错误而不是静默失败 if (isTimeoutError) { const timeoutError = new Error(`API_TIMEOUT: ${this.config.providerName} API request timed out`); timeoutError.type = 'api_timeout'; timeoutError.provider = this.config.providerName; timeoutError.originalError = error; throw timeoutError; } throw error; } } /** * 检查是否是超时错误 */ isTimeoutError(error) { if (!error) return false; // 检查错误消息 const errorMessage = error.message || error.toString().toLowerCase(); const timeoutKeywords = [ 'timeout', 'timed out', 'request timed out', 'connection timeout', 'etimedout', 'esockettimedout' ]; const hasTimeoutMessage = timeoutKeywords.some(keyword => errorMessage.toLowerCase().includes(keyword)); // 检查错误类型或代码 const isTimeoutType = error.code === 'ETIMEDOUT' || error.code === 'ESOCKETTIMEDOUT' || error.name === 'TimeoutError' || error.name === 'APIConnectionTimeoutError' || error.type === 'timeout'; return hasTimeoutMessage || isTimeoutType; } /** * 获取provider名称 */ get providerName() { return this.config.providerName; } /** * 🔧 CRITICAL FIX: 应用响应格式兼容性修复 * 解决ModelScope/ShuaiHong等非标准API的格式问题 */ async applyResponseFormatFix(response, originalRequest) { // 如果响应格式正常,直接返回 if (response && response.choices && Array.isArray(response.choices) && response.choices.length > 0) { return response; } // 获取模型和Provider信息用于匹配 const modelName = originalRequest.metadata?.originalModel || originalRequest.model || 'unknown'; const providerId = this.config.providerName; console.log(`🔧 [FORMAT-FIX] Checking response format for ${modelName} on ${providerId}`); // 基于模型匹配的目标列表 const targetModels = [ 'gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-pro', 'gemini-flash', 'glm-4.5', 'glm-4-plus', 'glm-4', 'DeepSeek-V3', 'deepseek-v3', 'claude-4-sonnet', 'claude-3-sonnet', 'ZhipuAI/GLM-4.5', 'Qwen/Qwen3-Coder-480B-A35B-Instruct' ]; // 检查是否需要修复 const needsFix = targetModels.some(model => modelName.toLowerCase().includes(model.toLowerCase()) || model.toLowerCase().includes(modelName.toLowerCase())) || providerId.includes('modelscope') || providerId.includes('shuaihong'); if (!needsFix) { console.log(`⏭️ [FORMAT-FIX] Skipping fix for ${modelName} on ${providerId}`); return response; } console.log(`🔧 [FORMAT-FIX] Applying format fix for ${modelName} on ${providerId}`); // 构造标准OpenAI格式响应 const fixedResponse = { id: response?.id || `msg_${Date.now()}_fix`, object: 'chat.completion', created: response?.created || Math.floor(Date.now() / 1000), model: modelName, choices: [{ index: 0, message: { role: 'assistant', content: this.extractContent(response) || '', tool_calls: this.extractToolCalls(response) || null }, finish_reason: this.extractFinishReason(response) || 'stop' }], usage: response?.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 } }; // 如果有工具调用但没有内容,设置content为null if (fixedResponse.choices[0].message.tool_calls && !fixedResponse.choices[0].message.content) { fixedResponse.choices[0].message.content = null; } console.log(`✅ [FORMAT-FIX] Successfully fixed response format for ${modelName}`); return fixedResponse; } /** * 从非标准响应中提取内容 */ extractContent(data) { if (!data) return null; // 尝试多种可能的内容字段 if (data.content) return data.content; if (data.message && typeof data.message === 'string') return data.message; if (data.text) return data.text; if (data.response) return data.response; if (data.output) return data.output; // 尝试从嵌套对象中提取 if (data.result && data.result.content) return data.result.content; if (data.data && data.data.content) return data.data.content; return null; } /** * 从非标准响应中提取工具调用 */ extractToolCalls(data) { if (!data) return null; // 检查标准位置 if (data.tool_calls && Array.isArray(data.tool_calls)) { return data.tool_calls; } // 检查嵌套位置 if (data.message && data.message.tool_calls) { return data.message.tool_calls; } // 检查其他可能的位置 if (data.function_calls) { return data.function_calls; } return null; } /** * 从非标准响应中提取finish_reason */ extractFinishReason(data) { if (!data) return 'stop'; // 尝试多种可能的finish_reason字段 if (data.finish_reason) return data.finish_reason; if (data.stop_reason) return data.stop_reason; if (data.finishReason) return data.finishReason; if (data.status) return data.status; // 检查嵌套位置 if (data.result && data.result.finish_reason) return data.result.finish_reason; if (data.choices && data.choices[0] && data.choices[0].finish_reason) { return data.choices[0].finish_reason; } // 如果有工具调用相关内容,返回tool_calls if (this.extractToolCalls(data)) { return 'tool_calls'; } // 默认为stop return 'stop'; } } exports.OpenAIAPIHandler = OpenAIAPIHandler; /** * 流式响应模拟器 - 将非流式响应转换为流式格式 */ class StreamingSimulator { /** * 将BaseResponse转换为Anthropic流式事件序列 */ static *simulateStreamingResponse(response, requestId) { const messageId = response.id || `msg_${Date.now()}`; // 发送message_start事件 yield { event: 'message_start', data: { type: 'message_start', message: { id: messageId, type: 'message', role: 'assistant', content: [], model: response.model, stop_reason: null, stop_sequence: null, usage: response.usage } } }; // 发送内容块 for (let i = 0; i < response.content.length; i++) { const block = response.content[i]; // content_block_start yield { event: 'content_block_start', data: { type: 'content_block_start', index: i, content_block: block } }; // 如果是文本块,模拟文本增量 if (block.type === 'text' && block.text) { // 简单的文本分块模拟 const chunks = block.text.match(/.{1,10}/g) || [block.text]; for (const textChunk of chunks) { yield { event: 'content_block_delta', data: { type: 'content_block_delta', index: i, delta: { type: 'text_delta', text: textChunk } } }; } } // 如果是工具调用块,模拟参数增量 if (block.type === 'tool_use' && block.input) { const inputJson = JSON.stringify(block.input); // 简单的JSON分块模拟 const chunks = inputJson.match(/.{1,20}/g) || [inputJson]; for (const jsonChunk of chunks) { yield { event: 'content_block_delta', data: { type: 'content_block_delta', index: i, delta: { type: 'input_json_delta', partial_json: jsonChunk } } }; } } // content_block_stop yield { event: 'content_block_stop', data: { type: 'content_block_stop', index: i } }; } // 发送message_delta yield { event: 'message_delta', data: { type: 'message_delta', delta: { stop_reason: response.stop_reason, stop_sequence: response.stop_sequence }, usage: { output_tokens: response.usage?.output_tokens || 0 } } }; // 发送message_stop(如果不是工具调用) if (response.stop_reason !== 'tool_use') { yield { event: 'message_stop', data: { type: 'message_stop' } }; } } } exports.StreamingSimulator = StreamingSimulator; /** * 创建API处理器实例 */ function createAPIHandler(config) { return new OpenAIAPIHandler(config); } // 为了向后兼容,保留原来的函数名 function createStreamingHandler(config) { logger_1.logger.warn('createStreamingHandler is deprecated, use createAPIHandler instead'); return new OpenAIAPIHandler(config); } //# sourceMappingURL=openai-streaming-handler.js.map