本章总览
Message 是 Agent 的血液:API 只认 messages[],UI 也只渲染 messages。本章教你五种消息类型各自含义、何时产生、如何被 normalize 后发给 Claude。
总览图
学完本章你应该能
- 区分 User / Assistant / ToolResult / System / CompactBoundary
- 理解 messages.ts 为何是 5500+ 行的核心
- 知道 tool_result 必须紧跟 tool_use 的 API 约束
核心概念(先读懂这些)
为什么需要多种 Message
UserMessage 承载人类输入;AssistantMessage 存模型输出(含 tool_use blocks);ToolResultMessage 把执行结果喂回模型;SystemMessage 做本地通知;CompactBoundaryMessage 标记压缩边界,防止旧历史干扰新上下文。
normalizeMessagesForAPI 的意义
内存里的 Message 结构为了 UI 便利可能更富;发给 API 前必须 strip 多余字段、合并 block、修复顺序。这是 Agent 稳定性的关键一步,很多 bug 出在这里。
渲染与存储是同一份数据吗
基本是:components/messages/* 按类型渲染。会话持久化到 ~/.claude/projects/ 的也是 Message 序列。改 Message 形状会影响 UI、API、磁盘三处。
建议学习步骤
- 对照表格理解每种类型
- 在源码中搜索 createUserMessage
- 思考:一次 Bash 工具调用产生几条 Message
常见误区
注意
ToolResult 不是 AssistantMessage——角色不同,API 会拒收混排错误
Message 类型关系(类图)
一次 Bash 调用的消息流
messages.ts 数据管道
Message 类型一览
下表是 Claude Code 内部对话消息的五种核心类型。读源码时看到 Message 相关类型,先回来对照此表,判断它在「对话生命周期」的哪一环。
| 类型 | 说明 | 渲染组件 |
|---|---|---|
| UserMessage | 用户输入 | UserMessage.tsx |
| AssistantMessage | Claude 回复 | AssistantMessage.tsx |
| ToolResultMessage | 工具执行结果 | ToolResultMessage.tsx |
| SystemMessage | 系统通知 | SystemMessage.tsx |
| CompactBoundaryMessage | 压缩边界标记 | CompactBoundaryMessage.tsx |
逐类型详解
UserMessage — 用户的意图入口
无论是键盘输入、粘贴的图片、还是 SDK 传入的 prompt,最终都封装为 UserMessage。它可能包含纯文本,也可能含 content blocks(如图片)。教学要点:UserMessage 进入 messages[] 后不会直接发给 API,往往还要经过 slash 命令拦截、附件处理等预处理。
AssistantMessage — 模型的结构化回复
不仅包含可见文字,还可能含 tool_use content blocks。UI 层拆 block 渲染:文字走 Markdown,tool_use 走工具卡片。教学要点:一次 Assistant turn 可能同时有解释文字和多个 tool call。
ToolResultMessage — 把「行动结果」喂回模型
每个 tool_use 必须对应 tool_result(同 tool_use_id)。Bash 输出、文件内容、错误栈都在这里。教学要点:结果过长会触发截断或摘要,影响下一轮模型判断。
SystemMessage — 仅本地可见的通知
例如权限提示、配置变更,不一定进入 API messages。用于 REPL 内系统级反馈。
CompactBoundaryMessage — 压缩后的「分界线」
autoCompact 后,边界前的历史被摘要替代。此标记告诉 normalize 逻辑:边界前/后的消息处理规则不同。
一次 Bash 调用的消息流(示例)
1. UserMessage: "帮我跑 npm test"
2. AssistantMessage: text + tool_use(Bash, { command: "npm test" })
3. [StreamingToolExecutor 执行 Bash]
4. ToolResultMessage: { tool_use_id, content: "stdout..." }
5. AssistantMessage: "测试通过了,因为..."
→ 以上 1-5 全部 append 到 messages[],下一轮 API 请求带上完整序列
messages.ts (5513行)
为何这么大
Message 同时服务 UI 渲染、API 序列化、会话持久化、压缩边界四种场景。createUserMessage / normalizeMessagesForAPI / getMessagesAfterCompactBoundary 是三个必记入口函数。
normalizeMessagesForAPI
发送前最后一道关卡:删 UI 字段、合并 block、修复 tool_use/tool_result 配对、处理 CompactBoundary。读码建议:debug API 400 错误时第一个打开此函数。
getMessagesAfterCompactBoundary
压缩后只取「有效上下文」:边界之后的消息 + 摘要块。理解它才能理解长会话为何还能继续工作。
本章小结与延伸
Message 管道:创建 → 展示 → 规范化 → API。messages.ts 是必读文件。 继续学习: