Imported as a linear vendor commit so forge rebases do not replay the upstream root tree. git-subtree-dir: third_party/pi git-subtree-split: 71023aec1546d6d677a65669c5665c0927ce0dd0
95 lines
3.3 KiB
TypeScript
95 lines
3.3 KiB
TypeScript
import { type Component, TuiMainScreen } from "@earendil-works/pi-tui";
|
|
import { Type } from "typebox";
|
|
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
import { VirtualTerminal } from "../../tui/test/virtual-terminal.ts";
|
|
import type { ToolDefinition } from "../src/core/extensions/types.ts";
|
|
import { createEditToolDefinition } from "../src/core/tools/edit.ts";
|
|
import { BashExecutionComponent } from "../src/modes/interactive/components/bash-execution.ts";
|
|
import { CompactionSummaryMessageComponent } from "../src/modes/interactive/components/compaction-summary-message.ts";
|
|
import { ToolExecutionComponent } from "../src/modes/interactive/components/tool-execution.ts";
|
|
import { initTheme } from "../src/modes/interactive/theme/theme.ts";
|
|
import { stripAnsi } from "../src/utils/ansi.ts";
|
|
|
|
const ui = new TuiMainScreen(new VirtualTerminal(80, 24));
|
|
|
|
const tool: ToolDefinition = {
|
|
name: "custom_tool",
|
|
label: "custom_tool",
|
|
description: "custom tool",
|
|
parameters: Type.Any(),
|
|
execute: async () => ({ content: [{ type: "text", text: "ok" }], details: {} }),
|
|
};
|
|
|
|
type OutputPaddedComponent = Component & { setOutputPad(outputPad: number): void };
|
|
|
|
/** Text lines without ANSI codes or trailing fill. Blank lines and full-width borders are skipped. */
|
|
function renderLines(component: Component): string[] {
|
|
return component
|
|
.render(60)
|
|
.map((line) => stripAnsi(line).trimEnd())
|
|
.filter((line) => /[\w$(]/.test(line));
|
|
}
|
|
|
|
function createTool(definition: ToolDefinition | undefined, outputPad: number): ToolExecutionComponent {
|
|
const component = new ToolExecutionComponent("custom_tool", "id", {}, { outputPad }, definition, ui, "/");
|
|
component.updateResult({ content: [{ type: "text", text: "ok" }], isError: false });
|
|
return component;
|
|
}
|
|
|
|
const components: Array<{ name: string; create: (outputPad: number) => OutputPaddedComponent }> = [
|
|
{
|
|
name: "bash execution",
|
|
create: (outputPad) => {
|
|
const component = new BashExecutionComponent("pwd", ui, false, outputPad);
|
|
component.appendOutput("/tmp");
|
|
component.setComplete(1, false);
|
|
return component;
|
|
},
|
|
},
|
|
{ name: "tool execution", create: (outputPad) => createTool(tool, outputPad) },
|
|
{ name: "tool execution without a definition", create: (outputPad) => createTool(undefined, outputPad) },
|
|
{
|
|
name: "self-rendered edit result",
|
|
create: (outputPad) => {
|
|
const component = new ToolExecutionComponent(
|
|
"edit",
|
|
"id",
|
|
{ path: "file.txt", edits: [{ oldText: "old", newText: "new" }] },
|
|
{ outputPad },
|
|
createEditToolDefinition("/"),
|
|
ui,
|
|
"/",
|
|
);
|
|
component.updateResult({ content: [{ type: "text", text: "Could not find old text" }], isError: true });
|
|
return component;
|
|
},
|
|
},
|
|
{
|
|
name: "compaction summary",
|
|
create: (outputPad) =>
|
|
new CompactionSummaryMessageComponent(
|
|
{ role: "compactionSummary", summary: "summary", tokensBefore: 10, timestamp: 0 },
|
|
undefined,
|
|
outputPad,
|
|
),
|
|
},
|
|
];
|
|
|
|
describe("outputPad", () => {
|
|
beforeAll(() => {
|
|
initTheme("dark");
|
|
});
|
|
|
|
afterAll(() => {
|
|
ui.stop();
|
|
});
|
|
|
|
test.each(components)("$name renders at outputPad 0 and 1", ({ create }) => {
|
|
const component = create(0);
|
|
const lines = renderLines(component);
|
|
expect(lines.filter((line) => line.startsWith(" "))).toEqual([]);
|
|
component.setOutputPad(1);
|
|
expect(renderLines(component)).toEqual(lines.map((line) => ` ${line}`));
|
|
});
|
|
});
|