feat(coding-agent): Custom tool export rendering in export
Hello! Follows https://github.com/badlogic/pi-mono/issues/669
Opening this as a draft PR so we can iterate on it and get feedback if people want to try it.
Turns out the implementation is not too far from the POC. Here's how it works:
- We create a
ToolHtmlRendererwith everything needed to render a tool (how to retrieve a tool definition and the theme) - We loop on the session entries, storing the non builtin-tool calls and their result. then we call their
renderCallandrenderResultfunctions to get the TUI components. Finally, we convert them to html and store them in a map - Then at html generation, we embed that map in the html template.
- "Client" side, when the data is parsed, it looks up if the tool call is in the map. If yes, is renders the HTML. Otherwise, it renders the JSON like we currently have.
Changes from the POC:
- Instead of calling twice the rendering for collapsed and expanded, we call it once and collapse after 10 lines.
- We no longer pass the full
ExtensionRunnerto the renderer: less coupling might make it easier to do have theexportsubcommand also render the custom tools.
Below in the Opus recap, some addition design decisions that I found cleaner while planning the feature.
Example session: https://buildwithpi.ai/session/?a5b49300f48efdbe4aaf8197c35487c2
Implementation session: https://buildwithpi.ai/session/?b3a82b64fb192cefb3b36ee18e98c779
Opus 4.5 summary
Custom Tool HTML Export
Summary
When using /share or /export commands, custom tools (extensions) now render using their TUI component renderers (renderCall/renderResult) instead of showing raw JSON. Colors and formatting are preserved via ANSI-to-HTML conversion.
What was implemented
New files:
-
packages/coding-agent/src/core/export-html/ansi-to-html.ts- Converts ANSI escape codes to HTML with inline styles. Supports standard colors (30-37, 40-47), bright colors (90-97, 100-107), 256-color palette, RGB true color, and styles (bold, dim, italic, underline). -
packages/coding-agent/src/core/export-html/tool-renderer.ts- Factory function that creates a renderer for custom tools. Takes dependencies (getToolDefinition,theme, optionalwidth) for loose coupling and testability.
Modified files:
-
runner.ts- AddedgetToolDefinition()method to look up tool definitions by name -
index.ts- AddedToolHtmlRendererinterface,RenderedToolHtmltype,renderedToolsfield in SessionData, and pre-rendering logic inexportSessionToHtml() -
template.js- Updated default case inrenderToolCall()to use pre-rendered HTML with 10-line truncation and expand/collapse -
agent-session.ts- Creates tool renderer when extension runner exists and passes to export
Design decisions
-
Single expanded render + client-side truncation - Custom tools are rendered once with
expanded: true. The client applies 10-line truncation (matching built-in tools likeread). Simpler than rendering both collapsed and expanded versions. -
Loose coupling - Tool renderer takes a deps object rather than ExtensionRunner directly. More testable and flexible.
-
Line-based HTML output - Each ANSI line wrapped in
<div class="ansi-line">for proper layout and easy line counting for truncation.
What was left out (explicitly out of scope)
-
CLI
pi export <file>support - No ExtensionRunner available in CLI context. Custom tools fall back to JSON rendering. -
CSS styling for ANSI output - Uses inline styles for portability. Could add
.ansi-renderedCSS customization later. -
Configurable truncation threshold - Hardcoded to 10 lines to match built-in tools.
-
Image support in custom tool results - Built-in tools handle images separately.
Testing
Manual testing required:
- Use an extension with custom
renderCall/renderResult(e.g.,examples/extensions/truncated-tool.ts) - Use the tool in a conversation
- Run
/exportor/share - Verify HTML shows custom rendering with colors preserved