import { Box, Container, Markdown, Spacer, Text } from "@mariozechner/pi-tui" ;
import { formatToolDetail, resolveToolDisplay } from "../../agents/tool-display.js" ;
import { markdownTheme, theme } from "../theme/theme.js" ;
import { sanitizeRenderableText } from "../tui-formatters.js" ;
type ToolResultContent = {
type?: string;
text?: string;
mimeType?: string;
bytes?: number;
omitted?: boolean ;
};
type ToolResult = {
content?: ToolResultContent[];
details?: Record<string, unknown>;
};
const PREVIEW_LINES = 12 ;
function formatArgs(toolName: string, args: unknown): string {
const display = resolveToolDisplay({ name: toolName, args });
const detail = formatToolDetail(display);
if (detail) {
return sanitizeRenderableText(detail);
}
if (!args || typeof args !== "object" ) {
return "" ;
}
try {
return sanitizeRenderableText(JSON.stringify(args));
} catch {
return "" ;
}
}
function extractText(result?: ToolResult): string {
if (!result?.content) {
return "" ;
}
const lines: string[] = [];
for (const entry of result.content) {
if (entry.type === "text" && entry.text) {
lines.push(sanitizeRenderableText(entry.text));
} else if (entry.type === "image" ) {
const mime = entry.mimeType ?? "image" ;
const size = entry.bytes ? ` ${Math.round(entry.bytes / 1024 )}kb` : "" ;
const omitted = entry.omitted ? " (omitted)" : "" ;
lines.push(`[${mime}${size}${omitted}]`);
}
}
return lines.join("\n" ).trim();
}
export class ToolExecutionComponent extends Container {
private box: Box;
private header: Text;
private argsLine: Text;
private output: Markdown;
private toolName: string;
private args: unknown;
private result?: ToolResult;
private expanded = false ;
private isError = false ;
private isPartial = true ;
constructor(toolName: string, args: unknown) {
super ();
this .toolName = toolName;
this .args = args;
this .box = new Box(1 , 1 , (line) => theme.toolPendingBg(line));
this .header = new Text("" , 0 , 0 );
this .argsLine = new Text("" , 0 , 0 );
this .output = new Markdown("" , 0 , 0 , markdownTheme, {
color: (line) => theme.toolOutput(line),
});
this .addChild(new Spacer(1 ));
this .addChild(this .box);
this .box.addChild(this .header);
this .box.addChild(this .argsLine);
this .box.addChild(this .output);
this .refresh();
}
setArgs(args: unknown) {
this .args = args;
this .refresh();
}
setExpanded(expanded: boolean ) {
this .expanded = expanded;
this .refresh();
}
setResult(result: ToolResult | undefined, opts?: { isError?: boolean }) {
this .result = result;
this .isPartial = false ;
this .isError = Boolean (opts?.isError);
this .refresh();
}
setPartialResult(result: ToolResult | undefined) {
this .result = result;
this .isPartial = true ;
this .refresh();
}
private refresh() {
const bg = this .isPartial
? theme.toolPendingBg
: this .isError
? theme.toolErrorBg
: theme.toolSuccessBg;
this .box.setBgFn((line) => bg(line));
const display = resolveToolDisplay({
name: this .toolName,
args: this .args,
});
const title = `${display.emoji} ${display.label}${this .isPartial ? " (running)" : "" }`;
this .header.setText(theme.toolTitle(theme.bold(title)));
const argLine = formatArgs(this .toolName, this .args);
this .argsLine.setText(argLine ? theme.dim(argLine) : theme.dim(" " ));
const raw = extractText(this .result);
const text = raw || (this .isPartial ? "…" : "" );
if (!this .expanded && text) {
const lines = text.split("\n" );
const preview =
lines.length > PREVIEW_LINES ? `${lines.slice(0 , PREVIEW_LINES).join("\n" )}\n…` : text;
this .output.setText(preview);
} else {
this .output.setText(text);
}
}
}
Messung V0.5 in Prozent C=99 H=98 G=98
¤ Dauer der Verarbeitung: 0.4 Sekunden
¤
*© Formatika GbR, Deutschland