From e77e51e7ba8d60f42ca1dd680f15f57c08619904 Mon Sep 17 00:00:00 2001 From: Aliou Diallo Date: Sat, 7 Mar 2026 19:01:33 +0100 Subject: [PATCH] fix(coding-agent): custom tool collapsed/expanded rendering in HTML export (#1934) --- packages/coding-agent/CHANGELOG.md | 3 ++ .../src/core/export-html/index.ts | 14 ++++---- .../src/core/export-html/template.js | 25 ++++++-------- .../src/core/export-html/tool-renderer.ts | 34 ++++++++++++++----- 4 files changed, 48 insertions(+), 28 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 59216cc3..dfdab207 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,9 @@ ## [Unreleased] +### Fixed +- Fixed custom tool collapsed/expanded rendering in HTML exports. Custom tools that define different collapsed vs expanded displays now render correctly in exported HTML, with expandable sections when both states differ and direct display when only expanded exists ([#1934](https://github.com/badlogic/pi-mono/pull/1934) by [@aliou](https://github.com/aliou)) + ## [0.57.0] - 2026-03-07 ### New Features diff --git a/packages/coding-agent/src/core/export-html/index.ts b/packages/coding-agent/src/core/export-html/index.ts index a961fd78..a4cb5fe6 100644 --- a/packages/coding-agent/src/core/export-html/index.ts +++ b/packages/coding-agent/src/core/export-html/index.ts @@ -14,19 +14,20 @@ import { SessionManager } from "../session-manager.js"; export interface ToolHtmlRenderer { /** Render a tool call to HTML. Returns undefined if tool has no custom renderer. */ renderCall(toolName: string, args: unknown): string | undefined; - /** Render a tool result to HTML. Returns undefined if tool has no custom renderer. */ + /** Render a tool result to HTML. Returns collapsed/expanded or undefined if tool has no custom renderer. */ renderResult( toolName: string, result: Array<{ type: string; text?: string; data?: string; mimeType?: string }>, details: unknown, isError: boolean, - ): string | undefined; + ): { collapsed?: string; expanded?: string } | undefined; } /** Pre-rendered HTML for a custom tool call and result */ interface RenderedToolHtml { callHtml?: string; - resultHtml?: string; + resultHtmlCollapsed?: string; + resultHtmlExpanded?: string; } export interface ExportOptions { @@ -204,11 +205,12 @@ function preRenderCustomTools( // Only render if we have a pre-rendered call OR it's not a built-in tool const existing = renderedTools[msg.toolCallId]; if (existing || !BUILTIN_TOOLS.has(toolName)) { - const resultHtml = toolRenderer.renderResult(toolName, msg.content, msg.details, msg.isError || false); - if (resultHtml) { + const rendered = toolRenderer.renderResult(toolName, msg.content, msg.details, msg.isError || false); + if (rendered) { renderedTools[msg.toolCallId] = { ...existing, - resultHtml, + resultHtmlCollapsed: rendered.collapsed, + resultHtmlExpanded: rendered.expanded, }; } } diff --git a/packages/coding-agent/src/core/export-html/template.js b/packages/coding-agent/src/core/export-html/template.js index d97bb04d..e170d7a6 100644 --- a/packages/coding-agent/src/core/export-html/template.js +++ b/packages/coding-agent/src/core/export-html/template.js @@ -966,7 +966,7 @@ default: { // Check for pre-rendered custom tool HTML const rendered = renderedTools?.[call.id]; - if (rendered?.callHtml || rendered?.resultHtml) { + if (rendered?.callHtml || rendered?.resultHtmlCollapsed || rendered?.resultHtmlExpanded) { // Custom tool with pre-rendered HTML from TUI renderer if (rendered.callHtml) { html += `
${rendered.callHtml}
`; @@ -974,20 +974,17 @@ html += `
${escapeHtml(name)}
`; } - if (rendered.resultHtml) { - // Apply same truncation as built-in tools (10 lines) - const lines = rendered.resultHtml.split('\n'); - if (lines.length > 10) { - const preview = lines.slice(0, 10).join('\n'); - html += ``; - } else { - html += `
${rendered.resultHtml}
`; - } + if (rendered.resultHtmlCollapsed && rendered.resultHtmlExpanded && rendered.resultHtmlCollapsed !== rendered.resultHtmlExpanded) { + // Both collapsed and expanded differ - render expandable section + html += ``; + } else if (rendered.resultHtmlExpanded) { + // Only expanded exists (or collapsed is identical) - show directly + html += `
${rendered.resultHtmlExpanded}
`; } else if (result) { - // Fallback to JSON for result if no pre-rendered HTML + // No pre-rendered result HTML - fallback to JSON const output = getResultText(); if (output) html += formatExpandableOutput(output, 10); } diff --git a/packages/coding-agent/src/core/export-html/tool-renderer.ts b/packages/coding-agent/src/core/export-html/tool-renderer.ts index d1b86d3c..b1f241ff 100644 --- a/packages/coding-agent/src/core/export-html/tool-renderer.ts +++ b/packages/coding-agent/src/core/export-html/tool-renderer.ts @@ -22,13 +22,13 @@ export interface ToolHtmlRendererDeps { export interface ToolHtmlRenderer { /** Render a tool call to HTML. Returns undefined if tool has no custom renderer. */ renderCall(toolName: string, args: unknown): string | undefined; - /** Render a tool result to HTML. Returns undefined if tool has no custom renderer. */ + /** Render a tool result to collapsed/expanded HTML. Returns undefined if tool has no custom renderer. */ renderResult( toolName: string, result: Array<{ type: string; text?: string; data?: string; mimeType?: string }>, details: unknown, isError: boolean, - ): string | undefined; + ): { collapsed?: string; expanded?: string } | undefined; } /** @@ -65,7 +65,7 @@ export function createToolHtmlRenderer(deps: ToolHtmlRendererDeps): ToolHtmlRend result: Array<{ type: string; text?: string; data?: string; mimeType?: string }>, details: unknown, isError: boolean, - ): string | undefined { + ): { collapsed?: string; expanded?: string } | undefined { try { const toolDef = getToolDefinition(toolName); if (!toolDef?.renderResult) { @@ -80,13 +80,31 @@ export function createToolHtmlRenderer(deps: ToolHtmlRendererDeps): ToolHtmlRend isError, }; - // Always render expanded, client-side will apply truncation - const component = toolDef.renderResult(agentToolResult, { expanded: true, isPartial: false }, theme); - if (!component) { + // Render collapsed + const collapsedComponent = toolDef.renderResult( + agentToolResult, + { expanded: false, isPartial: false }, + theme, + ); + const collapsed = collapsedComponent ? ansiLinesToHtml(collapsedComponent.render(width)) : undefined; + + // Render expanded + const expandedComponent = toolDef.renderResult( + agentToolResult, + { expanded: true, isPartial: false }, + theme, + ); + const expanded = expandedComponent ? ansiLinesToHtml(expandedComponent.render(width)) : undefined; + + // Return collapsed only if it exists and differs from expanded + if (!expanded) { return undefined; } - const lines = component.render(width); - return ansiLinesToHtml(lines); + + return { + ...(collapsed && collapsed !== expanded ? { collapsed } : {}), + expanded, + }; } catch { // On error, return undefined to trigger JSON fallback return undefined;