# Step 69: 工具调用 UI 打磨

一句话导读:把工具结果从“一行成功提示”升级成可折叠工具卡,并引入 marked 做完整 Markdown 渲染。


# 一、这一步做了什么(What)

step69 打磨 Web 前端:tool_result 事件增加 argSummary 和 preview,前端渲染为 <details> 工具卡;Markdown 从手写正则换成第三方库 marked;同时加流式光标、权限卡片样式和缓存控制。

# 二、面试官视角:为什么要做?(Why)

面试题:工具调用已经能显示成功/失败了,为什么还要做可折叠工具卡?

因为 agent 的可解释性很大程度来自工具过程。用户需要知道模型跑了什么命令、读了哪个文件、输出大概是什么;但又不能让长输出淹没对话。

一行提示 工具卡
只知道 Bash 成功 能看到命令摘要和输出预览
长结果会刷屏 默认折叠,需要时展开
难排错 输入/输出上下文可查

# 三、原理:它是怎么工作的(How)

QueryEngine.ts 在产生 tool_result 时带上摘要:

yield {
  type: "tool_result",
  name,
  ok,
  elapsed,
  argSummary,
  preview,
};
1
2
3
4
5
6
7
8

前端用 <details>:

<details class="tool-card">
  <summary>✓ Bash (31ms) echo hi</summary>
  <pre>hi</pre>
</details>
1
2
3
4

Markdown 改为服务端托管 marked:

if (path === "/vendor/marked.js") {
  // 返回 node_modules/marked/... 的浏览器构建
}
1
2
3

浏览器里:

function md(raw) {
  return marked.parse(raw, { breaks: true });
}
1
2
3

# 四、深入追问(面试常见 follow-up)

Q:为什么不把完整工具输出都塞进标题?
A:标题要可扫描,只放摘要;完整输出进折叠内容,避免长日志破坏聊天节奏。

Q:marked 有什么安全问题?
A:marked 默认不消毒 HTML。生产环境要配 DOMPurify;教学版本地 demo 明确标注该缺口。

Q:为什么要本地托管 marked,而不是 CDN?
A:离线可用、版本可控,也符合工具型应用少依赖外网的原则。

Q:为什么 <summary> 还会踩坑?
A:浏览器对 summary 默认样式和自定义布局有差异。强制 display:flex 能避免折叠标题变成空条。

# 五、踩坑 / 设计权衡

README 记录了几个真实排障:旧进程占端口导致一直访问旧服务、/?v=69 因路径精确匹配落到兜底、浏览器缓存旧 HTML。这些都不是 AI 问题,是 Web 工程日常。

# 六、与真实源码的对照

我们的实现 Claude Code 源码
<details> 工具卡 完整工具消息组件
argSummary/preview 结构化工具输入输出、diff、复制
marked 渲染 Markdown Markdown + 高亮 + 消毒
简单视觉打磨 设计系统、主题、复杂交互状态

# 七、一句话总结

工具 UI 的目标是“过程可见但不喧宾夺主”:摘要用于扫描,详情用于排错,Markdown 交给成熟库。

# 下一节预告

下一篇是 step70:抽出 createCore(),让终端和 Web 共用同一套工具装配,解决功能漂移。