4.12 工具 UI 渲染模式
每个工具不仅定义执行逻辑,还自带完整的 UI 渲染能力。这是"渲染即工具"设计理念的体现——工具最了解自己的输入输出应该如何展示。
渲染方法一览
每个工具可以定义 4-6 个 React 渲染方法:
| 方法 | 用途 | 触发时机 |
|---|---|---|
renderToolUseMessage() | 渲染工具调用过程 | 模型发出 tool_use 时 |
renderToolResultMessage() | 渲染工具执行结果 | 工具完成执行后 |
renderToolUseRejectedMessage() | 渲染权限拒绝信息 | 用户拒绝权限请求时 |
renderToolUseErrorMessage() | 渲染错误信息 | 工具执行出错时 |
renderGroupedToolUse() | 合并渲染多个同类调用 | 同类型工具连续调用时 |
renderGroupedToolUse:批量合并渲染
当模型连续调用多个相同类型的工具(如连续 5 个 FileReadTool),逐个渲染会占据大量终端空间。renderGroupedToolUse() 方法将这些调用合并为一个紧凑的视图:
📖 Read 5 files: src/agent.ts, src/tools.ts, src/cli.ts, ...而非:
📖 Read src/agent.ts
📖 Read src/tools.ts
📖 Read src/cli.ts
📖 Read src/prompt.ts
📖 Read src/session.ts这不仅节省屏幕空间,也让用户更容易理解模型的意图——"它在批量阅读文件"而非"它在一个一个读文件"。
backfillObservableInput:输入回填
工具输入在到达 UI 之前会经过 backfillObservableInput() 处理。这个方法在不修改发送给 API 的实际输入的前提下,为 UI 观察者扩展输入信息。
典型例子:模型调用 FileEditTool 时只传入相对路径 src/agent.ts,但 UI 展示时需要完整路径 /home/user/project/src/agent.ts。backfillObservableInput() 将 CWD 补全到路径中供 UI 使用,但 API 侧的 tool_use block 保持原样。
为什么不直接修改 API 输入?因为 prompt cache 稳定性。API 请求中的消息内容是缓存键的一部分,任何修改都会导致缓存失效。backfillObservableInput() 只影响 UI 展示层,API 层的消息原封不动。
具体示例:FileEditTool 的 UI 渲染
FileEditTool 的 UI.tsx 根据操作类型渲染不同的视觉效果:
- 创建文件:标题显示"Create",渲染完整文件内容并附带语法高亮
- 编辑文件:标题显示"Update",渲染结构化的 diff 补丁,新增行标绿、删除行标红
- 错误状态:显示匹配失败的上下文,帮助用户理解为什么
old_string未能在文件中找到匹配
每个工具的 UI.tsx 都遵循同样的模式:导入工具的类型定义,实现相应的 render* 方法,返回 React 节点。这种规范化结构让新工具的 UI 开发有模板可循。
动手实践:在 claude-code-from-scratch 的
src/tools.ts中,~325 行代码实现了 7 个核心工具。对比本章的 60+ 工具体系,这是理解"最小可用工具集"的最佳起点。参见教程 第 2 章:工具系统。