// OpenAI-compatible stand-in for api.teamorouter.com — used by the node tests, no dependencies. import { createServer } from 'node:http'; // 1x1 transparent PNG. export const TINY_PNG_BASE64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFAAH/q842iQAAAABJRU5ErkJggg=='; export const MODELS = [ { id: 'gpt-5.6-sol', object: 'model', owned_by: 'openai', pricing: { prompt: 0.00000053, completion: 0.00000318 } }, { id: 'claude-sonnet-5', object: 'model', owned_by: 'anthropic', pricing: { prompt: 0.0000005, completion: 0.00000252 } }, { id: 'deepseek-flash-free', object: 'model', owned_by: 'deepseek' }, { id: 'gemini-3.1-pro-preview', object: 'model', owned_by: 'google', pricing: { input: 0.29, output: 1.73 } }, { id: 'gpt-image-2', object: 'model', owned_by: 'openai' }, { id: 'nano-banana-2.1', object: 'model', owned_by: 'google' }, ]; const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); /** * Models whose IDs start with "fail-" make the mock answer with the matching HTTP status, * "slow-model" delays the answer, everything else echoes the user message back. */ export function createMockServer({ apiKey = 'test-key' } = {}) { const requests = []; const handle = async (request, response) => { const chunks = []; for await (const chunk of request) chunks.push(chunk); const rawBody = Buffer.concat(chunks).toString('utf8'); let body; try { body = rawBody === '' ? undefined : JSON.parse(rawBody); } catch { body = undefined; } requests.push({ method: request.method, url: request.url, headers: request.headers, body }); const json = (status, payload) => { response.writeHead(status, { 'content-type': 'application/json' }); response.end(JSON.stringify(payload)); }; if (!request.url.startsWith('/v1/')) { return json(404, { error: { message: `unknown path ${request.url}` } }); } if (request.headers.authorization !== `Bearer ${apiKey}`) { return json(401, { error: { message: 'invalid api key' } }); } if (request.method === 'GET' && request.url === '/v1/models') { return json(200, { object: 'list', data: MODELS }); } if (request.method === 'POST' && request.url === '/v1/chat/completions') { const model = body?.model ?? ''; if (model.startsWith('fail-')) { const status = Number(model.replace('fail-', '')); return json(status, { error: { message: `forced failure ${status}` } }); } if (model === 'slow-model') await delay(2500); const userMessage = [...(body?.messages ?? [])].reverse().find((message) => message.role === 'user'); const content = `echo: ${userMessage?.content ?? ''}`; if (body?.stream === true) { response.writeHead(200, { 'content-type': 'text/event-stream' }); response.write( `data: ${JSON.stringify({ object: 'chat.completion.chunk', choices: [{ index: 0, delta: { content } }] })}\n\n`, ); response.write('data: [DONE]\n\n'); return response.end(); } return json(200, { object: 'chat.completion', model, choices: [{ index: 0, message: { role: 'assistant', content }, finish_reason: 'stop' }], usage: { prompt_tokens: 11, completion_tokens: 4, total_tokens: 15 }, }); } // LangChain's ChatOpenAI serves "gpt-5.6*" and "codex" model IDs through the Responses API. if (request.method === 'POST' && request.url === '/v1/responses') { const userMessage = (body?.input ?? []).find?.((message) => message.role === 'user'); const content = `echo: ${typeof body?.input === 'string' ? body.input : userMessage?.content ?? ''}`; return json(200, { id: 'resp_mock', object: 'response', created_at: 1, status: 'completed', model: body?.model, output: [ { type: 'message', id: 'msg_mock', role: 'assistant', status: 'completed', content: [{ type: 'output_text', text: content, annotations: [] }], }, ], usage: { input_tokens: 11, output_tokens: 4, total_tokens: 15 }, }); } if (request.method === 'POST' && request.url === '/v1/images/generations') { if (String(body?.prompt ?? '').includes('noimage')) { return json(400, { error: { message: 'prompt rejected' } }); } return json(200, { created: 1, data: [{ b64_json: TINY_PNG_BASE64 }] }); } return json(404, { error: { message: `unknown route ${request.method} ${request.url}` } }); }; const server = createServer((request, response) => { handle(request, response).catch((error) => { response.writeHead(500, { 'content-type': 'application/json' }); response.end(JSON.stringify({ error: { message: String(error) } })); }); }); return { requests, /** Starts on `port` (0 = random) and returns the base URL, e.g. http://127.0.0.1:4123 */ async listen(port = 0) { await new Promise((resolve) => server.listen(port, '127.0.0.1', resolve)); return `http://127.0.0.1:${server.address().port}`; }, async close() { await new Promise((resolve) => server.close(resolve)); }, }; } if (process.argv[1]?.endsWith('mock-server.mjs') === true) { const mock = createMockServer({ apiKey: process.env.MOCK_API_KEY ?? 'test-key' }); const baseUrl = await mock.listen(Number(process.env.PORT ?? 4123)); console.log(`mock TeamoRouter listening on ${baseUrl}/v1 (api key: ${process.env.MOCK_API_KEY ?? 'test-key'})`); }