Registro de renderers de herramientas
pi-web-ui desacopla el render UI de las llamadas a herramientas: el execute se define en AgentTool, el render lo implementa la interfaz ToolRenderer, y se asocian por nombre de herramienta en el Map toolRenderers. renderTool es la entrada unificada: busca por nombre en la registry, y si hay hit usa el renderer custom, si no va a DefaultRenderer. Incluye renderers integrados para bash, extract_document, javascript_repl, etc., y el host puede inyectar renderers de negocio vía registerToolRenderer.
Responsabilidades
- Registry global: el Map
toolRenderersguarda instancias deToolRendererpor toolName. Verpackages/web-ui/src/tools/renderer-registry.ts:9-16. - Entrada unificada:
renderTool(toolName, params, result, isStreaming)primero consulta la registry, si hay lo usa, si no hace fallback aDefaultRenderer; también soportashowJsonModecomo flag global para forzar el render JSON por defecto. Verpackages/web-ui/src/tools/index.ts:28-44. - Helper de cabecera:
renderHeaderyrenderCollapsibleHeaderunifican el icono + texto + interacción de plegado en los tres estados (inprogress/complete/error). Verpackages/web-ui/src/tools/renderer-registry.ts:29-130. - Render inline:
ToolMessage.renderinvocarenderTooly, segúnisCustom, decide si envolver en tarjeta. Verpackages/web-ui/src/components/Messages.ts:258-276. - Renderers integrados:
extract-document.tsyjavascript-repl.tsse auto-registran al final del archivo conregisterToolRenderer(name, renderer). Verpackages/web-ui/src/tools/extract-document.ts:275ypackages/web-ui/src/tools/javascript-repl.ts:293.
Motivación de diseño
¿Por qué no añadir un método render directamente a AgentTool? Porque el execute de la herramienta se define en la capa pi-agent-core, lógica de backend; el render se define en la capa pi-web-ui, lógica de frontend. Las dos capas no se pueden acoplar: la misma herramienta bash se puede usar en CLI, TUI y web, el execute se reutiliza y el render se escribe aparte para cada una. El patrón registry permite que web-ui registre un renderer para cualquier nombre de herramienta, aunque la herramienta viva en otro paquete.
¿Por qué renderTool devuelve {content, isCustom} en vez de un TemplateResult? Porque algunos renderers quieren controlar todo el layout (por ejemplo la herramienta de artifacts se inlinea en el canvas, sin tarjeta); isCustom: true le dice a ToolMessage que no envuelva en wrapper de tarjeta y devuelva el content directo; isCustom: false envuelve en una tarjeta border rounded-md. Esta alternativa "contenido custom + envoltorio por defecto" da libertad al renderer y a la vez mantiene una apariencia uniforme para la mayoría de las herramientas.
¿Por qué extract-document y javascript-repl ponen renderer y tool en el mismo archivo? Porque son pareados: la forma de result.details la define la tool, y el renderer la consume. Mantenerlos en el mismo archivo reduce el riesgo de inconsistencia; el registerToolRenderer al final es "auto-registro": tools/index.ts hace import "./javascript-repl.js" para disparar el efecto secundario y el host no tiene que llamar a nada a mano.
Archivos clave
packages/web-ui/src/tools/types.ts:4-15—ToolRenderResulte interfazToolRenderer, define la firma de render.packages/web-ui/src/tools/renderer-registry.ts:9-23— MaptoolRenderers,registerToolRenderer,getToolRenderer.packages/web-ui/src/tools/renderer-registry.ts:29-63—renderHeader: iconos y texto de tres estados.packages/web-ui/src/tools/renderer-registry.ts:69-130—renderCollapsibleHeader: interacción de plegado con chevron.packages/web-ui/src/tools/index.ts:9-13— Registra el rendererbash, inicializadefaultRenderer, dos imports de efecto secundario.packages/web-ui/src/tools/index.ts:28-44— EntradarenderTool, gestionashowJsonModey fallback.packages/web-ui/src/tools/extract-document.ts:36-181—createExtractDocumentTool, fetch directo con fallback a proxy.packages/web-ui/src/tools/extract-document.ts:190-272—extractDocumentRenderer: render de tres estados (con result, sólo params, sin params).packages/web-ui/src/tools/javascript-repl.ts:128-195—createJavaScriptReplTool, ejecuta código en un iframe sandbox y devuelve archivos.packages/web-ui/src/tools/javascript-repl.ts:200-290—javascriptReplRenderer: bloque de código plegable + salida de consola + tile de attachment.packages/web-ui/src/components/Messages.ts:258-276—ToolMessage.renderinvocarenderTooly, segúnisCustom, decide si envolver en tarjeta.
renderTool es la entrada unificada, con lógica muy corta:
// packages/web-ui/src/tools/index.ts:28-44
export function renderTool(
toolName: string,
params: any | undefined,
result: ToolResultMessage | undefined,
isStreaming?: boolean,
): ToolRenderResult {
if (showJsonMode) {
return defaultRenderer.render(params, result, isStreaming);
}
const renderer = getToolRenderer(toolName);
if (renderer) {
return renderer.render(params, result, isStreaming);
}
return defaultRenderer.render(params, result, isStreaming);
}El auto-registro al final de javascript-repl.ts es el patrón "import es registro":
// packages/web-ui/src/tools/javascript-repl.ts:292-293
// Auto-register the renderer
registerToolRenderer(javascriptReplTool.name, javascriptReplRenderer);Flujo de datos
Ruta de render de una llamada a herramienta, desde AssistantMessage hasta que el renderer devuelve:
Límites y fallos
- Herramienta sin renderer registrado:
renderToolcae aDefaultRenderer, que muestra params y result como JSON, sin dejar la UI en blanco. Verpackages/web-ui/src/tools/index.ts:39-43. - En streaming aún no llega result: el renderer recibe
result === undefinedy muestrarenderHeader(state="inprogress", ...)con spinner + texto. Verpackages/web-ui/src/tools/extract-document.ts:249-264. - Llamada a herramienta abortada:
ToolMessagesintetiza unToolResultMessageisError placeholder en modoaborted, y el renderer lo muestra como error. Verpackages/web-ui/src/components/Messages.ts:248-258. - Degradación ante CORS: si
extract_documentfalla en fetch directo yisCorsErrorlo detecta y haycorsProxyUrlconfigurado, reintenta por proxy. Verpackages/web-ui/src/tools/extract-document.ts:103-135. - Toggle de depuración showJsonMode: con
setShowJsonMode(true), todas las herramientas se fuerzan al JSON view deDefaultRenderer, útil para inspeccionar resultados. Verpackages/web-ui/src/tools/index.ts:21-23. - Registro duplicado de renderer:
registerToolRendererusaMap.sety sobrescribe; el último gana, sin error. El host puede inyectar un renderer custom para sobrescribir el integrado.
Resumen
renderer-registry desacopla execute y render de la herramienta: renderTool consulta el Map por toolName, si hay hit usa el renderer custom, si no va a DefaultRenderer. extract_document y javascript_repl se auto-registran con el patrón "import es registro". La entrada de render en componentes de render de mensajes ToolMessage, y el ensamblaje de instancias de herramienta en elemento top-level ChatPanel.