Something went wrong. Try again.
fork of prime agent with changes i want but are not upstreamable probqbly (and some fixes)
Something went wrong. Try again.
5.2 kB · 167 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168/** * Tool HTML renderer for custom tools in HTML export. * * Renders custom tool calls and results to HTML by invoking their TUI renderers * and converting the ANSI output to HTML. */
import type { ImageContent, TextContent } from "@earendil-works/pi-ai";import type { Component } from "@earendil-works/pi-tui";import type { Theme } from "../../modes/interactive/theme/theme.js";import type { ToolDefinition, ToolRenderContext } from "../extensions/types.js";import { ansiLinesToHtml } from "./ansi-to-html.js";
export interface ToolHtmlRendererDeps { /** Function to look up tool definition by name */ getToolDefinition: (name: string) => ToolDefinition | undefined; /** Theme for styling */ theme: Theme; /** Working directory for render context */ cwd: string; /** Terminal width for rendering (default: 100) */ width?: number;}
export interface ToolHtmlRenderer { /** Render a tool call to HTML. Returns undefined if tool has no custom renderer. */ renderCall(toolCallId: string, toolName: string, args: unknown): string | undefined; /** Render a tool result to collapsed/expanded HTML. Returns undefined if tool has no custom renderer. */ renderResult( toolCallId: string, toolName: string, result: Array<{ type: string; text?: string; data?: string; mimeType?: string }>, details: unknown, isError: boolean, ): { collapsed?: string; expanded?: string } | undefined;}
/** * Create a tool HTML renderer. * * The renderer looks up tool definitions and invokes their renderCall/renderResult * methods, converting the resulting TUI Component output (ANSI) to HTML. */const ANSI_ESCAPE_REGEX = /\x1b\[[\d;]*m/g;
function isBlankRenderedLine(line: string): boolean { return line.replace(ANSI_ESCAPE_REGEX, "").trim().length === 0;}
function trimRenderedResultLines(lines: string[]): string[] { let start = 0; let end = lines.length; while (start < end && isBlankRenderedLine(lines[start])) start++; while (end > start && isBlankRenderedLine(lines[end - 1])) end--; return lines.slice(start, end);}
export function createToolHtmlRenderer(deps: ToolHtmlRendererDeps): ToolHtmlRenderer { const { getToolDefinition, theme, cwd, width = 100 } = deps;
const renderedCallComponents = new Map<string, Component>(); const renderedResultComponents = new Map<string, Component>(); const renderedStates = new Map<string, any>(); const renderedArgs = new Map<string, unknown>();
const getState = (toolCallId: string): any => { let state = renderedStates.get(toolCallId); if (!state) { state = {}; renderedStates.set(toolCallId, state); } return state; };
const createRenderContext = ( toolCallId: string, lastComponent: Component | undefined, expanded: boolean, isPartial: boolean, isError: boolean, ): ToolRenderContext => { return { args: renderedArgs.get(toolCallId), toolCallId, invalidate: () => {}, lastComponent, state: getState(toolCallId), cwd, executionStarted: true, argsComplete: true, isPartial, expanded, showImages: false, includeImageDimensions: true, isError, }; };
return { renderCall(toolCallId: string, toolName: string, args: unknown): string | undefined { try { renderedArgs.set(toolCallId, args); const toolDef = getToolDefinition(toolName); if (!toolDef?.renderCall) { return undefined; }
const component = toolDef.renderCall( args, theme, createRenderContext(toolCallId, renderedCallComponents.get(toolCallId), false, true, false), ); renderedCallComponents.set(toolCallId, component); const lines = component.render(width); return ansiLinesToHtml(lines); } catch { return undefined; } },
renderResult( toolCallId: string, toolName: string, result: Array<{ type: string; text?: string; data?: string; mimeType?: string }>, details: unknown, isError: boolean, ): { collapsed?: string; expanded?: string } | undefined { try { const toolDef = getToolDefinition(toolName); if (!toolDef?.renderResult) { return undefined; }
const agentToolResult = { content: result as (TextContent | ImageContent)[], details, isError, };
const collapsedComponent = toolDef.renderResult( agentToolResult, { expanded: false, isPartial: false }, theme, createRenderContext(toolCallId, renderedResultComponents.get(toolCallId), false, false, isError), ); renderedResultComponents.set(toolCallId, collapsedComponent); const collapsed = ansiLinesToHtml(trimRenderedResultLines(collapsedComponent.render(width)));
const expandedComponent = toolDef.renderResult( agentToolResult, { expanded: true, isPartial: false }, theme, createRenderContext(toolCallId, renderedResultComponents.get(toolCallId), true, false, isError), ); renderedResultComponents.set(toolCallId, expandedComponent); const expanded = ansiLinesToHtml(trimRenderedResultLines(expandedComponent.render(width)));
return { ...(collapsed && collapsed !== expanded ? { collapsed } : {}), expanded, }; } catch { return undefined; } }, };}