import { stripVTControlCharacters } from "node:util"; import type { AgentToolResult } from "@earendil-works/pi-agent-core"; import type { Text } from "@earendil-works/pi-tui"; import { beforeAll, describe, expect, it } from "vitest"; import type { ToolRenderContext } from "../src/core/extensions/types.ts"; import { codemodeRenderers } from "../src/extensions/codemode/renderer.ts"; import type { CodemodeToolDetails } from "../src/extensions/codemode/tool.ts"; import { initTheme, theme } from "../src/modes/interactive/theme/theme.ts"; function render(result: AgentToolResult, isError = false): string { const context = { args: { code: "" }, toolCallId: "call", invalidate: () => {}, lastComponent: undefined, state: {}, cwd: "/", executionStarted: true, argsComplete: true, isPartial: false, expanded: true, showImages: false, isError, } satisfies ToolRenderContext; const component = codemodeRenderers.renderResult?.( result, { expanded: true, isPartial: false }, theme, context, ) as Text; return stripVTControlCharacters(component.render(200).join("\n")) .split("\n") .map((line) => line.trimEnd()) .join("\n") .trim(); } describe("codemode renderer", () => { beforeAll(() => initTheme("dark")); it("hides the script header and shows the output", () => { const text = render({ content: [ { type: "text", text: "Script completed\nWall time 0.1 seconds\nOutput:\n" }, { type: "text", text: "hello" }, ], details: { calls: [{ id: "call/1", name: "read", args: '{"path":"a"}', status: "ok", durationMs: 5 }] }, }); expect(text).toBe('✓ read {"path":"a"} 5ms\n\nhello'); }); it("shows results without a header, such as rejected options", () => { const text = render( { content: [{ type: "text", text: "The @options line must be followed by JavaScript source" }], details: undefined, }, true, ); expect(text).toBe("The @options line must be followed by JavaScript source"); }); });