import { readFileSync } from "node:fs"; import { runInNewContext } from "node:vm"; import { getSystemMessageText, renderSystemMessageUpdate, type SystemMessage } from "@earendil-works/pi-ai"; import { describe, expect, it } from "vitest"; interface SystemHelpers { getSystemMessageText(message: SystemMessage): string; renderSystemMessageUpdate(message: SystemMessage): string; isLeadingSystemEntry(entry: { id: string; parentId: string | null }): boolean; describeSystemUpdate(message: SystemMessage): string; } const templateJs = readFileSync(new URL("../src/core/export-html/template.js", import.meta.url), "utf-8"); function loadSystemHelpers(entries: unknown[]): SystemHelpers { const start = templateJs.indexOf("function systemContentText(content)"); const end = templateJs.indexOf("const hasSystemEntries"); expect(start).toBeGreaterThanOrEqual(0); expect(end).toBeGreaterThan(start); const context: { entries: unknown[]; helpers?: SystemHelpers } = { entries }; runInNewContext( `const byId = new Map(entries.map(entry => [entry.id, entry])); ${templateJs.slice(start, end)} helpers = { getSystemMessageText, renderSystemMessageUpdate, isLeadingSystemEntry, describeSystemUpdate };`, context, ); return context.helpers!; } const initial: SystemMessage = { role: "system", content: "You are a helpful assistant.", sections: { rules: "\nbe concise\n", project_context: "\nv1\n" }, toolsAdded: [{ name: "read", description: "Read a file", parameters: { type: "object", properties: {} } as never }], timestamp: 1, }; const update: SystemMessage = { role: "system", content: [{ type: "text", text: "Extra instructions" }], sections: { project_context: "\nv2\n", rules: null }, toolsAdded: [ { name: "bash", description: "Run a command", parameters: { type: "object", properties: {} } as never }, ], toolsRemoved: [{ name: "read" }], timestamp: 2, }; const entries = [ { type: "message", id: "s1", parentId: null, message: initial }, { type: "message", id: "u1", parentId: "s1", message: { role: "user", content: "hi" } }, { type: "message", id: "s2", parentId: "u1", message: update }, ]; // Regression tests for #11261: system messages were stored in the export but never rendered. describe("export HTML system messages", () => { it("renders system message text the same way the model receives it", () => { const helpers = loadSystemHelpers(entries); expect(helpers.getSystemMessageText(initial)).toBe(getSystemMessageText(initial)); expect(helpers.renderSystemMessageUpdate(update)).toBe(renderSystemMessageUpdate(update)); }); it("renders the first system entry on a branch as the prompt and later ones as updates", () => { const helpers = loadSystemHelpers(entries); expect(helpers.isLeadingSystemEntry(entries[0])).toBe(true); expect(helpers.isLeadingSystemEntry(entries[2])).toBe(false); expect(helpers.describeSystemUpdate(update)).toBe("instructions, project_context, -rules, tools +bash -read"); }); it("treats a compaction snapshot as earlier system state", () => { const helpers = loadSystemHelpers([ { type: "compaction", id: "c1", parentId: null, systemMessage: initial }, { type: "message", id: "s2", parentId: "c1", message: update }, ]); expect(helpers.isLeadingSystemEntry({ id: "s2", parentId: "c1" })).toBe(false); }); it("renders system entries in the transcript", () => { expect(templateJs).toMatch(/if \(msg\.role === 'system'\) \{\s*let html = '';/); expect(templateJs).toContain("renderSystemPromptBlock('System Prompt', text)"); expect(templateJs).toContain("renderToolsList('Available Tools', msg.toolsAdded)"); }); });