manifest
Version:
Self-hosted Manifest LLM router with embedded server, SQLite database, and dashboard
295 lines • 10.7 kB
JavaScript
"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