Registre des renderers d'outils
pi-web-ui découple le rendu UI des appels d'outils : le execute de l'outil est défini dans AgentTool, le rendu est implémenté par l'interface ToolRenderer, et les deux sont appariés par nom d'outil dans une Map toolRenderers. renderTool est le point d'entrée unique : il recherche dans le registry par nom d'outil — s'il trouve, il utilise le renderer personnalisé, sinon il tombe sur DefaultRenderer. Les renderers intégrés bash, extract_document, javascript_repl, etc. sont disponibles, et l'hôte peut aussi injecter des rendus métier via registerToolRenderer.
Responsabilités
- Registry global : la Map
toolRenderersstocke des instances deToolRendererpar toolName, voirpackages/web-ui/src/tools/renderer-registry.ts:9-16. - Point d'entrée unique :
renderTool(toolName, params, result, isStreaming)cherche d'abord dans le registry, l'utilise si trouvé, sinon fallback surDefaultRenderer. Il gère aussi un switch globalshowJsonModepour forcer le rendu JSON par défaut, voirpackages/web-ui/src/tools/index.ts:28-44. - Helpers d'en-tête :
renderHeaderetrenderCollapsibleHeadersont deux helpers qui unifient l'icône, le texte et l'interaction repli pour trois états (inprogress/complete/error), voirpackages/web-ui/src/tools/renderer-registry.ts:29-130. - Rendu en ligne :
ToolMessage.renderappellerenderTool, puis décide selonisCustoms'il faut envelopper dans une carte, voirpackages/web-ui/src/components/Messages.ts:258-276. - Renderers intégrés :
extract-document.tsetjavascript-repl.tss'enregistrent automatiquement viaregisterToolRenderer(name, renderer)à la fin du fichier, voirpackages/web-ui/src/tools/extract-document.ts:275etpackages/web-ui/src/tools/javascript-repl.ts:293.
Motivations de design
Pourquoi ne pas ajouter une méthode render directement sur AgentTool ? Parce que le execute de l'outil est défini dans la couche pi-agent-core, logique backend ; le rendu est défini dans pi-web-ui, logique frontend. Les deux couches ne doivent pas être couplées — un même outil bash peut être utilisé dans trois UI (CLI, TUI, web) : execute est réutilisé, le rendu s'écrit à part. Le pattern registry permet à web-ui d'enregistrer un renderer pour n'importe quel nom d'outil, même si l'outil est dans un autre package.
Pourquoi renderTool retourne-t-il {content, isCustom} plutôt qu'un TemplateResult ? Parce que certains renderers veulent contrôler tout le layout (par exemple l'outil artifacts s'affiche en ligne sur le canevas, sans carte autour) ; isCustom: true indique à ToolMessage de ne pas envelopper dans une carte et de renvoyer directement le content ; isCustom: false enveloppe dans une carte border rounded-md. Cette alternative « contenu personnalisé + coque par défaut » laisse au renderer sa liberté tout en garantissant une apparence cohérente pour la plupart des outils.
Pourquoi extract-document et javascript-repl mettent-ils renderer et tool dans le même fichier ? Parce qu'ils vont de pair : la forme de result.details est définie par l'outil, et le renderer la consomme directement. Les maintenir dans le même fichier réduit le risque d'incohérence ; le registerToolRenderer à la fin du fichier agit comme un auto-enregistrement — tools/index.ts utilise import "./javascript-repl.js" pour déclencher l'effet de bord et compléter l'enregistrement, sans que l'hôte n'ait à appeler quoi que ce soit manuellement.
Fichiers clés
packages/web-ui/src/tools/types.ts:4-15—ToolRenderResultet l'interfaceToolRenderer, signature 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: icônes et texte pour les trois états.packages/web-ui/src/tools/renderer-registry.ts:69-130—renderCollapsibleHeader: interaction repli avec chevron.packages/web-ui/src/tools/index.ts:9-13— enregistre le rendererbash, initialisedefaultRenderer, importe les deux imports à effet de bord.packages/web-ui/src/tools/index.ts:28-44— point d'entréerenderTool, gèreshowJsonModeet le fallback.packages/web-ui/src/tools/extract-document.ts:36-181—createExtractDocumentTool: fallback proxy en cas d'échec du fetch direct.packages/web-ui/src/tools/extract-document.ts:190-272—extractDocumentRenderer: rendu en trois états (avec result / seulement params / sans params).packages/web-ui/src/tools/javascript-repl.ts:128-195—createJavaScriptReplTool: exécute le code dans un iframe sandbox et renvoie des fichiers.packages/web-ui/src/tools/javascript-repl.ts:200-290—javascriptReplRenderer: bloc de code repliable + sortie console + tuile de pièce jointe.packages/web-ui/src/components/Messages.ts:258-276—ToolMessage.renderappellerenderTool, décide selonisCustoms'il faut envelopper dans une carte.
renderTool est le point d'entrée unique, la logique est très courte :
// 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);
}L'auto-enregistrement à la fin de javascript-repl.ts illustre le pattern « importer, c'est enregistrer » :
// packages/web-ui/src/tools/javascript-repl.ts:292-293
// Auto-register the renderer
registerToolRenderer(javascriptReplTool.name, javascriptReplRenderer);Flux de données
Le chemin de rendu d'un appel d'outil, depuis AssistantMessage jusqu'au renderer :
Limites et cas d'échec
- Outil sans renderer enregistré :
renderTooltombe surDefaultRenderer, qui affiche params et result en JSON, pour éviter un vide, voirpackages/web-ui/src/tools/index.ts:39-43. - Result pas encore arrivé pendant le streaming : le renderer reçoit
result === undefinedet utiliserenderHeader(state="inprogress", ...)pour afficher un spinner et un texte, voirpackages/web-ui/src/tools/extract-document.ts:249-264. - Appel d'outil interrompu (aborted) :
ToolMessagesynthétise unToolResultMessageisError en placeholder quandabortedest true ; le renderer l'affiche en état error, voirpackages/web-ui/src/components/Messages.ts:248-258. - Dégradation sur échec CORS :
extract_documenttente un fetch direct ; en cas d'échec, siisCorsErrormatche et qu'uncorsProxyUrlest configuré, on réessaie via le proxy, voirpackages/web-ui/src/tools/extract-document.ts:103-135. - Switch de debug showJsonMode : après
setShowJsonMode(true), tous les outils passent en force par la vue JSON deDefaultRenderer, pratique pour inspecter les retours d'outils, voirpackages/web-ui/src/tools/index.ts:21-23. - Enregistrement de renderer en doublon :
registerToolRendererfait unMap.setqui écrase ; c'est le dernier enregistré qui gagne, sans erreur — l'hôte peut donc remplacer une implémentation intégrée par la sienne.
Pour résumer
renderer-registry découple le execute des outils de leur rendu : renderTool cherche dans la Map par toolName, utilise le renderer personnalisé si trouvé, sinon DefaultRenderer. extract_document et javascript_repl s'enregistrent automatiquement via le pattern « importer, c'est enregistrer ». Le point d'entrée du rendu est ToolMessage dans Composants de rendu des messages ; l'assemblage des instances d'outils se fait dans ChatPanel, élément de haut niveau.