import test from 'node:test'; import assert from 'node:assert/strict'; import http from 'node:http'; const requests = []; let responder; const server = http.createServer(async (req, res) => { let body = ''; for await (const chunk of req) body += chunk; requests.push(JSON.parse(body)); res.writeHead(200, { 'Content-Type': 'text/event-stream' }); for (const event of responder(requests.length)) res.write(`data: ${JSON.stringify(event)}\n\n`); res.end('data: [DONE]\n\n'); }); await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)); process.env.OPENROUTER_URL = `http://127.0.0.1:${server.address().port}`; process.env.OPENROUTER_API_KEY = 'test'; process.env.LLM_MODE = 'openrouter'; const { streamAnswer } = await import('../server/providers.mjs'); test.after(() => server.close()); const event = (delta, finish_reason = null) => ({ choices: [{ delta, finish_reason }] }); const settings = { systemPrompt: 'test', llmModel: 'test' }; test('assembles fragmented calls before executing and streams text', async () => { requests.length = 0; responder = n => n === 1 ? [event({ tool_calls: [{ index: 0, id: 'call_1', type: 'function', function: { name: 'spotify_play', arguments: '{"query":"' } }] }), event({ tool_calls: [{ index: 0, function: { arguments: 'Queen"}' } }] }, 'tool_calls')] : [event({ content: 'Готово' }, 'stop')]; const calls = [], text = []; const answer = await streamAnswer(settings, [], 'play', new AbortController().signal, part => text.push(part), async (name, args) => { calls.push([name, args]); return { ok: true }; }); assert.equal(answer, 'Готово'); assert.deepEqual(text, ['Готово']); assert.deepEqual(calls, [['spotify_play', { query: 'Queen' }]]); assert.equal(requests[0].tools[0].type, 'openrouter:web_search'); assert.equal(requests[1].messages.at(-1).tool_call_id, 'call_1'); }); test('does not execute incomplete tool calls', async () => { requests.length = 0; responder = () => [event({ tool_calls: [{ index: 0, id: 'x', function: { name: 'spotify_control', arguments: '{"action":"next"}' } }] }, 'length')]; let called = false; await assert.rejects(streamAnswer(settings, [], 'next', new AbortController().signal, () => {}, async () => { called = true; }), /llm_incomplete/); assert.equal(called, false); }); test('limits repeated tool calls', async () => { requests.length = 0; responder = n => [event({ tool_calls: [{ index: 0, id: `call_${n}`, function: { name: 'spotify_control', arguments: '{"action":"next"}' } }] }, 'tool_calls')]; let calls = 0; await assert.rejects(streamAnswer(settings, [], 'next', new AbortController().signal, () => {}, async () => { calls++; return { ok: true }; }), /llm_limit/); assert.equal(calls, 3); });