Spaces:
Sleeping
Sleeping
| 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"; | |
| 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 detail; | |
| } | |
| if (!args || typeof args !== "object") { | |
| return ""; | |
| } | |
| try { | |
| return 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(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); | |
| } | |
| } | |
| } | |