UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with external MCP server integration, multi-provider support, and professional CLI. Connect to 65+ MCP servers for filesystem, GitHub, database operations, and more. Build, test, and deploy AI applications with 9 major pr

309 lines (308 loc) 12.3 kB
/** * Anthropic AI Provider (Direct API) * * Direct integration with Anthropic's Claude models via their native API. * Supports Claude 3.5 Sonnet, Claude 3.5 Haiku, and Claude 3 Opus. */ import { AIProviderName } from '../core/types.js'; import { logger } from '../utils/logger.js'; export class AnthropicProvider { name = AIProviderName.ANTHROPIC; apiKey; baseURL; defaultModel; constructor() { this.apiKey = this.getApiKey(); this.baseURL = process.env.ANTHROPIC_BASE_URL || 'https://api.anthropic.com'; this.defaultModel = process.env.ANTHROPIC_MODEL || 'claude-3-5-sonnet-20241022'; logger.debug(`[AnthropicProvider] Initialized with model: ${this.defaultModel}`); } getApiKey() { const apiKey = process.env.ANTHROPIC_API_KEY; if (!apiKey) { throw new Error('ANTHROPIC_API_KEY environment variable is required'); } return apiKey; } getModel() { return this.defaultModel; } async makeRequest(endpoint, body, stream = false) { const url = `${this.baseURL}/v1/${endpoint}`; const headers = { 'Content-Type': 'application/json', 'x-api-key': this.apiKey, 'anthropic-version': '2023-06-01', 'anthropic-dangerous-direct-browser-access': 'true' // Required for browser usage }; logger.debug(`[AnthropicProvider.makeRequest] ${stream ? 'Streaming' : 'Non-streaming'} request to ${url}`); logger.debug(`[AnthropicProvider.makeRequest] Model: ${body.model}, Max tokens: ${body.max_tokens}`); const response = await fetch(url, { method: 'POST', headers, body: JSON.stringify(body) }); if (!response.ok) { const errorText = await response.text(); logger.error(`[AnthropicProvider.makeRequest] API error ${response.status}: ${errorText}`); throw new Error(`Anthropic API error ${response.status}: ${errorText}`); } return response; } async generateText(optionsOrPrompt, schema) { logger.debug('[AnthropicProvider.generateText] Starting text generation'); // Parse parameters with backward compatibility const options = typeof optionsOrPrompt === 'string' ? { prompt: optionsOrPrompt } : optionsOrPrompt; const { prompt, temperature = 0.7, maxTokens = 500, systemPrompt = 'You are Claude, an AI assistant created by Anthropic. You are helpful, harmless, and honest.' } = options; logger.debug(`[AnthropicProvider.generateText] Prompt: "${prompt.substring(0, 100)}...", Temperature: ${temperature}, Max tokens: ${maxTokens}`); const requestBody = { model: this.getModel(), max_tokens: maxTokens, messages: [ { role: 'user', content: prompt } ], temperature, system: systemPrompt }; try { const response = await this.makeRequest('messages', requestBody); const data = await response.json(); logger.debug(`[AnthropicProvider.generateText] Success. Generated ${data.usage.output_tokens} tokens`); const content = data.content.map(block => block.text).join(''); return { content, provider: this.name, model: data.model, usage: { promptTokens: data.usage.input_tokens, completionTokens: data.usage.output_tokens, totalTokens: data.usage.input_tokens + data.usage.output_tokens }, finishReason: data.stop_reason }; } catch (error) { logger.error('[AnthropicProvider.generateText] Error:', error); throw error; } } async streamText(optionsOrPrompt, schema) { logger.debug('[AnthropicProvider.streamText] Starting text streaming'); // Parse parameters with backward compatibility const options = typeof optionsOrPrompt === 'string' ? { prompt: optionsOrPrompt } : optionsOrPrompt; const { prompt, temperature = 0.7, maxTokens = 500, systemPrompt = 'You are Claude, an AI assistant created by Anthropic. You are helpful, harmless, and honest.' } = options; logger.debug(`[AnthropicProvider.streamText] Streaming prompt: "${prompt.substring(0, 100)}..."`); const requestBody = { model: this.getModel(), max_tokens: maxTokens, messages: [ { role: 'user', content: prompt } ], temperature, system: systemPrompt, stream: true }; try { const response = await this.makeRequest('messages', requestBody, true); if (!response.body) { throw new Error('No response body received'); } // Return a StreamTextResult-like object return { textStream: this.createAsyncIterable(response.body), text: '', usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 }, finishReason: 'end_turn' }; } catch (error) { logger.error('[AnthropicProvider.streamText] Error:', error); throw error; } } async *createAsyncIterable(body) { const reader = body.getReader(); const decoder = new TextDecoder(); let buffer = ''; try { while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { if (line.trim() === '') continue; if (line.startsWith('data: ')) { const data = line.slice(6); if (data.trim() === '[DONE]') continue; try { const chunk = JSON.parse(data); // Extract text content from different chunk types if (chunk.type === 'content_block_delta' && chunk.delta?.text) { yield chunk.delta.text; } } catch (parseError) { logger.warn('[AnthropicProvider.createAsyncIterable] Failed to parse chunk:', parseError); continue; } } } } } finally { reader.releaseLock(); } } async *generateTextStream(optionsOrPrompt) { logger.debug('[AnthropicProvider.generateTextStream] Starting text streaming'); // Parse parameters with backward compatibility const options = typeof optionsOrPrompt === 'string' ? { prompt: optionsOrPrompt } : optionsOrPrompt; const { prompt, temperature = 0.7, maxTokens = 500, systemPrompt = 'You are Claude, an AI assistant created by Anthropic. You are helpful, harmless, and honest.' } = options; logger.debug(`[AnthropicProvider.generateTextStream] Streaming prompt: "${prompt.substring(0, 100)}..."`); const requestBody = { model: this.getModel(), max_tokens: maxTokens, messages: [ { role: 'user', content: prompt } ], temperature, system: systemPrompt, stream: true }; try { const response = await this.makeRequest('messages', requestBody, true); if (!response.body) { throw new Error('No response body received'); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; try { while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { if (line.trim() === '') continue; if (line.startsWith('data: ')) { const data = line.slice(6); if (data.trim() === '[DONE]') continue; try { const chunk = JSON.parse(data); // Extract text content from different chunk types if (chunk.type === 'content_block_delta' && chunk.delta?.text) { yield { content: chunk.delta.text, provider: this.name, model: this.getModel() }; } } catch (parseError) { logger.warn('[AnthropicProvider.generateTextStream] Failed to parse chunk:', parseError); continue; } } } } } finally { reader.releaseLock(); } logger.debug('[AnthropicProvider.generateTextStream] Streaming completed'); } catch (error) { logger.error('[AnthropicProvider.generateTextStream] Error:', error); throw error; } } async testConnection() { logger.debug('[AnthropicProvider.testConnection] Testing connection to Anthropic API'); const startTime = Date.now(); try { await this.generateText({ prompt: 'Hello', maxTokens: 5 }); const responseTime = Date.now() - startTime; logger.debug(`[AnthropicProvider.testConnection] Connection test successful (${responseTime}ms)`); return { success: true, responseTime }; } catch (error) { const responseTime = Date.now() - startTime; logger.error(`[AnthropicProvider.testConnection] Connection test failed (${responseTime}ms):`, error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error', responseTime }; } } isConfigured() { try { this.getApiKey(); return true; } catch { return false; } } getRequiredConfig() { return ['ANTHROPIC_API_KEY']; } getOptionalConfig() { return ['ANTHROPIC_MODEL', 'ANTHROPIC_BASE_URL']; } getModels() { return [ 'claude-3-5-sonnet-20241022', 'claude-3-5-haiku-20241022', 'claude-3-opus-20240229', 'claude-3-sonnet-20240229', 'claude-3-haiku-20240307' ]; } supportsStreaming() { return true; } supportsSchema() { return false; // Anthropic doesn't have native JSON schema support like OpenAI } getCapabilities() { return [ 'text-generation', 'streaming', 'conversation', 'system-prompts', 'long-context' // Claude models support up to 200k tokens ]; } }