import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { fingerprintToolCalls, isSuccessfulWrite, summarizeToolTrace, truncateToolResult, } from "../../src/modules/agent/server/loop-guards"; import { runAgentChat } from "../../src/modules/agent/server/run"; import type { ChatCompletionRequest, ChatCompletionResult, LlmClient, } from "../../src/lib/llm/types"; describe("loop-guards", () => { it("fingerprints tool calls stably regardless of key order", () => { const a = fingerprintToolCalls([ { name: "create_event", arguments: '{"title":"Dentist","calendarName":"Family"}' }, ]); const b = fingerprintToolCalls([ { name: "create_event", arguments: '{"calendarName":"Family","title":"Dentist"}' }, ]); assert.equal(a, b); }); it("treats create_event 201 as a successful write", () => { assert.equal(isSuccessfulWrite("create_event", 201), true); assert.equal(isSuccessfulWrite("list_calendars", 200), false); assert.equal( isSuccessfulWrite("call_api", 201, '{"method":"POST","path":"/api/v1/events"}'), true, ); assert.equal( isSuccessfulWrite("call_api", 200, '{"method":"GET","path":"/api/v1/events"}'), false, ); }); it("truncates oversized tool results", () => { const result = truncateToolResult("x".repeat(7000), 100); assert.ok(result.length < 200); assert.match(result, /truncated/); }); it("summarizes tool traces", () => { assert.equal( summarizeToolTrace([ { name: "list_calendars", status: 200 }, { name: "create_event", status: 201 }, ]), "list_calendars→200, create_event→201", ); }); }); describe("runAgentChat loop guards", () => { it("stops after a successful write instead of looping", async () => { let calls = 0; const llm: LlmClient = { async chatCompletion(request: ChatCompletionRequest): Promise { calls += 1; if (calls === 1) { return { message: { role: "assistant", content: null, tool_calls: [ { id: "1", type: "function", function: { name: "create_event", arguments: JSON.stringify({ title: "Dentist", startAt: "2026-07-10T15:00:00.000Z", endAt: "2026-07-10T16:00:00.000Z", }), }, }, ], }, finishReason: "tool_calls", }; } assert.equal(request.tools, undefined); return { message: { role: "assistant", content: "Added Dentist to your calendar." }, finishReason: "stop", }; }, }; const result = await runAgentChat({ messages: [{ role: "user", content: "add dentist tomorrow at 10" }], request: new Request("http://localhost:3000/api/agent/chat"), llm, executeTool: async () => JSON.stringify({ status: 201, body: { id: "evt-1", title: "Dentist" } }), }); assert.equal(calls, 2); assert.equal(result.toolCalls.length, 1); assert.equal(result.toolCalls[0]?.name, "create_event"); assert.match(result.message.content, /Dentist/); }); it("breaks duplicate identical tool rounds", async () => { let calls = 0; const llm: LlmClient = { async chatCompletion(): Promise { calls += 1; if (calls <= 2) { return { message: { role: "assistant", content: null, tool_calls: [ { id: String(calls), type: "function", function: { name: "list_calendars", arguments: "{}", }, }, ], }, finishReason: "tool_calls", }; } return { message: { role: "assistant", content: "You have one Family calendar." }, finishReason: "stop", }; }, }; const result = await runAgentChat({ messages: [{ role: "user", content: "what calendars do I have?" }], request: new Request("http://localhost:3000/api/agent/chat"), llm, executeTool: async () => JSON.stringify({ status: 200, body: [{ id: "cal-1", name: "Family" }] }), }); assert.equal(calls, 3); assert.equal(result.toolCalls.length, 1); assert.match(result.message.content, /Family/); }); });