UNPKG

capsule-ai-cli

Version:

The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing

246 lines 9.84 kB
import { BaseProvider } from './base.js'; export class AnthropicProvider extends BaseProvider { name = 'anthropic'; models = ['claude-opus-4-20250514', 'claude-sonnet-4-20250514']; supportsStreaming = true; supportsTools = true; pricing = { 'claude-opus-4-20250514': { prompt: 15, completion: 75 }, 'claude-sonnet-4-20250514': { prompt: 3, completion: 15 } }; apiVersion = '2023-06-01'; baseEndpoint = 'https://api.anthropic.com/v1/messages'; constructor(apiKey) { super(apiKey); } async complete(messages, options = {}) { this.validateMessages(messages); const model = options.model || this.models[0]; const systemMessage = messages.find(m => m.role === 'system'); const nonSystemMessages = messages.filter(m => m.role !== 'system'); const body = { model, max_tokens: options.maxTokens || 1024, temperature: options.temperature, messages: this.formatMessages(nonSystemMessages) }; if (systemMessage) { body.system = typeof systemMessage.content === 'string' ? systemMessage.content : this.getTextContent(systemMessage); } if (options.tools && options.tools.length) { body.tools = this.formatTools(options.tools); body.tool_choice = { type: 'auto' }; } const res = await fetch(this.baseEndpoint, { method: 'POST', headers: { 'x-api-key': this.apiKey, 'anthropic-version': this.apiVersion, 'content-type': 'application/json' }, body: JSON.stringify(body) }); if (!res.ok) { const err = await res.text(); throw new Error(`Anthropic API error ${res.status}: ${err}`); } const data = await res.json(); return this.parseMessageResponse(data, model); } async *stream(messages, options = {}) { this.validateMessages(messages); const model = options.model || this.models[0]; const systemMessage = messages.find(m => m.role === 'system'); const nonSystemMessages = messages.filter(m => m.role !== 'system'); const body = { model, max_tokens: options.maxTokens || 1024, temperature: options.temperature, messages: this.formatMessages(nonSystemMessages), stream: true }; if (systemMessage) { body.system = typeof systemMessage.content === 'string' ? systemMessage.content : this.getTextContent(systemMessage); } if (options.tools && options.tools.length) { body.tools = this.formatTools(options.tools); body.tool_choice = { type: 'auto' }; } const res = await fetch(this.baseEndpoint, { method: 'POST', headers: { 'x-api-key': this.apiKey, 'anthropic-version': this.apiVersion, 'content-type': 'application/json' }, body: JSON.stringify(body) }); if (!res.ok || !res.body) { const err = await res.text(); throw new Error(`Anthropic stream error ${res.status}: ${err}`); } const reader = res.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; let totalTokens = 0; let currentToolCall = null; let toolInputBuffer = ''; while (true) { const { value, done } = 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: ')) continue; const payload = line.slice(6); if (payload === '[DONE]') return; try { const evt = JSON.parse(payload); if (evt.type === 'content_block_delta' && evt.delta?.text) { totalTokens += this.countTokens(evt.delta.text); yield { delta: evt.delta.text }; } if (evt.type === 'content_block_start' && evt.content_block?.type === 'tool_use') { currentToolCall = { id: evt.content_block.id, name: evt.content_block.name }; toolInputBuffer = ''; } if (evt.type === 'content_block_delta' && evt.delta?.type === 'input_json_delta') { toolInputBuffer += evt.delta.partial_json; } if (evt.type === 'content_block_stop' && currentToolCall) { yield { delta: '', toolCall: { id: currentToolCall.id, name: currentToolCall.name, arguments: JSON.parse(toolInputBuffer) } }; currentToolCall = null; toolInputBuffer = ''; } if (evt.type === 'message_stop' && evt.usage) { const u = evt.usage; yield { delta: '', usage: { promptTokens: u.input_tokens, completionTokens: u.output_tokens, totalTokens: u.output_tokens + u.input_tokens } }; } } catch (e) { } } } } calculateCost(usage, model = this.models[0]) { const price = this.pricing[model] || this.pricing[this.models[0]]; const promptCost = (usage.promptTokens / 1_000_000) * price.prompt; const completionCost = (usage.completionTokens / 1_000_000) * price.completion; return { amount: promptCost + completionCost, currency: 'USD', breakdown: { prompt: promptCost, completion: completionCost } }; } formatMessages(messages) { const formatted = []; for (const msg of messages) { if (msg.role === 'tool_result') { formatted.push({ role: 'user', content: [ { type: 'tool_result', tool_use_id: msg.tool_call_id, content: typeof msg.content === 'string' ? msg.content : this.getTextContent(msg) } ] }); continue; } const claudeMsg = { role: msg.role, content: [] }; if (msg.role === 'assistant' && msg.tool_calls) { if (msg.content && typeof msg.content === 'string' && msg.content.trim()) { claudeMsg.content.push({ type: 'text', text: msg.content }); } for (const toolCall of msg.tool_calls) { claudeMsg.content.push({ type: 'tool_use', id: toolCall.id, name: toolCall.name, input: toolCall.arguments }); } if (claudeMsg.content.length === 0) { continue; } formatted.push(claudeMsg); continue; } if (typeof msg.content === 'string') { if (!msg.content.trim()) { continue; } claudeMsg.content = msg.content; } else if (Array.isArray(msg.content)) { for (const part of msg.content) { if (part.type === 'text') { claudeMsg.content.push({ type: 'text', text: part.text }); } else if (part.type === 'image') { claudeMsg.content.push(part); } } if (claudeMsg.content.length === 0) { continue; } } formatted.push(claudeMsg); } return formatted; } parseMessageResponse(data, model) { let contentText = ''; const toolCalls = []; for (const block of data.content) { if (block.type === 'text') { contentText += block.text; } else if (block.type === 'tool_use') { toolCalls.push({ id: block.id, name: block.name, arguments: block.input }); } } const usage = { promptTokens: data.usage?.input_tokens || 0, completionTokens: data.usage?.output_tokens || 0, totalTokens: (data.usage?.input_tokens || 0) + (data.usage?.output_tokens || 0) }; return { provider: this.name, model, content: contentText, usage, toolCalls: toolCalls.length ? toolCalls : undefined }; } formatTools(tools) { return tools.map(t => ({ name: t.name, description: t.description, input_schema: t.parameters || t.input_schema })); } } //# sourceMappingURL=anthropic.js.map