UNPKG

anon-identity

Version:

Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure

500 lines 17.3 kB
"use strict"; /** * Anthropic MCP Provider Implementation * * Provider implementation for Anthropic Claude LLM services */ Object.defineProperty(exports, "__esModule", { value: true }); exports.AnthropicProviderFactory = exports.AnthropicProvider = void 0; const types_1 = require("../types"); const base_provider_1 = require("./base-provider"); /** * Anthropic MCP Provider */ class AnthropicProvider extends base_provider_1.BaseLLMProvider { constructor(config) { super(config); this.apiKey = config.apiKey || process.env.ANTHROPIC_API_KEY || ''; this.baseURL = config.endpoint || 'https://api.anthropic.com/v1'; this.apiVersion = '2023-06-01'; // Set Anthropic-specific capabilities this.capabilities = { completion: true, streaming: true, functionCalling: false, // Claude doesn't have native function calling yet embeddings: false, moderation: false, multimodal: true, codeGeneration: true, jsonMode: false }; // Set Anthropic models this.models = [ { id: 'claude-3-opus-20240229', name: 'Claude 3 Opus', description: 'Most capable Claude 3 model', capabilities: ['completion', 'code_generation', 'multimodal'], contextLength: 200000, inputCost: 0.015, outputCost: 0.075, deprecated: false }, { id: 'claude-3-sonnet-20240229', name: 'Claude 3 Sonnet', description: 'Balanced performance and speed', capabilities: ['completion', 'code_generation', 'multimodal'], contextLength: 200000, inputCost: 0.003, outputCost: 0.015, deprecated: false }, { id: 'claude-3-haiku-20240307', name: 'Claude 3 Haiku', description: 'Fastest Claude 3 model', capabilities: ['completion', 'code_generation'], contextLength: 200000, inputCost: 0.00025, outputCost: 0.00125, deprecated: false }, { id: 'claude-2.1', name: 'Claude 2.1', description: 'Previous generation Claude model', capabilities: ['completion', 'code_generation'], contextLength: 200000, inputCost: 0.008, outputCost: 0.024, deprecated: false } ]; } /** * Initialize the provider */ async initialize() { if (!this.apiKey) { throw new types_1.MCPError({ code: types_1.MCPErrorCode.INVALID_CONFIG, message: 'Anthropic API key is required', timestamp: new Date(), provider: this.id, retryable: false }); } try { // Test the API key with a simple request await this.testConnection(); this.status = types_1.ProviderStatus.AVAILABLE; } catch (error) { this.status = types_1.ProviderStatus.ERROR; throw new types_1.MCPError({ code: types_1.MCPErrorCode.PROVIDER_ERROR, message: `Failed to initialize Anthropic provider: ${error.message}`, timestamp: new Date(), provider: this.id, retryable: true }); } } /** * Test connection to Anthropic API */ async testConnection() { // Anthropic doesn't have a models endpoint, so we'll try a minimal message const response = await fetch(`${this.baseURL}/messages`, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify({ model: 'claude-3-haiku-20240307', max_tokens: 1, messages: [ { role: 'user', content: 'test' } ] }) }); if (!response.ok && response.status !== 400) { // 400 is expected for minimal request, but other errors indicate real issues throw new Error(`HTTP ${response.status}: ${response.statusText}`); } } /** * Check provider health */ async health() { const start = Date.now(); try { await this.testConnection(); const latency = Date.now() - start; return { status: 'healthy', latency, details: { apiEndpoint: this.baseURL, modelsAvailable: this.models.length, version: this.apiVersion } }; } catch (error) { return { status: 'unhealthy', details: { error: error.message, apiEndpoint: this.baseURL } }; } } /** * Send completion request */ async completion(request, context) { const model = request.parameters?.model || this.getDefaultModel(request.type); this.validateModel(model); const requestBody = this.buildMessageRequest(request, model); try { const response = await fetch(`${this.baseURL}/messages`, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify(requestBody) }); if (!response.ok) { throw await this.handleAPIError(response); } const data = await response.json(); return this.convertMessageResponse(request, context, data); } catch (error) { if (error instanceof types_1.MCPError) { throw error; } throw new types_1.MCPError({ code: types_1.MCPErrorCode.PROVIDER_ERROR, message: `Anthropic API error: ${error.message}`, timestamp: new Date(), provider: this.id, requestId: request.id, retryable: true }); } } /** * Send streaming completion request */ async *stream(request, context) { const model = request.parameters?.model || this.getDefaultModel(request.type); this.validateModel(model); const requestBody = this.buildMessageRequest(request, model, true); try { const response = await fetch(`${this.baseURL}/messages`, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify(requestBody) }); if (!response.ok) { throw await this.handleAPIError(response); } if (!response.body) { throw new Error('No response body for streaming'); } 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.startsWith('data: ')) { const data = line.slice(6); if (data === '[DONE]') { return; } try { const event = JSON.parse(data); const responseChunk = this.convertStreamEvent(request, event); if (responseChunk) { yield responseChunk; } } catch (parseError) { // Skip invalid JSON chunks continue; } } } } } finally { reader.releaseLock(); } } catch (error) { if (error instanceof types_1.MCPError) { throw error; } throw new types_1.MCPError({ code: types_1.MCPErrorCode.PROVIDER_ERROR, message: `Anthropic streaming error: ${error.message}`, timestamp: new Date(), provider: this.id, requestId: request.id, retryable: true }); } } /** * Generate embeddings (not supported by Anthropic) */ async embed(request, context) { throw new types_1.MCPError({ code: types_1.MCPErrorCode.FUNCTION_NOT_FOUND, message: 'Embeddings not supported by Anthropic provider', timestamp: new Date(), provider: this.id, requestId: request.id, retryable: false }); } /** * Moderate content (not supported by Anthropic) */ async moderate(request, context) { throw new types_1.MCPError({ code: types_1.MCPErrorCode.FUNCTION_NOT_FOUND, message: 'Content moderation not supported by Anthropic provider', timestamp: new Date(), provider: this.id, requestId: request.id, retryable: false }); } /** * Build message request body for Anthropic API */ buildMessageRequest(request, model, stream = false) { const messages = this.buildMessages(request); const requestBody = { model, max_tokens: request.parameters?.maxTokens || 4096, messages, stream }; // Add optional parameters if (request.parameters?.temperature !== undefined) { requestBody.temperature = request.parameters.temperature; } if (request.parameters?.topP !== undefined) { requestBody.top_p = request.parameters.topP; } if (request.parameters?.stop) { requestBody.stop_sequences = Array.isArray(request.parameters.stop) ? request.parameters.stop : [request.parameters.stop]; } // Add system message if available from context if (request.context?.summary) { requestBody.system = request.context.summary; } return requestBody; } /** * Build messages array for Anthropic API */ buildMessages(request) { const messages = []; // Add context messages if available (skip system messages as they go in system field) if (request.context?.history) { for (const msg of request.context.history) { if (msg.role !== 'system') { messages.push({ role: msg.role === 'assistant' ? 'assistant' : 'user', content: msg.content }); } } } // Add current prompt messages.push({ role: 'user', content: request.prompt }); return messages; } /** * Convert Anthropic message response to standard format */ convertMessageResponse(request, context, data) { const baseResponse = this.createBaseResponse(request, context); const content = data.content.map(block => block.text).join(''); const usage = { promptTokens: data.usage.input_tokens, completionTokens: data.usage.output_tokens, totalTokens: data.usage.input_tokens + data.usage.output_tokens, model: data.model, provider: this.id, cost: this.calculateCost(data.usage.input_tokens, data.usage.output_tokens, data.model) }; // Handle function calling through tool use (if we implement it later) let functionCall; if (request.type === types_1.LLMRequestType.FUNCTION_CALL) { // For now, we'll try to extract function calls from the text response functionCall = this.extractFunctionCallFromText(content); } return { ...baseResponse, content, functionCall, usage, model: data.model }; } /** * Convert Anthropic stream event to standard format */ convertStreamEvent(request, event) { if (event.type === 'content_block_delta' && event.delta?.text) { return { id: `chunk-${Date.now()}`, requestId: request.id, delta: event.delta.text, finished: false }; } if (event.type === 'message_stop') { return { id: `chunk-${Date.now()}`, requestId: request.id, delta: '', finished: true }; } return null; } /** * Extract function calls from text response (basic implementation) */ extractFunctionCallFromText(content) { // Look for function call patterns in the response const functionCallPattern = /<function_call>\s*\{([^}]+)\}\s*<\/function_call>/; const match = content.match(functionCallPattern); if (match) { try { const call = JSON.parse(`{${match[1]}}`); return { name: call.name, arguments: call.arguments || {}, id: `call-${Date.now()}` }; } catch { // Invalid function call format } } return undefined; } /** * Get HTTP headers for Anthropic API */ getHeaders() { const headers = { 'Content-Type': 'application/json', 'x-api-key': this.apiKey, 'anthropic-version': this.apiVersion, 'User-Agent': 'anon-identity-mcp/1.0.0' }; // Add custom headers from config if (this.config.customHeaders) { Object.assign(headers, this.config.customHeaders); } return headers; } /** * Handle Anthropic API errors */ async handleAPIError(response) { let errorData; try { errorData = await response.json(); } catch { errorData = { error: { message: response.statusText } }; } const errorMessage = errorData.error?.message || errorData.message || 'Unknown Anthropic API error'; let errorCode = types_1.MCPErrorCode.PROVIDER_ERROR; switch (response.status) { case 400: errorCode = types_1.MCPErrorCode.INVALID_REQUEST; break; case 401: errorCode = types_1.MCPErrorCode.UNAUTHORIZED; break; case 403: errorCode = types_1.MCPErrorCode.FORBIDDEN; break; case 404: errorCode = types_1.MCPErrorCode.MODEL_NOT_FOUND; break; case 429: errorCode = types_1.MCPErrorCode.RATE_LIMITED; break; case 500: case 502: case 503: errorCode = types_1.MCPErrorCode.PROVIDER_UNAVAILABLE; break; } return new types_1.MCPError({ code: errorCode, message: errorMessage, timestamp: new Date(), provider: this.id, retryable: [500, 502, 503, 429].includes(response.status), details: { status: response.status, headers: Object.fromEntries(response.headers.entries()), body: errorData } }); } /** * Override function conversion for Anthropic (currently not supported) */ convertFunctions(functions) { // Anthropic doesn't have native function calling yet // We could potentially convert to tool use format in the future return []; } } exports.AnthropicProvider = AnthropicProvider; /** * Anthropic Provider Factory */ class AnthropicProviderFactory { static createProvider(config) { return new AnthropicProvider(config); } static validateConfig(config) { if (!config.apiKey && !process.env.ANTHROPIC_API_KEY) { return false; } return true; } static getProviderType() { return 'anthropic'; } } exports.AnthropicProviderFactory = AnthropicProviderFactory; //# sourceMappingURL=anthropic-provider.js.map