/** * Presentation for the codemode tool. * * The script call and the script output (header stripped, output preview, full-output path and * timer) come from codemode's own renderers, so they stay identical to the host. Between them this * display lists every recorded nested call with the same per-tool call summary and result renderer * that a direct call uses, followed by its bounded saved result. Nothing here executes a tool or * changes the model-visible result. */ import { Container, Spacer, Text, type Component, } from "@earendil-works/pi-tui"; import { formatSize, type CodemodeToolDetails, type Theme, type ToolRenderers, type ToolRenderResultOptions, } from "@selesai/code"; import { toRecord } from "./tool-metadata.js"; import type { ToolDisplayConfig } from "./types.js"; export type CodemodeNestedCall = CodemodeToolDetails["calls"][number]; export type CodemodeNestedResult = NonNullable; type CodemodeNestedResultPreview = Extract; type CodemodeScriptBudgetResult = Extract; export interface CodemodeDisplayDeps { getConfig: () => ToolDisplayConfig; /** Codemode's own renderers: the highlighted script, output preview, full-output path and timer. */ getHostRenderers: () => ToolRenderers | undefined; /** Raw pi-tool-display renderers for a nested tool, or undefined when it is not owned. */ getNestedToolRenderers: (toolName: string) => ToolRenderers | undefined; /** Whether the live write capture still has the previous file content for this nested call. */ hasLiveWriteMeta?: (toolCallId: string, state: Record) => boolean; /** Whether saved details carry a valid `toolDisplay` write record (not `{}` or unrelated details). */ hasWriteDisplayDetails?: (details: unknown) => boolean; } interface NestedCallState { id?: string; callComponent?: Component; resultComponent?: Component; state?: Record; } interface CodemodeDisplayState { hostState: Record; hostCallComponent?: Component; hostResultComponent?: Component; nested: NestedCallState[]; } const DISPLAY_STATE_KEY = "__piToolDisplayCodemode"; /** Wide enough that a call formatter never wraps before the surrounding Text wraps to the terminal. */ const FORMAT_WIDTH = 100_000; /** Probe width for deciding whether a nested result renderer produced any visible output. */ const RESULT_PROBE_WIDTH = 120; function getDisplayState(context: { state?: unknown }): CodemodeDisplayState { const carrier = context.state as Record | undefined; const existing = carrier?.[DISPLAY_STATE_KEY] as CodemodeDisplayState | undefined; if (existing) { return existing; } const state: CodemodeDisplayState = { hostState: {}, nested: [] }; if (carrier) { carrier[DISPLAY_STATE_KEY] = state; } return state; } function getNestedCallState(display: CodemodeDisplayState, index: number, id: string): NestedCallState { const existing = display.nested[index]; if (existing && existing.id === id) { return existing; } const state: NestedCallState = { id, state: {} }; display.nested[index] = state; return state; } type ParsedArgs = { ok: true; value: unknown } | { ok: false }; function parseArgs(call: CodemodeNestedCall): ParsedArgs { if (call.argsTruncated || !call.args) { return { ok: false }; } try { return { ok: true, value: JSON.parse(call.args) }; } catch { return { ok: false }; } } function statusIcon(call: CodemodeNestedCall, theme: Theme): string { switch (call.status) { case "running": return theme.fg("warning", "…"); case "ok": return theme.fg("success", "✓"); case "error": return theme.fg("error", "✗"); case "cancelled": return theme.fg("muted", "⊘"); } } function formatDuration(ms: number | undefined): string { if (ms === undefined) return ""; return ms < 1000 ? `${Math.round(ms)}ms` : `${(ms / 1000).toFixed(1)}s`; } /** Cents for larger amounts, two significant digits for the fractions of a cent classifier calls cost. */ function formatCost(cost: number): string { return `$${cost >= 0.01 ? cost.toFixed(2) : cost.toPrecision(2)}`; } function renderComponentText(component: Component, width = FORMAT_WIDTH): string { try { return component .render(width) .map((line) => line.replace(/\s+$/u, "")) .join("\n") .trim(); } catch { return ""; } } function fallbackCallLine(call: CodemodeNestedCall, theme: Theme): string { const name = theme.fg("toolTitle", theme.bold(call.name)); const args = call.args.trim(); return args ? `${name} ${theme.fg("muted", args)}` : name; } function formatNestedCallLine( call: CodemodeNestedCall, parsed: ParsedArgs, nestedRenderers: ToolRenderers | undefined, nestedState: NestedCallState, theme: Theme, context: Record, ): string { let summary = ""; if (nestedRenderers?.renderCall && parsed.ok) { try { const nestedContext = { ...context, args: parsed.value, toolCallId: call.id, lastComponent: nestedState.callComponent, state: nestedState.state, isPartial: false, expanded: false, argsComplete: false, executionStarted: false, isError: call.status === "error" || call.status === "cancelled", durationMs: call.durationMs, }; const component = nestedRenderers.renderCall(parsed.value, theme, nestedContext as never); nestedState.callComponent = component; summary = renderComponentText(component); } catch { summary = ""; } } if (!summary) { summary = fallbackCallLine(call, theme); } let line = `${statusIcon(call, theme)} ${summary}`; const duration = formatDuration(call.durationMs); if (duration) line += ` ${theme.fg("dim", duration)}`; if (call.cost) line += ` ${theme.fg("dim", formatCost(call.cost))}`; return line; } function getOmittedDetailsBytes(details: unknown): number | undefined { const record = toRecord(details); return record.omitted === true && typeof record.bytes === "number" ? record.bytes : undefined; } function isScriptBudgetResult(result: CodemodeNestedResult): result is CodemodeScriptBudgetResult { return (result as { reason?: unknown }).reason === "script-budget"; } function indentText(text: string): string { return text .split("\n") .map((line) => ` ${line}`) .join("\n"); } function renderGenericPreview( preview: CodemodeNestedResultPreview, expanded: boolean, config: ToolDisplayConfig, theme: Theme, ): string { if (!preview.text) { return theme.fg("muted", "↳ (no output)"); } const lines = preview.text.split("\n"); const limit = expanded ? Math.min(lines.length, Math.max(0, config.expandedPreviewMaxLines) || lines.length) : Math.max(1, config.previewLines); const shown = lines.slice(0, limit); const remaining = lines.length - shown.length; let text = shown .map((line) => theme.fg(preview.isError ? "error" : "toolOutput", line)) .join("\n"); if (remaining > 0) { text += `\n${theme.fg("muted", `... (${remaining} more lines)`)}`; } return text; } function buildNestedResultContext( call: CodemodeNestedCall, parsed: ParsedArgs, nestedState: NestedCallState, context: Record, ) { return { ...context, args: parsed.ok ? parsed.value : undefined, toolCallId: call.id, lastComponent: nestedState.resultComponent, state: nestedState.state, executionStarted: false, argsComplete: true, isPartial: false, isError: false, durationMs: call.durationMs, }; } function addPreviewMarkers( component: Container, preview: CodemodeNestedResultPreview, detailsOmitted: number | undefined, theme: Theme, extra: readonly string[], ): void { const markers: string[] = []; if (preview.truncated) { const shownLines = preview.text ? preview.text.split("\n").length : 0; markers.push( `preview truncated: showing ${shownLines}/${preview.textLines} lines, ${formatSize(preview.textBytes)} full`, ); } if (preview.images > 0) { markers.push(`${preview.images} image${preview.images === 1 ? "" : "s"} not saved`); } if (detailsOmitted !== undefined) { markers.push(`details omitted (${formatSize(detailsOmitted)})`); } markers.push(...extra); for (const marker of markers) { component.addChild(new Text(theme.fg("warning", ` ${marker}`), 0, 0)); } } function renderNestedResult( component: Container, call: CodemodeNestedCall, parsed: ParsedArgs, nestedState: NestedCallState, options: ToolRenderResultOptions, theme: Theme, context: Record, deps: CodemodeDisplayDeps, ): void { if (!call.result) { return; } if (isScriptBudgetResult(call.result)) { component.addChild(new Text(theme.fg("muted", " result omitted (script budget)"), 0, 0)); return; } const preview = call.result as CodemodeNestedResultPreview; const detailsOmitted = getOmittedDetailsBytes(preview.details); const config = deps.getConfig(); const nestedRenderers = call.name === "codemode" ? undefined : deps.getNestedToolRenderers(call.name); const liveWriteMeta = call.name === "write" && deps.hasLiveWriteMeta?.(call.id, nestedState.state ?? {}) === true; // A write diff needs the previous content. Without live capture or a persisted record, showing // the new content as a complete overwrite diff would invent data, so fall back to the saved // result text and say why. const hasPersistedWriteDetails = call.name === "write" && detailsOmitted === undefined && deps.hasWriteDisplayDetails?.(preview.details) === true; const writeDiffAvailable = call.name !== "write" || liveWriteMeta || hasPersistedWriteDetails; const editDetailsUnavailable = call.name === "edit" && detailsOmitted !== undefined; const useNestedRenderer = Boolean(nestedRenderers?.renderResult) && writeDiffAvailable && !editDetailsUnavailable; const result = { content: preview.text ? [{ type: "text" as const, text: preview.text }] : [], details: detailsOmitted === undefined ? preview.details : undefined, isError: preview.isError, }; let rendered: Component | undefined; let rendererFailed = false; if (useNestedRenderer) { try { const nestedContext = buildNestedResultContext(call, parsed, nestedState, context); rendered = nestedRenderers?.renderResult?.( result as never, { expanded: options.expanded, isPartial: false }, theme, { ...nestedContext, isError: preview.isError } as never, ); nestedState.resultComponent = rendered; } catch { rendered = undefined; rendererFailed = true; } } let visibleResult = false; const renderedText = rendered ? renderComponentText(rendered, RESULT_PROBE_WIDTH) : ""; if (rendered && renderedText.trim()) { component.addChild(rendered); visibleResult = true; } else if (preview.isError && preview.text.trim()) { // An output mode can hide a successful result; an error stays visible. component.addChild(new Text(indentText(theme.fg("error", preview.text)), 0, 0)); visibleResult = true; } else if (options.expanded && useNestedRenderer && !rendererFailed) { // A hidden output mode draws nothing even when expanded. The user asked for this row to be // expanded, so reveal the bounded saved preview and label it instead of showing nothing. component.addChild(new Text(theme.fg("muted", "saved result preview:"), 0, 0)); component.addChild(new Text(renderGenericPreview(preview, true, config, theme), 0, 0)); visibleResult = true; } else if (!useNestedRenderer || rendererFailed) { const generic = renderGenericPreview(preview, options.expanded, config, theme); if (generic) { component.addChild(new Text(generic, 0, 0)); visibleResult = true; } } const extra: string[] = []; if (!writeDiffAvailable) { extra.push("write diff unavailable: previous content was not saved"); } if (editDetailsUnavailable) { extra.push("edit diff unavailable: details omitted"); } if (visibleResult) { addPreviewMarkers(component, preview, detailsOmitted, theme, extra); } } function renderExpandedArgs( component: Container, call: CodemodeNestedCall, parsed: ParsedArgs, theme: Theme, ): void { if (call.argsTruncated) { const savedBytes = Buffer.byteLength(call.args, "utf8"); component.addChild( new Text( theme.fg( "warning", ` args truncated: saved ${formatSize(savedBytes)} of ${formatSize(call.argsBytes ?? savedBytes)}`, ), 0, 0, ), ); } if (!call.args) { return; } const display = parsed.ok ? JSON.stringify(parsed.value, null, 2) : call.args; if (display) { component.addChild(new Text(theme.fg("muted", display), 2, 0)); } } function renderCostTotal(component: Container, calls: readonly CodemodeNestedCall[], theme: Theme): void { const priced = calls.filter((call) => call.cost); if (priced.length <= 1) { return; } const total = priced.reduce((sum, call) => sum + (call.cost ?? 0), 0); component.addChild(new Text(theme.fg("muted", `Model calls: ${formatCost(total)}`), 0, 0)); } function renderHostResult( component: Container, result: Parameters>[0], options: ToolRenderResultOptions, theme: Theme, context: Record, display: CodemodeDisplayState, deps: CodemodeDisplayDeps, ): void { const host = deps.getHostRenderers(); if (!host?.renderResult) { return; } const details = toRecord(result.details); const hostResult = host.renderResult( { ...result, // Codemode's own renderer draws the nested calls and the cost total; this display already // did, so the host renderer gets the script-only tail. details: { ...details, calls: [] }, } as never, options, theme, { ...context, lastComponent: display.hostResultComponent, state: display.hostState, } as never, ) as Component; display.hostResultComponent = hostResult; component.addChild(hostResult); } export function createCodemodeToolRenderers(deps: CodemodeDisplayDeps): ToolRenderers { return { renderCall(args, theme, context) { const display = getDisplayState(context as never); const outer = (context.lastComponent as Container | undefined) ?? new Container(); outer.clear(); const host = deps.getHostRenderers(); if (host?.renderCall) { const hostContext = { ...(context as unknown as Record), lastComponent: display.hostCallComponent, state: display.hostState, }; const hostCall = host.renderCall(args, theme, hostContext as never) as Component; display.hostCallComponent = hostCall; outer.addChild(hostCall); } else { outer.addChild(new Text((theme as Theme).fg("toolTitle", (theme as Theme).bold("codemode")), 0, 0)); } return outer; }, renderResult(result, options, theme, context) { const display = getDisplayState(context as never); const outer = (context.lastComponent as Container | undefined) ?? new Container(); outer.clear(); const details = (result.details ?? { calls: [] }) as CodemodeToolDetails; const calls = Array.isArray(details.calls) ? details.calls : []; const baseContext = context as unknown as Record; if (calls.length > 0) { outer.addChild(new Spacer(1)); } calls.forEach((call, index) => { const nestedState = getNestedCallState(display, index, call.id); const parsed = parseArgs(call); const nestedRenderers = call.name === "codemode" ? undefined : deps.getNestedToolRenderers(call.name); const line = formatNestedCallLine(call, parsed, nestedRenderers, nestedState, theme as Theme, baseContext); outer.addChild(new Text(line, 0, 0)); if (options.expanded) { if (call.error) { outer.addChild(new Text(indentText((theme as Theme).fg("error", call.error)), 0, 0)); } renderExpandedArgs(outer, call, parsed, theme as Theme); } renderNestedResult( outer, call, parsed, nestedState, options, theme as Theme, baseContext, deps, ); }); if (calls.length > 0) { renderCostTotal(outer, calls, theme as Theme); } renderHostResult(outer, result as never, options, theme as Theme, baseContext, display, deps); return outer; }, }; }