UNPKG

route-claudecode

Version:

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

235 lines 9.94 kB
"use strict"; /** * Anthropic API Client * Direct integration with Anthropic's official Messages API * Project owner: Jason Zhang */ Object.defineProperty(exports, "__esModule", { value: true }); exports.AnthropicClient = void 0; const logger_1 = require("../../utils/logger"); class AnthropicClient { config; apiKey; baseUrl; constructor(config) { this.config = config; const credentials = config.authentication.credentials; const apiKey = credentials ? (credentials.apiKey || credentials.api_key) : undefined; this.apiKey = Array.isArray(apiKey) ? apiKey[0] : (apiKey || ''); if (!this.apiKey) { throw new Error('Anthropic API key is required'); } this.baseUrl = config.endpoint || 'https://api.anthropic.com'; logger_1.logger.info('Anthropic client initialized', { endpoint: this.baseUrl, hasApiKey: !!this.apiKey }); } async healthCheck() { try { // Simple health check - try to make a minimal request const response = await fetch(`${this.baseUrl}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey, 'anthropic-version': '2023-06-01' }, signal: AbortSignal.timeout(30000), // 30 second timeout for health check body: JSON.stringify({ model: 'claude-3-haiku-20240307', max_tokens: 1, messages: [{ role: 'user', content: 'Hi' }] }) }); // We expect either success or a recognizable error (not network/auth errors) return response.status < 500; } catch (error) { logger_1.logger.error('Anthropic health check failed', { error: error instanceof Error ? error.message : String(error) }); return false; } } async createMessage(request) { const anthropicRequest = this.convertToAnthropicFormat(request); logger_1.logger.trace(request.metadata?.requestId || 'unknown', 'provider', 'Sending request to Anthropic API', { model: anthropicRequest.model, messageCount: anthropicRequest.messages.length, hasTools: !!anthropicRequest.tools, maxTokens: anthropicRequest.max_tokens }); const response = await fetch(`${this.baseUrl}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey, 'anthropic-version': '2023-06-01' }, body: JSON.stringify(anthropicRequest), signal: AbortSignal.timeout(300000) // 5 minute timeout for production }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Anthropic API error (${response.status}): ${errorText}`); } const anthropicResponse = await response.json(); return this.convertFromAnthropicFormat(anthropicResponse, request); } async *streamMessage(request) { const anthropicRequest = { ...this.convertToAnthropicFormat(request), stream: true }; logger_1.logger.debug('Starting streaming request to Anthropic API', { model: anthropicRequest.model, messageCount: anthropicRequest.messages.length }); const response = await fetch(`${this.baseUrl}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey, 'anthropic-version': '2023-06-01' }, body: JSON.stringify(anthropicRequest), signal: AbortSignal.timeout(300000) // 5 minute timeout for production }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Anthropic streaming API error (${response.status}): ${errorText}`); } if (!response.body) { throw new Error('No response body for streaming request'); } const reader = response.body.getReader(); const decoder = new TextDecoder(); try { while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value, { stream: true }); const lines = chunk.split('\n').filter(line => line.trim()); for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6); if (data === '[DONE]') { return; } try { const event = JSON.parse(data); yield this.convertStreamEvent(event); } catch (error) { logger_1.logger.warn('Failed to parse Anthropic stream event', { line, error: error instanceof Error ? error.message : String(error) }); } } } } } finally { reader.releaseLock(); } } convertToAnthropicFormat(request) { const anthropicRequest = { model: request.model, messages: this.filterMessages(request.messages), max_tokens: request.max_tokens || 4096 }; // Add optional parameters if (request.temperature !== undefined) { anthropicRequest.temperature = request.temperature; } // Handle system messages - extract from messages array if present const systemMessages = request.messages.filter((msg) => msg.role === 'system'); if (systemMessages.length > 0) { anthropicRequest.system = systemMessages.map((msg) => msg.content).join('\n'); anthropicRequest.messages = anthropicRequest.messages.filter((msg) => msg.role !== 'system'); } // Handle tools if present - but only for compatible APIs const anthropicRequest_typed = request; if (anthropicRequest_typed.tools && this.supportsTools()) { anthropicRequest.tools = anthropicRequest_typed.tools; } return anthropicRequest; } filterMessages(messages) { // Filter out tool_result messages for APIs that don't support them if (!this.supportsToolResults()) { return messages.map((msg) => { if (msg.role === 'user' && Array.isArray(msg.content)) { // Filter out tool_result content blocks const filteredContent = msg.content.filter((block) => block.type !== 'tool_result'); if (filteredContent.length === 0) { // If all content was tool_result, replace with summarized text const toolResults = msg.content.filter((block) => block.type === 'tool_result'); const summary = this.summarizeToolResults(toolResults); return { ...msg, content: summary }; } return { ...msg, content: filteredContent }; } return msg; }).filter((msg) => msg !== null); } return messages; } supportsTools() { // Check if this API endpoint supports tools return !this.baseUrl.includes('modelscope.cn'); } supportsToolResults() { // Check if this API endpoint supports tool_result messages return !this.baseUrl.includes('modelscope.cn'); } summarizeToolResults(toolResults) { if (toolResults.length === 0) { return "Previous tool execution completed."; } // Create a summary of tool results that maintains context const summaries = toolResults.map((result) => { const toolName = result.tool_use_id || 'tool'; const content = result.content || ''; // Truncate very long content but preserve key information if (typeof content === 'string' && content.length > 200) { return `Tool ${toolName} executed successfully. Result: ${content.substring(0, 200)}...`; } return `Tool ${toolName} executed successfully. Result: ${content}`; }); return summaries.join('\n'); } convertFromAnthropicFormat(anthropicResponse, originalRequest) { return { id: anthropicResponse.id, type: 'message', model: anthropicResponse.model, role: 'assistant', content: anthropicResponse.content || [], stop_reason: anthropicResponse.stop_reason, stop_sequence: anthropicResponse.stop_sequence || null, usage: { input_tokens: anthropicResponse.usage?.input_tokens || 0, output_tokens: anthropicResponse.usage?.output_tokens || 0 } }; } convertStreamEvent(event) { // Convert Anthropic streaming events to our standard format // This maintains compatibility with existing stream processing return { type: event.type, index: event.index || 0, delta: event.delta, content_block: event.content_block, message: event.message, usage: event.usage }; } } exports.AnthropicClient = AnthropicClient; //# sourceMappingURL=client.js.map