import { describe, expect, test } from "bun:test"; import { toChatSafeToolOutput, toJsonSafeToolOutput } from "./tool-output"; describe("toJsonSafeToolOutput", () => { test("converts Date fields to ISO strings (the ModelMessage crash fix)", () => { const d = new Date("2026-07-18T19:22:18.820Z"); const out = toJsonSafeToolOutput({ count: 1, events: [{ timestamp: d, source: "git" }], }) as { events: Array<{ timestamp: unknown }> }; expect(out.events[0].timestamp).toBe("2026-07-18T19:22:18.820Z"); expect(typeof out.events[0].timestamp).toBe("string"); }); test("drops undefined fields (undefined is not a JSON value)", () => { const out = toJsonSafeToolOutput({ a: 1, b: undefined }) as { a: number; b?: unknown; }; expect("b" in out).toBe(false); expect(out.a).toBe(1); }); test("null-safes an undefined result", () => { expect(toJsonSafeToolOutput(undefined)).toBeNull(); }); test("passes plain JSON output through unchanged", () => { const v = { count: 2, items: ["a", "b"], nested: { n: 1 } }; expect(toJsonSafeToolOutput(v)).toEqual(v); }); }); // A minimal ActivityReport-shaped object with the raw events echo attached. function reportWithEvents(eventCount: number) { return { range: { start: null, end: null }, totalEstimatedMinutes: 120, totalEvents: eventCount, activeDays: 1, projects: [{ projectName: "scribe", estimatedMinutes: 120 }], events: Array.from({ length: eventCount }, (_, i) => ({ id: `evt-${i}`, source: "git", metadata: { title: `commit ${i}` }, })), }; } describe("toChatSafeToolOutput", () => { test("removes the raw events echo from a generateReport payload (the 509k-token context bomb)", () => { const out = toChatSafeToolOutput({ scope: "me", depth: "exhaustive", report: reportWithEvents(3), }) as Record; const report = out.report as Record; expect("events" in report).toBe(false); expect(out.eventsOmittedFromChat).toBe(3); expect(String(out.eventsNote)).toContain("saveReport"); // The structured rollups survive untouched. expect(report.totalEvents).toBe(3); expect(report.projects).toEqual([ { projectName: "scribe", estimatedMinutes: 120 }, ]); }); test("removes the events echo from getReport's nested { report: { report } } shape", () => { const out = toChatSafeToolOutput({ report: { scope: "me", depth: "exhaustive", report: reportWithEvents(2) }, }) as { report: Record }; const inner = out.report.report as Record; expect("events" in inner).toBe(false); expect(out.report.eventsOmittedFromChat).toBe(2); }); test("leaves a summary report (no events echo) unannotated", () => { const { events: _events, ...summaryReport } = reportWithEvents(0); const out = toChatSafeToolOutput({ scope: "me", depth: "summary", report: summaryReport, }) as Record; expect("eventsOmittedFromChat" in out).toBe(false); expect("eventsNote" in out).toBe(false); }); test("does not touch non-report outputs that happen to have an events array (searchTimeline)", () => { const v = { count: 1, events: [{ timestamp: "2026-07-18T19:22:18.820Z", source: "git" }], }; expect(toChatSafeToolOutput(v)).toEqual(v); }); test("is JSON-safe like toJsonSafeToolOutput (Dates to ISO, undefined dropped)", () => { const out = toChatSafeToolOutput({ when: new Date("2026-07-18T19:22:18.820Z"), gone: undefined, }) as Record; expect(out.when).toBe("2026-07-18T19:22:18.820Z"); expect("gone" in out).toBe(false); }); });