fix(coding-agent): custom tool collapsed/expanded rendering in HTML export (#1934)
This commit is contained in:
@@ -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