Tool-Renderer-Registry
pi-web-ui entkoppelt das UI-Rendering von Tool-Aufrufen: Das execute des Tools liegt im AgentTool, das Rendering in einer ToolRenderer-Implementierung; die beiden werden über den Tool-Namen in der toolRenderers-Map gepaart. renderTool ist der einheitliche Einstiegspunkt, der in der Registry nach dem Tool-Namen nachschlägt — bei Treffer wird der Custom-Renderer genutzt, sonst fällt es auf DefaultRenderer zurück. Eingebaute Renderer für bash, extract_document, javascript_repl usw. sind enthalten; der Host kann über registerToolRenderer fachliche Renderer injizieren.
Zuständigkeiten
- Globale Registry: Die Map
toolRenderersspeichertToolRenderer-Instanzen nach toolName. Siehepackages/web-ui/src/tools/renderer-registry.ts:9-16. - Einheitlicher Einstieg:
renderTool(toolName, params, result, isStreaming)schlägt zuerst in der Registry nach; Treffer nutzt sie, sonst Fallback aufDefaultRenderer. Außerdem erzwingt der globale SchaltershowJsonModedas Default-JSON-Rendering. Siehepackages/web-ui/src/tools/index.ts:28-44. - Header-Hilfsfunktionen:
renderHeaderundrenderCollapsibleHeadervereinheitlichen Icon, Text und Interaktion für die drei Zustände (inprogress/complete/error). Siehepackages/web-ui/src/tools/renderer-registry.ts:29-130. - Inline-Rendering:
ToolMessage.renderruftrenderToolund entscheidet nachisCustom, ob eine Karte drumherum gelegt wird. Siehepackages/web-ui/src/components/Messages.ts:258-276. - Eingebaute Renderer:
extract-document.tsundjavascript-repl.tsregistrieren am Dateiende perregisterToolRenderer(name, renderer)automatisch. Siehepackages/web-ui/src/tools/extract-document.ts:275undpackages/web-ui/src/tools/javascript-repl.ts:293.
Designmotivation
Warum kommt nicht einfach eine render-Methode auf AgentTool? Weil das execute des Tools auf der pi-agent-core-Ebene definiert ist (Backend-Logik), das Rendering auf der pi-web-ui-Ebene (Frontend-Logik). Die beiden Schichten dürfen nicht gekoppelt werden — dasselbe bash-Tool kann in CLI, TUI und Web genutzt werden; execute wird wiederverwendet, das Rendering wird pro UI neu geschrieben. Das Registry-Muster erlaubt web-ui, für beliebige Tool-Namen einen Renderer zu registrieren, auch wenn das Tool in einem anderen Package liegt.
Warum liefert renderTool {content, isCustom} statt eines TemplateResult? Weil manche Renderer ihr Layout selbst kontrollieren wollen (etwa das artifacts-Tool, das inline auf die Leinwand zeichnet und keine Karten-Umhüllung will): isCustom: true signalisiert ToolMessage, keine Karten-Wrapper drumherum zu legen, sondern nur den content zurückzugeben; isCustom: false wickelt den content in eine border rounded-md-Karte. Diese „Custom-Inhalt vs. Default-Hülle"-Entscheidung gibt dem Renderer einerseits Freiheit und sorgt andererseits dafür, dass die meisten Tools ein einheitliches Aussehen behalten.
Warum liegen in extract-document und javascript-repl der Renderer und das Tool in derselben Datei? Weil sie gepaart sind: Die Form von tool.result.details wird durch das Tool definiert und vom Renderer direkt konsumiert. Die gemeinsame Datei verringert das Risiko von Inkonsistenz; das registerToolRenderer am Dateiende ist „automatische Registrierung" — tools/index.ts löst sie per import "./javascript-repl.js" als Side-Effect aus, der Host muss nicht manuell aufrufen.
Wichtige Dateien
packages/web-ui/src/tools/types.ts:4-15—ToolRenderResultundToolRenderer-Interface, legen die render-Signatur fest.packages/web-ui/src/tools/renderer-registry.ts:9-23—toolRenderers-Map,registerToolRenderer,getToolRenderer.packages/web-ui/src/tools/renderer-registry.ts:29-63—renderHeader: drei Zustände, Icon und Text.packages/web-ui/src/tools/renderer-registry.ts:69-130—renderCollapsibleHeader: mit chevron, interaktiv auf- und zuklappbar.packages/web-ui/src/tools/index.ts:9-13— registriert denbash-Renderer, initialisiertdefaultRenderer, zwei Side-Effect-Imports.packages/web-ui/src/tools/index.ts:28-44—renderTool-Einstieg, behandeltshowJsonModeund Fallback.packages/web-ui/src/tools/extract-document.ts:36-181—createExtractDocumentTool, fällt bei direktem fetch-Fehler auf den Proxy zurück.packages/web-ui/src/tools/extract-document.ts:190-272—extractDocumentRenderer: drei Zustände — mit result / nur params / ohne params.packages/web-ui/src/tools/javascript-repl.ts:128-195—createJavaScriptReplTool, führt Code in einer Sandbox-iframe aus und liefert Dateien zurück.packages/web-ui/src/tools/javascript-repl.ts:200-290—javascriptReplRenderer: aufklappbarer Code-Block + Konsolenausgabe + Anhang-Tile.packages/web-ui/src/components/Messages.ts:258-276—ToolMessage.renderruftrenderToolund entscheidet nachisCustom, ob die Karte drumherum gelegt wird.
renderTool ist der einheitliche Einstieg, die Logik ist kurz:
// 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);
}Die automatische Registrierung am Ende von javascript-repl.ts ist das „import als Registrierung"-Muster:
// packages/web-ui/src/tools/javascript-repl.ts:292-293
// Auto-register the renderer
registerToolRenderer(javascriptReplTool.name, javascriptReplRenderer);Datenfluss
Der Render-Pfad eines Tool-Aufrufs vom AssistantMessage bis zum zurückgegebenen Renderer-Ergebnis:
Randbedingungen und Fehler
- Tool ohne registrierten Renderer:
renderToolfällt aufDefaultRendererzurück, der params und result als JSON anzeigt; kein leeres Ergebnis. Siehepackages/web-ui/src/tools/index.ts:39-43. - Während Streaming ist result noch nicht da: Der Renderer bekommt
result === undefinedund zeichnet überrenderHeader(state="inprogress", ...)Spinner und Text. Siehepackages/web-ui/src/tools/extract-document.ts:249-264. - aborted-Tool-Aufruf:
ToolMessagesynthetisiert imaborted-Fall ein isError-ToolResultMessageals Platzhalter; der Renderer rendert den Fehlerzustand. Siehepackages/web-ui/src/components/Messages.ts:248-258. - CORS-Fehler-Degradation: Wenn bei
extract_documentder direkte fetch scheitert,isCorsErrorgreift undcorsProxyUrlgesetzt ist, wird über den Proxy erneut versucht. Siehepackages/web-ui/src/tools/extract-document.ts:103-135. - showJsonMode-Debug-Schalter: Nach
setShowJsonMode(true)geht jeder Tool-Aufruf erzwungen über die JSON-Ansicht desDefaultRenderer, was das Debuggen von Tool-Rückgaben erleichtert. Siehepackages/web-ui/src/tools/index.ts:21-23. - Renderer mehrfach registriert:
registerToolRendererüberschreibt perMap.setohne Fehler; ein später registrierter Renderer gewinnt. Der Host kann so einen eingebauten Renderer überschreiben.
Zusammenfassung
renderer-registry entkoppelt das execute eines Tools vom Rendering: renderTool schlägt nach toolName in der Map nach, bei Treffer wird der Custom-Renderer genutzt, sonst fällt es auf DefaultRenderer zurück. extract_document und javascript_repl registrieren sich per „import als Registrierung" selbst. Der Render-Einstieg liegt im ToolMessage der Nachrichten-Render-Komponenten, die Verdrahtung der Tool-Instanzen in ChatPanel Top-Level-Element.