Skip to content

ツールレンダラーレジストリ

源码版本v0.73.1

pi-web-ui はツール呼び出しの UI 描画を切り離す。ツールの execute は AgentTool で定義し、描画は ToolRenderer インターフェースの実装が担う。両者はツール名で toolRenderers Map にペアリングされる。renderTool が統一入口で、ツール名でレジストリ (registry) を引き、ヒットすればカスタム renderer を、外れれば DefaultRenderer に流れる。組み込みで bashextract_documentjavascript_repl などのレンダラを持ち、host は registerToolRenderer で業務専用の描画も注入できる。

責務

  1. グローバル registry:toolRenderers Map が toolName → ToolRenderer インスタンスを保持する。packages/web-ui/src/tools/renderer-registry.ts:9-16 参照。
  2. 統一入口:renderTool(toolName, params, result, isStreaming) はまず registry を引き、あればそれを使い、無ければ DefaultRenderer にフォールバックする。showJsonMode のグローバルスイッチで強制的にデフォルトの JSON 描画にすることも可能。packages/web-ui/src/tools/index.ts:28-44 参照。
  3. ヘッダー補助:renderHeaderrenderCollapsibleHeader の 2 ヘルパーが、3 状態(inprogress/complete/error)のアイコン + 文言 + 折りたたみ操作を統一する。packages/web-ui/src/tools/renderer-registry.ts:29-130 参照。
  4. インライン描画:ToolMessage.renderrenderTool を呼んだあと、isCustom に応じてカード wrapper の有無を決める。packages/web-ui/src/components/Messages.ts:258-276 参照。
  5. 組み込みレンダラ:extract-document.tsjavascript-repl.ts はファイル末尾で registerToolRenderer(name, renderer) を呼び、自動登録する。packages/web-ui/src/tools/extract-document.ts:275packages/web-ui/src/tools/javascript-repl.ts:293 参照。

設計動機

なぜ AgentTool に直接 render メソッドを生やさないのか。ツールの execute は pi-agent-core 層で定義されバックエンドのロジック、描画は pi-web-ui 層で定義されフロントエンドのロジックだ。両層を結合してはいけない。同じ bash ツールが CLI、TUI、web の 3 種の UI で使われるかもしれず、execute は再利用し、描画は各 UI で別々に書く。registry パターンにより、web-ui は任意のツール名にレンダラーを登録でき、ツール自身が別パッケージにあっても構わない。

なぜ renderToolTemplateResult ではなく {content, isCustom} を返すのか。一部のレンダラーはレイアウト全体を自分で制御したいからだ(例えば artifacts ツールはキャンバスにインラインし、カード枠が要らない)。isCustom: true なら ToolMessage はカード wrapper を被せず、content をそのまま返す。isCustom: false なら border rounded-md のカードで包む。この「カスタム内容 + デフォルト外殻」の二者択一により、レンダラーに自由を与えつつ、大半のツールは統一した見た目を保てる。

なぜ extract-documentjavascript-repl は renderer と tool を同じファイルに置くのか。両者はペアだからだ。tool の result.details の形は tool 側で決まり、renderer がそれを直接消費する。同じファイルで保守すれば不整合のリスクが減る。ファイル末尾の registerToolRenderer は「自動登録」で、tools/index.tsimport "./javascript-repl.js" を通じて副作用による登録を起こし、host は手動で呼ぶ必要がない。

主要ファイル

renderTool は統一入口で、ロジックは短い:

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);
}

javascript-repl.ts 末尾の自動登録は「import 即登録」パターンだ:

typescript
// packages/web-ui/src/tools/javascript-repl.ts:292-293
// Auto-register the renderer
registerToolRenderer(javascriptReplTool.name, javascriptReplRenderer);

データフロー

ツール呼び出しの描画経路は、AssistantMessage が発火してから renderer が返すまで:

境界と失敗

  • ツールに renderer 未登録:renderToolDefaultRenderer にフォールバックし、JSON で params と result を表示する。空白にはならない。packages/web-ui/src/tools/index.ts:39-43 参照。
  • ストリーミング中の result 未着:renderer は result === undefined を受け取り、renderHeader(state="inprogress", ...) で spinner + 文言を示す。packages/web-ui/src/tools/extract-document.ts:249-264 参照。
  • aborted ツール呼び出し:ToolMessageaborted 時に合成した isError の ToolResultMessage で穴埋めし、renderer は error 状態として描画する。packages/web-ui/src/components/Messages.ts:248-258 参照。
  • CORS 失敗のグレードダウン:extract_document は直結 fetch 失敗時、isCorsError がヒットし corsProxyUrl が設定されていればプロキシで再試行する。packages/web-ui/src/tools/extract-document.ts:103-135 参照。
  • showJsonMode デバッグスイッチ:setShowJsonMode(true) ですべてのツールが強制的に DefaultRenderer の JSON ビューに切り替わる。tool の戻り値を追うのに便利。packages/web-ui/src/tools/index.ts:21-23 参照。
  • renderer の重複登録:registerToolRenderer は単に Map.set で上書きし、後登録が勝つ。エラーは出ない。host がカスタム renderer を注入すれば組み込み実装を上書きできる。

まとめ

renderer-registry はツールの execute と描画を切り離す。renderTool が toolName で Map を引き、ヒットすればカスタム renderer、外れれば DefaultRenderer に流す。extract_documentjavascript_repl は「import 即登録」で自動登録する。描画の入口は メッセージ描画コンポーネントToolMessage にあり、ツールインスタンスの組み立ては ChatPanel 最上位要素 で行われる。