UNPKG

manifest

Version:

Self-hosted Manifest LLM router with embedded server, SQLite database, and dashboard

295 lines 10.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.toAnthropicRequest = toAnthropicRequest; exports.fromAnthropicResponse = fromAnthropicResponse; exports.createAnthropicStreamTransformer = createAnthropicStreamTransformer; exports.transformAnthropicStreamChunk = transformAnthropicStreamChunk; const crypto_1 = require("crypto"); const CACHE = { type: 'ephemeral' }; const SUBSCRIPTION_IDENTITY_BLOCK = { type: 'text', text: "You are a Claude agent, built on Anthropic's Claude Agent SDK.", cache_control: { type: 'ephemeral' }, }; function safeParseArgs(args) { try { return JSON.parse(args || '{}'); } catch { return {}; } } function extractSystemBlocks(messages) { const blocks = []; for (const msg of messages) { if (msg.role !== 'system' && msg.role !== 'developer') continue; if (typeof msg.content === 'string') { blocks.push({ type: 'text', text: msg.content }); } else if (Array.isArray(msg.content)) { for (const part of msg.content) { if (part.type === 'text' && typeof part.text === 'string') { blocks.push({ type: 'text', text: part.text }); } } } } return blocks; } function toContentBlocks(content) { if (typeof content === 'string') return content ? [{ type: 'text', text: content }] : []; if (Array.isArray(content)) { return content .filter((b) => b.type === 'text' && typeof b.text === 'string') .map((b) => ({ type: 'text', text: b.text })); } return []; } function convertMessage(msg) { if (msg.role === 'system' || msg.role === 'developer') return null; if (msg.role === 'tool') { const text = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content ?? ''); return { role: 'user', content: [ { type: 'tool_result', tool_use_id: msg.tool_call_id || 'unknown', content: text, }, ], }; } if (msg.role === 'assistant') { const blocks = [...toContentBlocks(msg.content)]; if (Array.isArray(msg.tool_calls)) { for (const tc of msg.tool_calls) { blocks.push({ type: 'tool_use', id: tc.id, name: tc.function.name, input: safeParseArgs(tc.function.arguments), }); } } return blocks.length > 0 ? { role: 'assistant', content: blocks } : null; } const blocks = toContentBlocks(msg.content); return blocks.length > 0 ? { role: 'user', content: blocks } : null; } function convertTools(tools) { if (!tools || tools.length === 0) return undefined; const out = []; for (const t of tools) { const fn = t.function; if (fn) out.push({ name: fn.name, description: fn.description, input_schema: fn.parameters }); } return out.length > 0 ? out : undefined; } function toAnthropicRequest(body, _model, options) { const shouldCache = options?.injectCacheControl !== false; const messages = body.messages || []; const systemBlocks = extractSystemBlocks(messages); if (systemBlocks.length > 0 && shouldCache) { systemBlocks[systemBlocks.length - 1].cache_control = CACHE; } if (options?.injectSubscriptionIdentity) { systemBlocks.unshift({ ...SUBSCRIPTION_IDENTITY_BLOCK }); } const converted = messages.map(convertMessage).filter(Boolean); const result = { messages: converted, max_tokens: body.max_tokens || 4096, }; if (systemBlocks.length > 0) result.system = systemBlocks; const tools = convertTools(body.tools); if (tools) { if (shouldCache) tools[tools.length - 1].cache_control = CACHE; result.tools = tools; } if (body.temperature !== undefined) result.temperature = body.temperature; if (body.top_p !== undefined) result.top_p = body.top_p; return result; } function mapStopReason(reason) { if (!reason) return 'stop'; const map = { end_turn: 'stop', max_tokens: 'length', tool_use: 'tool_calls', stop_sequence: 'stop', }; return map[reason] ?? 'stop'; } function fromAnthropicResponse(resp, model) { const content = resp.content || []; let textContent = ''; const toolCalls = []; for (const block of content) { if (block.type === 'text' && typeof block.text === 'string') textContent += block.text; if (block.type === 'tool_use') { toolCalls.push({ id: block.id, type: 'function', function: { name: block.name, arguments: JSON.stringify(block.input ?? {}) }, }); } } const message = { role: 'assistant', content: textContent || null }; if (toolCalls.length > 0) message.tool_calls = toolCalls; const usage = resp.usage; const cacheRead = usage?.cache_read_input_tokens ?? 0; const cacheCreation = usage?.cache_creation_input_tokens ?? 0; const totalInput = (usage?.input_tokens ?? 0) + cacheRead + cacheCreation; const totalOutput = usage?.output_tokens ?? 0; return { id: `chatcmpl-${(0, crypto_1.randomUUID)()}`, object: 'chat.completion', created: Math.floor(Date.now() / 1000), model, choices: [ { index: 0, message, finish_reason: mapStopReason(resp.stop_reason) }, ], usage: usage ? { prompt_tokens: totalInput, completion_tokens: totalOutput, total_tokens: totalInput + totalOutput, prompt_tokens_details: { cached_tokens: cacheRead }, cache_read_tokens: cacheRead, cache_creation_tokens: cacheCreation, } : undefined, }; } function makeChunkSse(model, delta, finishReason) { return `data: ${JSON.stringify({ id: `chatcmpl-${(0, crypto_1.randomUUID)()}`, object: 'chat.completion.chunk', created: Math.floor(Date.now() / 1000), model, choices: [{ index: 0, delta, finish_reason: finishReason }], })}\n\n`; } function parseStreamEvent(chunk) { if (!chunk.trim()) return null; const lines = chunk.split('\n'); let eventType = ''; let jsonPayload = ''; for (const line of lines) { const trimmed = line.trim(); if (trimmed.startsWith('event:')) eventType = trimmed.slice(6).trim(); else if (trimmed) jsonPayload = trimmed; } if (!jsonPayload) return null; try { return { eventType, data: JSON.parse(jsonPayload) }; } catch { return null; } } function makeUsageSse(model, usage) { return `data: ${JSON.stringify({ id: `chatcmpl-${(0, crypto_1.randomUUID)()}`, object: 'chat.completion.chunk', created: Math.floor(Date.now() / 1000), model, choices: [], usage, })}\n\n`; } function createAnthropicStreamTransformer(model) { let inputTokens = 0; let cacheReadTokens = 0; let cacheCreationTokens = 0; let toolCallIndex = 0; const blockToToolIndex = new Map(); return (chunk) => { const parsed = parseStreamEvent(chunk); if (!parsed) return null; const { eventType, data } = parsed; if (eventType === 'message_start' || data.type === 'message_start') { const msg = data.message; const usage = msg?.usage; inputTokens = usage?.input_tokens ?? 0; cacheReadTokens = usage?.cache_read_input_tokens ?? 0; cacheCreationTokens = usage?.cache_creation_input_tokens ?? 0; return makeChunkSse(model, { role: 'assistant', content: '' }, null); } if (eventType === 'content_block_start' || data.type === 'content_block_start') { const block = data.content_block; if (block?.type === 'tool_use') { const idx = toolCallIndex++; blockToToolIndex.set(data.index, idx); return makeChunkSse(model, { tool_calls: [ { index: idx, id: block.id, type: 'function', function: { name: block.name, arguments: '' }, }, ], }, null); } return null; } if (eventType === 'content_block_delta' || data.type === 'content_block_delta') { const delta = data.delta; if (!delta) return null; if (delta.type === 'text_delta' && typeof delta.text === 'string') { return makeChunkSse(model, { content: delta.text }, null); } if (delta.type === 'input_json_delta' && typeof delta.partial_json === 'string') { const idx = blockToToolIndex.get(data.index); if (idx !== undefined) { return makeChunkSse(model, { tool_calls: [{ index: idx, function: { arguments: delta.partial_json } }], }, null); } } return null; } if (eventType === 'message_delta' || data.type === 'message_delta') { const delta = data.delta; const usage = data.usage; const outputTokens = usage?.output_tokens ?? 0; const totalInput = inputTokens + cacheReadTokens + cacheCreationTokens; const total = totalInput + outputTokens; const finishChunk = makeChunkSse(model, {}, mapStopReason(delta?.stop_reason)); const usageChunk = makeUsageSse(model, { prompt_tokens: totalInput, completion_tokens: outputTokens, total_tokens: total, prompt_tokens_details: { cached_tokens: cacheReadTokens }, cache_read_tokens: cacheReadTokens, cache_creation_tokens: cacheCreationTokens, }); return finishChunk + usageChunk; } return null; }; } function transformAnthropicStreamChunk(chunk, model) { return createAnthropicStreamTransformer(model)(chunk); } //# sourceMappingURL=anthropic-adapter.js.map