pi-mono/packages/coding-agent/test/codemode-renderer.test.ts
Armin Ronacher 2498ca492e refactor(agent,coding-agent): isolate codemode, tool search, and MCP from the core
The agent loop is back to main's tool execution: it only exports runToolCall(), honors isError results, and passes structuredContent through afterToolCall. Nested calls (ctx.executeTool, ids, events with parentToolCallId, the nestedCalls record) live in coding-agent and start on the first call.

AgentSession no longer knows exec or tool_search. Tools adjust the declared loadout through a generic prepareLoadout hook, and codemode and tool_search use it like any other tool.

exec and tool_search move to built-in extensions under src/extensions/ and register inactive (defaultActive: false). The CLI loads them with MCP; SDK sessions add createCodemodeExtension(), createToolSearchExtension(), and createMcpExtension(). Extensions gain pi.getSettings() and model catalog and classifier access on ModelRegistry.
2026-09-27 21:15:56 +02:00

59 lines
1.9 KiB
TypeScript

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<CodemodeToolDetails | undefined>, 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("exec renderer", () => {
beforeAll(() => initTheme("dark"));
it("hides the Codex-style 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 pragmas", () => {
const text = render(
{ content: [{ type: "text", text: "exec pragma must be followed by JavaScript source" }], details: undefined },
true,
);
expect(text).toBe("exec pragma must be followed by JavaScript source");
});
});