import test from "node:test"; import assert from "node:assert/strict"; import { runAgent } from "../src/agent.js"; import { renderMiniCPMPrompt, normalizedCall } from "../src/prompt.js"; import { DEFAULTS } from "../src/core.js"; const call = (args) => ({ index: 0, id: "test-call", function: { name: "create_html", arguments: args }, }); const chunk = (delta, finish) => ({ choices: [{ delta, finish_reason: finish }], }); test("an unterminated tool call is reported, repaired with a larger budget, executed and followed up", async () => { const history = [{ role: "user", content: "Create a black hole in HTML" }]; let requests = 0, created = 0; async function chat(messages, settings, tools, signal, emit) { requests++; if (requests === 1) { await emit( chunk( { tool_calls: [call('{"title":"Black hole","html":"')] }, "length", ), ); return; } assert.doesNotThrow(() => renderMiniCPMPrompt(messages, { tools: [] })); for (const m of messages) for (const c of m.tool_calls || []) assert.doesNotThrow(() => JSON.parse(c.function.arguments)); if (requests === 2) { assert.ok(settings.maxTokens >= 8192); assert.equal(settings.thinking, false); assert.match(messages.at(-1).content, /cut off/); await emit( chunk( { tool_calls: [ call( JSON.stringify({ title: "Black hole", html: "

Black hole

", }), ), ], }, "tool_calls", ), ); return; } assert.equal(JSON.parse(messages.at(-1).content).success, true); await emit(chunk({ content: "Your black hole is ready." }, "stop")); } const events = []; await runAgent({ chat, history, settings: { ...DEFAULTS, maxTokens: 1536, thinking: true }, signal: new AbortController().signal, createArtifact: async (name, args) => { created++; assert.ok(args.html.includes("

")); return { id: "file", filename: "Black-hole.html" }; }, onEvent: (event) => events.push(event), }); assert.equal(requests, 3); assert.equal(created, 1); assert.equal(events.find((e) => e.type === "tool-success").recovered, true); assert.equal(events.at(-1).type, "done"); }); test("tool validation errors are fed back and do not terminate the turn", async () => { let requests = 0, attempts = 0; const history = [{ role: "user", content: "Make a file" }]; async function chat(messages, settings, tools, signal, emit) { requests++; if (requests < 3) { if (requests === 2) assert.match(messages.at(-1).content, /closing tag/); await emit( chunk( { tool_calls: [ call( JSON.stringify({ title: "Page", html: requests === 1 ? '' : 'Fixed', }), ), ], }, "stop", ), ); } else await emit(chunk({ content: "Done" }, "stop")); } await runAgent({ chat, history, settings: DEFAULTS, signal: new AbortController().signal, createArtifact: async (_, args) => { attempts++; if (args.html.startsWith(" { const c = { id: "x", function: { name: "create_spreadsheet", arguments: JSON.stringify({ sheets: [{ rows: [[1, true, null]] }] }), }, }; const prompt = renderMiniCPMPrompt([ { role: "user", content: "Make a sheet" }, { role: "assistant", content: "", tool_calls: [c] }, { role: "tool", content: "success" }, ]); assert.equal(prompt.match(/\nsuccess")); }); test("malformed older history remains representable", () => { const c = normalizedCall({ function: { name: "create_html", arguments: '{"html":"unfinished' }, }); assert.equal( JSON.parse(c.function.arguments).invalid_arguments, '{"html":"unfinished', ); }); test("consumer errors propagate and cancellation stops callback streaming", async () => { const controller = new AbortController(); let delivered = 0; const chat = async (m, s, t, signal, emit) => { for (let i = 0; i < 10000; i++) { signal.throwIfAborted(); await emit(chunk({ content: "x" }, null)); } }; await assert.rejects( runAgent({ chat, history: [], settings: DEFAULTS, signal: controller.signal, createArtifact: () => {}, onEvent: (e) => { if (e.type === "chunk" && ++delivered === 500) controller.abort(); }, }), { name: "AbortError" }, ); assert.equal(delivered, 500); await assert.rejects( runAgent({ chat, history: [], settings: DEFAULTS, signal: new AbortController().signal, createArtifact: () => {}, onEvent: (e) => { if (e.type === "chunk") throw Error("consumer failed"); }, }), /consumer failed/, ); });