Skip to content

Registre des renderers d'outils

源码版本v0.73.1

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

  1. Registry global : la Map toolRenderers stocke des instances de ToolRenderer par toolName, voir packages/web-ui/src/tools/renderer-registry.ts:9-16.
  2. Point d'entrée unique : renderTool(toolName, params, result, isStreaming) cherche d'abord dans le registry, l'utilise si trouvé, sinon fallback sur DefaultRenderer. Il gère aussi un switch global showJsonMode pour forcer le rendu JSON par défaut, voir packages/web-ui/src/tools/index.ts:28-44.
  3. Helpers d'en-tête : renderHeader et renderCollapsibleHeader sont deux helpers qui unifient l'icône, le texte et l'interaction repli pour trois états (inprogress/complete/error), voir packages/web-ui/src/tools/renderer-registry.ts:29-130.
  4. Rendu en ligne : ToolMessage.render appelle renderTool, puis décide selon isCustom s'il faut envelopper dans une carte, voir packages/web-ui/src/components/Messages.ts:258-276.
  5. Renderers intégrés : extract-document.ts et javascript-repl.ts s'enregistrent automatiquement via registerToolRenderer(name, renderer) à la fin du fichier, voir packages/web-ui/src/tools/extract-document.ts:275 et packages/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

renderTool est le point d'entrée unique, la logique est très courte :

typescript
// 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 » :

typescript
// 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é : renderTool tombe sur DefaultRenderer, qui affiche params et result en JSON, pour éviter un vide, voir packages/web-ui/src/tools/index.ts:39-43.
  • Result pas encore arrivé pendant le streaming : le renderer reçoit result === undefined et utilise renderHeader(state="inprogress", ...) pour afficher un spinner et un texte, voir packages/web-ui/src/tools/extract-document.ts:249-264.
  • Appel d'outil interrompu (aborted) : ToolMessage synthétise un ToolResultMessage isError en placeholder quand aborted est true ; le renderer l'affiche en état error, voir packages/web-ui/src/components/Messages.ts:248-258.
  • Dégradation sur échec CORS : extract_document tente un fetch direct ; en cas d'échec, si isCorsError matche et qu'un corsProxyUrl est configuré, on réessaie via le proxy, voir packages/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 de DefaultRenderer, pratique pour inspecter les retours d'outils, voir packages/web-ui/src/tools/index.ts:21-23.
  • Enregistrement de renderer en doublon : registerToolRenderer fait un Map.set qui é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.