manifest
Version:
Self-hosted Manifest LLM router with embedded server, SQLite database, and dashboard
272 lines • 10.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.toResponsesRequest = toResponsesRequest;
exports.fromResponsesResponse = fromResponsesResponse;
exports.transformResponsesStreamChunk = transformResponsesStreamChunk;
exports.collectChatGptSseResponse = collectChatGptSseResponse;
const crypto_1 = require("crypto");
const chatgpt_helpers_1 = require("./chatgpt-helpers");
function toResponsesRequest(body, model) {
const messages = (body.messages ?? []);
const input = [];
for (const message of messages) {
if (message.role === 'system' || message.role === 'developer')
continue;
if (message.role === 'assistant' &&
Array.isArray(message.tool_calls) &&
message.tool_calls.length > 0) {
const assistantText = (0, chatgpt_helpers_1.extractTextContent)(message.content);
if (assistantText) {
input.push({ role: 'assistant', content: (0, chatgpt_helpers_1.convertContent)(assistantText, 'assistant') });
}
input.push(...(0, chatgpt_helpers_1.convertAssistantToolCalls)(message.tool_calls));
continue;
}
if (message.role === 'tool' || message.role === 'function') {
input.push({
type: 'function_call_output',
call_id: typeof message.tool_call_id === 'string' ? message.tool_call_id : (0, crypto_1.randomUUID)(),
output: (0, chatgpt_helpers_1.extractTextContent)(message.content) ?? JSON.stringify(message.content ?? ''),
});
continue;
}
input.push({ role: message.role, content: (0, chatgpt_helpers_1.convertContent)(message.content, message.role) });
}
const request = {
model,
input,
stream: body.stream !== false,
store: false,
instructions: (0, chatgpt_helpers_1.extractInstructions)(messages),
};
if (Array.isArray(body.tools)) {
request.tools = (0, chatgpt_helpers_1.convertTools)(body.tools);
}
return request;
}
function fromResponsesResponse(data, model) {
const output = (data.output ?? []);
let text = '';
const toolCalls = [];
for (const item of output) {
if (item.type === 'message') {
const content = item.content;
if (!content)
continue;
for (const part of content) {
if (part.type === 'output_text' && part.text)
text += part.text;
}
continue;
}
if (item.type === 'function_call') {
toolCalls.push({
id: item.call_id ?? (0, crypto_1.randomUUID)(),
type: 'function',
function: {
name: item.name ?? '',
arguments: item.arguments ?? '{}',
},
});
}
}
const usage = data.usage ?? {};
const inputDetails = usage.input_tokens_details;
const message = {
role: 'assistant',
content: text || null,
};
if (toolCalls.length > 0) {
message.tool_calls = toolCalls;
}
return {
id: `chatcmpl-${(0, crypto_1.randomUUID)().replace(/-/g, '').slice(0, 29)}`,
object: 'chat.completion',
created: Math.floor(Date.now() / 1000),
model,
choices: [
{
index: 0,
message,
finish_reason: toolCalls.length > 0 ? 'tool_calls' : 'stop',
},
],
usage: {
prompt_tokens: usage.input_tokens ?? 0,
completion_tokens: usage.output_tokens ?? 0,
total_tokens: usage.total_tokens ?? 0,
cache_read_tokens: inputDetails?.cached_tokens ?? 0,
cache_creation_tokens: 0,
},
};
}
function transformResponsesStreamChunk(chunk, model) {
const lines = chunk.split('\n');
let eventType = '';
let dataStr = '';
for (const line of lines) {
if (line.startsWith('event: ')) {
eventType = line.slice(7).trim();
}
else if (line.startsWith('data: ')) {
dataStr = line.slice(6);
}
else if (line.trim()) {
dataStr = line.trim();
}
}
if (!eventType && !dataStr)
return null;
if (eventType === 'response.output_text.delta') {
const data = (0, chatgpt_helpers_1.safeParse)(dataStr);
if (!data)
return null;
const delta = typeof data.delta === 'string' ? data.delta : '';
return (0, chatgpt_helpers_1.formatSSE)({ delta: { content: delta }, finish_reason: null }, model);
}
if (eventType === 'response.function_call_arguments.delta') {
const data = (0, chatgpt_helpers_1.safeParse)(dataStr);
if (!data)
return null;
const delta = typeof data.delta === 'string' ? data.delta : '';
return (0, chatgpt_helpers_1.formatSSE)({
delta: {
tool_calls: [
{
index: typeof data.output_index === 'number' ? data.output_index : 0,
function: { arguments: delta },
},
],
},
finish_reason: null,
}, model);
}
if (eventType === 'response.output_item.added') {
const data = (0, chatgpt_helpers_1.safeParse)(dataStr);
if (!data)
return null;
const item = (0, chatgpt_helpers_1.isObjectRecord)(data.item) ? data.item : undefined;
if (item?.type !== 'function_call')
return null;
return (0, chatgpt_helpers_1.formatSSE)({
delta: {
tool_calls: [
{
index: typeof data.output_index === 'number' ? data.output_index : 0,
id: item.call_id ?? '',
type: 'function',
function: { name: item.name ?? '', arguments: '' },
},
],
},
finish_reason: null,
}, model);
}
if (eventType === 'response.completed') {
return handleCompletedEvent(dataStr, model);
}
return null;
}
function handleCompletedEvent(dataStr, model) {
const data = (0, chatgpt_helpers_1.safeParse)(dataStr);
const response = (0, chatgpt_helpers_1.isObjectRecord)(data?.response) ? data.response : undefined;
const responseUsage = response?.usage;
const inputDetails = responseUsage?.input_tokens_details;
const cachedTokens = inputDetails?.cached_tokens ?? 0;
const usage = responseUsage
? {
prompt_tokens: responseUsage.input_tokens ?? 0,
completion_tokens: responseUsage.output_tokens ?? 0,
total_tokens: responseUsage.total_tokens ?? 0,
cache_read_tokens: cachedTokens,
cache_creation_tokens: 0,
}
: undefined;
const responseOutput = Array.isArray(response?.output)
? response.output
: [];
const hasFunctionCalls = responseOutput.some((item) => item.type === 'function_call');
const finish = (0, chatgpt_helpers_1.formatSSE)({ delta: {}, finish_reason: hasFunctionCalls ? 'tool_calls' : 'stop' }, model, usage);
return `${finish}\ndata: [DONE]\n\n`;
}
function collectChatGptSseResponse(sseText, model) {
let text = '';
const toolCallMap = new Map();
let usage;
let hasFunctionCalls = false;
const events = sseText.split('\n\n');
for (const event of events) {
const lines = event.split('\n');
let eventType = '';
let dataStr = '';
for (const line of lines) {
if (line.startsWith('event: '))
eventType = line.slice(7).trim();
else if (line.startsWith('data: '))
dataStr = line.slice(6);
}
if (!eventType || !dataStr)
continue;
const data = (0, chatgpt_helpers_1.safeParse)(dataStr);
if (!data)
continue;
if (eventType === 'response.output_text.delta') {
text += typeof data.delta === 'string' ? data.delta : '';
}
else if (eventType === 'response.output_item.added') {
const item = (0, chatgpt_helpers_1.isObjectRecord)(data.item) ? data.item : undefined;
if (item?.type === 'function_call') {
const idx = typeof data.output_index === 'number' ? data.output_index : toolCallMap.size;
toolCallMap.set(idx, {
id: item.call_id ?? '',
type: 'function',
function: { name: item.name ?? '', arguments: '' },
});
}
}
else if (eventType === 'response.function_call_arguments.delta') {
const idx = typeof data.output_index === 'number' ? data.output_index : 0;
const tc = toolCallMap.get(idx);
if (tc) {
tc.function.arguments += typeof data.delta === 'string' ? data.delta : '';
}
}
else if (eventType === 'response.completed') {
const response = (0, chatgpt_helpers_1.isObjectRecord)(data.response) ? data.response : undefined;
const respUsage = response?.usage;
if (respUsage) {
const inputDetails = respUsage.input_tokens_details;
usage = {
prompt_tokens: respUsage.input_tokens ?? 0,
completion_tokens: respUsage.output_tokens ?? 0,
total_tokens: respUsage.total_tokens ?? 0,
cache_read_tokens: inputDetails?.cached_tokens ?? 0,
cache_creation_tokens: 0,
};
}
const output = Array.isArray(response?.output)
? response.output
: [];
hasFunctionCalls = output.some((item) => item.type === 'function_call');
}
}
const toolCalls = [...toolCallMap.values()];
const message = { role: 'assistant', content: text || null };
if (toolCalls.length > 0)
message.tool_calls = toolCalls;
return {
id: `chatcmpl-${(0, crypto_1.randomUUID)().replace(/-/g, '').slice(0, 29)}`,
object: 'chat.completion',
created: Math.floor(Date.now() / 1000),
model,
choices: [
{
index: 0,
message,
finish_reason: hasFunctionCalls || toolCalls.length > 0 ? 'tool_calls' : 'stop',
},
],
usage: usage ?? { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
};
}
//# sourceMappingURL=chatgpt-adapter.js.map