UNPKG

route-claudecode

Version:

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

422 lines 15.7 kB
"use strict"; /** * Anthropic Format Transformer * Handles conversion between Anthropic API format and unified format */ Object.defineProperty(exports, "__esModule", { value: true }); exports.AnthropicTransformer = void 0; exports.createAnthropicTransformer = createAnthropicTransformer; class AnthropicTransformer { name = 'anthropic'; /** * Convert Anthropic request to unified format */ transformRequestToUnified(request) { const unified = { messages: this.convertMessagesToUnified(request.messages || []), model: request.model, max_tokens: request.max_tokens || 131072, // 128K tokens default temperature: request.temperature, stream: request.stream || false }; // Handle system messages if (request.system) { if (typeof request.system === 'string') { unified.system = request.system; } else if (Array.isArray(request.system)) { unified.system = request.system .filter((s) => s.type === 'text') .map((s) => s.text) .join('\n'); } } // Handle tools if (request.tools && Array.isArray(request.tools)) { unified.tools = request.tools.map((tool) => ({ type: 'function', function: { name: tool.name, description: tool.description || '', parameters: tool.input_schema } })); } // Handle tool choice if (request.tool_choice) { if (request.tool_choice.type === 'auto') { unified.tool_choice = 'auto'; } else if (request.tool_choice.type === 'tool' && request.tool_choice.name) { unified.tool_choice = request.tool_choice.name; } } return unified; } /** * Convert unified request to Anthropic format */ transformRequestFromUnified(request) { const anthropicRequest = { model: request.model, messages: this.convertMessagesFromUnified(request.messages), max_tokens: request.max_tokens || 131072, // 128K tokens default temperature: request.temperature, stream: request.stream || false }; // Handle system messages if (request.system) { anthropicRequest.system = request.system; } // Handle tools if (request.tools && request.tools.length > 0) { anthropicRequest.tools = request.tools.map(tool => ({ name: tool.function.name, description: tool.function.description, input_schema: tool.function.parameters })); // Handle tool choice if (request.tool_choice) { if (request.tool_choice === 'auto') { anthropicRequest.tool_choice = { type: 'auto' }; } else if (typeof request.tool_choice === 'string') { anthropicRequest.tool_choice = { type: 'tool', name: request.tool_choice }; } } } return anthropicRequest; } /** * Convert Anthropic response to unified format */ transformResponseToUnified(response) { const textContent = response.content?.find((c) => c.type === 'text'); const toolUses = response.content?.filter((c) => c.type === 'tool_use') || []; const toolCalls = toolUses.map((toolUse, index) => ({ id: toolUse.id || `call_${Date.now()}_${index}`, type: 'function', function: { name: toolUse.name, arguments: JSON.stringify(toolUse.input || {}) } })); // 直接映射stop_reason为finish_reason,保持原始性 let finishReason = undefined; if (response.stop_reason) { const stopReasonMapping = { 'end_turn': 'stop', 'max_tokens': 'length', 'tool_use': 'tool_calls', 'stop_sequence': 'stop' }; finishReason = stopReasonMapping[response.stop_reason] || response.stop_reason; } return { id: response.id || `chatcmpl-${Date.now()}`, object: 'chat.completion', created: Math.floor(Date.now() / 1000), model: response.model, choices: [{ index: 0, message: { role: 'assistant', content: textContent?.text || null, tool_calls: toolCalls.length > 0 ? toolCalls : undefined }, // 只有在有原始finish_reason时才设置,否则为undefined finish_reason: finishReason }], usage: { prompt_tokens: response.usage?.input_tokens || 0, completion_tokens: response.usage?.output_tokens || 0, total_tokens: (response.usage?.input_tokens || 0) + (response.usage?.output_tokens || 0) } }; } /** * Convert unified response to Anthropic format */ transformResponseFromUnified(response) { const choice = response.choices[0]; const content = []; // Add text content if (choice.message.content) { content.push({ type: 'text', text: choice.message.content }); } // Add tool uses if (choice.message.tool_calls) { choice.message.tool_calls.forEach(toolCall => { content.push({ type: 'tool_use', id: toolCall.id, name: toolCall.function.name, input: JSON.parse(toolCall.function.arguments || '{}') }); }); } // 直接映射finish_reason为stop_reason,不使用外部处理器 let stopReason = undefined; if (choice.finish_reason) { const finishReasonMapping = { 'stop': 'end_turn', 'length': 'max_tokens', 'tool_calls': 'tool_use', 'function_call': 'tool_use', 'content_filter': 'stop_sequence' }; stopReason = finishReasonMapping[choice.finish_reason] || choice.finish_reason; } const result = { id: response.id, type: 'message', role: 'assistant', content, model: response.model, stop_sequence: null, usage: { input_tokens: response.usage.prompt_tokens, output_tokens: response.usage.completion_tokens } }; // 只有在有映射结果时才设置stop_reason if (stopReason) { result.stop_reason = stopReason; } return result; } /** * Convert Anthropic streaming chunk to unified format */ transformStreamChunk(chunk) { if (chunk.type === 'content_block_delta' && chunk.delta?.text) { return { id: `chatcmpl-${Date.now()}`, object: 'chat.completion.chunk', created: Math.floor(Date.now() / 1000), model: 'unknown', choices: [{ index: 0, delta: { content: chunk.delta.text } }] }; } if (chunk.type === 'message_delta' && chunk.delta?.stop_reason) { // 保留stop_reason,传递给下游处理 return { id: `chatcmpl-${Date.now()}`, object: 'chat.completion.chunk', created: Math.floor(Date.now() / 1000), model: 'unknown', choices: [{ index: 0, delta: {}, finish_reason: chunk.delta.stop_reason // 保留finish_reason传递给下游 }] }; } return null; } /** * Convert messages to unified format */ convertMessagesToUnified(messages) { return messages.map(msg => { const unified = { role: msg.role, content: this.convertContentToUnified(msg.content) }; return unified; }); } /** * Convert messages from unified format */ convertMessagesFromUnified(messages) { const anthropicMessages = []; for (let i = 0; i < messages.length; i++) { const msg = messages[i]; if (msg.role === 'system') { continue; // System messages handled separately } const anthropicMsg = { role: msg.role, content: this.convertContentFromUnified(msg, messages, i) }; anthropicMessages.push(anthropicMsg); } return this.preprocessMessages(anthropicMessages); } /** * Convert content to unified format */ convertContentToUnified(content) { if (typeof content === 'string') { return content; } if (Array.isArray(content)) { return content.map(block => { if (block.type === 'text') { return block.text; } else if (block.type === 'tool_result') { const result = typeof block.content === 'string' ? block.content : JSON.stringify(block.content); return `Tool result (${block.tool_use_id}): ${result}`; } else if (block.type === 'tool_use') { return `Tool call: ${block.name}(${JSON.stringify(block.input)})`; } return JSON.stringify(block); }).join('\n'); } return JSON.stringify(content); } /** * Convert content from unified format */ convertContentFromUnified(msg, allMessages, index) { const content = []; // Handle tool results for user messages if (msg.role === 'user') { // Look for preceding tool messages const toolResults = []; let j = index - 1; while (j >= 0 && allMessages[j].role === 'tool') { toolResults.unshift(allMessages[j]); j--; } // Add tool results first toolResults.forEach(toolMsg => { content.push({ type: 'tool_result', tool_use_id: toolMsg.tool_call_id, content: toolMsg.content || '' }); }); // Add user text content if (msg.content && typeof msg.content === 'string' && msg.content.trim()) { content.push({ type: 'text', text: msg.content }); } return content.length === 1 && content[0].type === 'text' ? content[0].text : content.length === 0 ? '' : content; } // Handle assistant messages with tool calls if (msg.role === 'assistant') { // Add text content if (msg.content && typeof msg.content === 'string' && msg.content.trim()) { content.push({ type: 'text', text: msg.content }); } // Add tool uses if (msg.tool_calls) { msg.tool_calls.forEach(toolCall => { content.push({ type: 'tool_use', id: toolCall.id, name: toolCall.function.name, input: JSON.parse(toolCall.function.arguments || '{}') }); }); } return content.length === 1 && content[0].type === 'text' ? content[0].text : content; } return msg.content || ''; } /** * Preprocess messages to handle tool result placement */ preprocessMessages(messages) { const processed = []; const toolResultMap = new Map(); const toolResultPositions = new Map(); // First pass: collect tool results and their positions messages.forEach((msg, index) => { if (Array.isArray(msg.content)) { msg.content.forEach((block) => { if (block.type === 'tool_result' && block.tool_use_id) { toolResultMap.set(block.tool_use_id, block); toolResultPositions.set(block.tool_use_id, index); } }); } }); // Second pass: process messages and handle tool result placement for (let i = 0; i < messages.length; i++) { const msg = messages[i]; if (msg.role === 'assistant' && Array.isArray(msg.content)) { const toolUses = msg.content.filter((block) => block.type === 'tool_use'); if (toolUses.length > 0) { processed.push(msg); // Check if tool results are properly placed const missingResults = []; toolUses.forEach((toolUse) => { if (!toolResultMap.has(toolUse.id)) { missingResults.push({ type: 'tool_result', tool_use_id: toolUse.id, content: JSON.stringify({ success: true, message: 'Tool call executed successfully' }) }); } }); // Add missing tool results as a user message if (missingResults.length > 0) { processed.push({ role: 'user', content: missingResults }); } } else { processed.push(msg); } } else { // Filter out tool results that are already handled if (Array.isArray(msg.content)) { const filteredContent = msg.content.filter((block) => block.type !== 'tool_result' || !toolResultMap.has(block.tool_use_id)); if (filteredContent.length > 0) { processed.push({ ...msg, content: filteredContent }); } } else { processed.push(msg); } } } return processed; } } exports.AnthropicTransformer = AnthropicTransformer; /** * Utility function to create Anthropic transformer */ function createAnthropicTransformer() { return new AnthropicTransformer(); } //# sourceMappingURL=anthropic.js.map