fix(coding-agent): custom tool collapsed/expanded rendering in HTML export (#1934)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 += `<div class="tool-header ansi-rendered">${rendered.callHtml}</div>`;
|
||||
@@ -974,20 +974,17 @@
|
||||
html += `<div class="tool-header"><span class="tool-name">${escapeHtml(name)}</span></div>`;
|
||||
}
|
||||
|
||||
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 += `<div class="tool-output expandable ansi-rendered" onclick="this.classList.toggle('expanded')">
|
||||
<div class="output-preview">${preview}<div class="expand-hint">... (${lines.length - 10} more lines)</div></div>
|
||||
<div class="output-full">${rendered.resultHtml}</div>
|
||||
</div>`;
|
||||
} else {
|
||||
html += `<div class="tool-output ansi-rendered">${rendered.resultHtml}</div>`;
|
||||
}
|
||||
if (rendered.resultHtmlCollapsed && rendered.resultHtmlExpanded && rendered.resultHtmlCollapsed !== rendered.resultHtmlExpanded) {
|
||||
// Both collapsed and expanded differ - render expandable section
|
||||
html += `<div class="tool-output expandable ansi-rendered" onclick="this.classList.toggle('expanded')">
|
||||
<div class="output-preview">${rendered.resultHtmlCollapsed}</div>
|
||||
<div class="output-full">${rendered.resultHtmlExpanded}</div>
|
||||
</div>`;
|
||||
} else if (rendered.resultHtmlExpanded) {
|
||||
// Only expanded exists (or collapsed is identical) - show directly
|
||||
html += `<div class="tool-output ansi-rendered">${rendered.resultHtmlExpanded}</div>`;
|
||||
} 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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user