fix(coding-agent): built-in tools work like extension tools
Export readToolDefinition / createReadToolDefinition and the equivalent built-in ToolDefinition APIs from @mariozechner/pi-coding-agent.
This commit is contained in:
@@ -13,9 +13,10 @@ 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;
|
||||
renderCall(toolCallId: string, toolName: string, args: unknown): string | undefined;
|
||||
/** Render a tool result to HTML. Returns collapsed/expanded or undefined if tool has no custom renderer. */
|
||||
renderResult(
|
||||
toolCallId: string,
|
||||
toolName: string,
|
||||
result: Array<{ type: string; text?: string; data?: string; mimeType?: string }>,
|
||||
details: unknown,
|
||||
@@ -191,7 +192,7 @@ function preRenderCustomTools(
|
||||
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
||||
for (const block of msg.content) {
|
||||
if (block.type === "toolCall" && !BUILTIN_TOOLS.has(block.name)) {
|
||||
const callHtml = toolRenderer.renderCall(block.name, block.arguments);
|
||||
const callHtml = toolRenderer.renderCall(block.id, block.name, block.arguments);
|
||||
if (callHtml) {
|
||||
renderedTools[block.id] = { callHtml };
|
||||
}
|
||||
@@ -205,7 +206,13 @@ 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 rendered = toolRenderer.renderResult(toolName, msg.content, msg.details, msg.isError || false);
|
||||
const rendered = toolRenderer.renderResult(
|
||||
msg.toolCallId,
|
||||
toolName,
|
||||
msg.content,
|
||||
msg.details,
|
||||
msg.isError || false,
|
||||
);
|
||||
if (rendered) {
|
||||
renderedTools[msg.toolCallId] = {
|
||||
...existing,
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
*/
|
||||
|
||||
import type { ImageContent, TextContent } from "@mariozechner/pi-ai";
|
||||
import type { Component } from "@mariozechner/pi-tui";
|
||||
import type { Theme } from "../../modes/interactive/theme/theme.js";
|
||||
import type { ToolDefinition } from "../extensions/types.js";
|
||||
import type { ToolDefinition, ToolRenderContext } from "../extensions/types.js";
|
||||
import { ansiLinesToHtml } from "./ansi-to-html.js";
|
||||
|
||||
export interface ToolHtmlRendererDeps {
|
||||
@@ -21,9 +22,10 @@ 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;
|
||||
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,
|
||||
@@ -40,27 +42,68 @@ export interface ToolHtmlRenderer {
|
||||
export function createToolHtmlRenderer(deps: ToolHtmlRendererDeps): ToolHtmlRenderer {
|
||||
const { getToolDefinition, theme, 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: process.cwd(),
|
||||
executionStarted: true,
|
||||
argsComplete: true,
|
||||
isPartial,
|
||||
expanded,
|
||||
showImages: false,
|
||||
isError,
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
renderCall(toolName: string, args: unknown): string | undefined {
|
||||
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);
|
||||
if (!component) {
|
||||
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 {
|
||||
// On error, return undefined to trigger JSON fallback
|
||||
// On error, return undefined so HTML export can fall back to structured result rendering
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
|
||||
renderResult(
|
||||
toolCallId: string,
|
||||
toolName: string,
|
||||
result: Array<{ type: string; text?: string; data?: string; mimeType?: string }>,
|
||||
details: unknown,
|
||||
@@ -85,28 +128,27 @@ export function createToolHtmlRenderer(deps: ToolHtmlRendererDeps): ToolHtmlRend
|
||||
agentToolResult,
|
||||
{ expanded: false, isPartial: false },
|
||||
theme,
|
||||
createRenderContext(toolCallId, renderedResultComponents.get(toolCallId), false, false, isError),
|
||||
);
|
||||
const collapsed = collapsedComponent ? ansiLinesToHtml(collapsedComponent.render(width)) : undefined;
|
||||
renderedResultComponents.set(toolCallId, collapsedComponent);
|
||||
const collapsed = ansiLinesToHtml(collapsedComponent.render(width));
|
||||
|
||||
// Render expanded
|
||||
const expandedComponent = toolDef.renderResult(
|
||||
agentToolResult,
|
||||
{ expanded: true, isPartial: false },
|
||||
theme,
|
||||
createRenderContext(toolCallId, renderedResultComponents.get(toolCallId), true, false, isError),
|
||||
);
|
||||
const expanded = expandedComponent ? ansiLinesToHtml(expandedComponent.render(width)) : undefined;
|
||||
|
||||
// Return collapsed only if it exists and differs from expanded
|
||||
if (!expanded) {
|
||||
return undefined;
|
||||
}
|
||||
renderedResultComponents.set(toolCallId, expandedComponent);
|
||||
const expanded = ansiLinesToHtml(expandedComponent.render(width));
|
||||
|
||||
return {
|
||||
...(collapsed && collapsed !== expanded ? { collapsed } : {}),
|
||||
expanded,
|
||||
};
|
||||
} catch {
|
||||
// On error, return undefined to trigger JSON fallback
|
||||
// On error, return undefined so HTML export can fall back to structured result rendering
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user