本章总览
先看整体:message-types 是 transcript 的类型地基,决定“每一行消息能长成什么样、渲染层如何分支、队列与桥接如何对齐”。在这个地基上,types/message.ts 定义判别联合与系统子类型,types/messageQueueTypes.ts 描述发送队列,utils/messages.ts 把类型收敛为可复用工厂。
学完本章你应该能
- 列举 Message 联合的全部成员及其 type 字面量
- 说明 MessageBase 扩展字段的设计意图
- 区分 UserMessage.message.content 的 string 与 block 数组形态
- 理解 SystemMessage subtype 家族(local_command、compact_boundary 等)
- 知道 NormalizedMessage 与 Message 在渲染层的取舍
- 能在 utils/messages.ts 找到 createUserMessage 等工厂与类型的对应关系
核心概念(先读懂这些)
Transcript 是一棵带 uuid 的消息树
每条 Message 可选 uuid、parentUuid,支持 fork/resume 与 compact 边界。isMeta 标记模型可见但 UI 折叠;isVirtual 用于 speculative 或合成消息。origin 保留 MessageOrigin 供 analytics 与 bridge 回放。类型层故意保留 [key: string]: unknown 以兼容历史 transcript JSON 而不阻塞新字段落地。
System 消息是 subtype 驱动的多态
SystemMessage 用 subtype 区分 local_command、bridge_status、thinking、memory_saved、compact_boundary 等。TypeScript 通过 SystemLocalCommandMessage、SystemCompactBoundaryMessage 等交叉类型给组件窄化——Ink 组件 switch(message.subtype) 时可获更好提示。level 字段(info/warning/error)服务横幅与 Doctor 展示。
Content blocks 与 SDK 对齐
UserMessage.message.content 可以是 string 或 { type: string; text?: string; ... }[],与 Anthropic Messages API ContentBlockParam 形态兼容。AssistantMessage.message.content 用 unknown,因 tool_use、thinking、redacted_thinking 等块形状随模型版本演进。工具层在读取前做运行时窄化而非在 types 层穷举每一种 block。
建议学习步骤
- 通读 message.ts 顶部 MessageBase 与 MessageOrigin
- 对照 UserMessage / AssistantMessage 的 message 嵌套结构
- 浏览 System* 别名列表,在 REPL 搜索 createSystemMessage 用法
- 阅读 ProgressMessage、HookResultMessage、TombstoneMessage 语义
- 打开 messageQueueTypes.ts 看 queued 与 in-flight 类型
- 在 utils/messages.ts 验证工厂函数返回类型
常见误区
注意
不要把 AttachmentMessage 与 user 侧 file block 混淆——attachment 是 transcript 行类型
注意
GroupedToolUseMessage 与 CollapsedReadSearchGroup 在 UI 层另有折叠逻辑
注意
StreamEvent 仅用于 streaming 解析,不应持久化进 transcript
注意
修改 Message 联合时需同步 bridge 序列化与 sessionStorage schema
Message 联合总览
Message 是 REPL、compact、bridge、SDK 共享的 transcript 行类型:
Message =
| UserMessage type: 'user'
| AssistantMessage type: 'assistant'
| ProgressMessage type: 'progress'
| SystemMessage type: 'system' (+ subtype)
| AttachmentMessage type: 'attachment'
| HookResultMessage type: 'hook_result'
| ToolUseSummaryMessage type: 'tool_use_summary'
| TombstoneMessage type: 'tombstone'
| GroupedToolUseMessage type: 'grouped_tool_use'
RenderableMessage 别名等于 Message,供组件 props 语义化。NormalizedMessage 是 user/assistant/progress/system/attachment 子集,用于需要排除 tombstone/grouped 的渲染路径。
源码引用: src/types/message.ts · 第 6–17 行(共 135 行)
6| export type MessageBase = {
7| uuid?: string
8| parentUuid?: string
9| timestamp?: string
10| createdAt?: string
11| isMeta?: boolean
12| isVirtual?: boolean
13| isCompactSummary?: boolean
14| toolUseResult?: unknown
15| origin?: MessageOrigin
16| [key: string]: unknown
17| }
源码引用: src/types/message.ts · 第 124–133 行(共 135 行)
124| export type Message =
125| | UserMessage
126| | AssistantMessage
127| | ProgressMessage
128| | SystemMessage
129| | AttachmentMessage
130| | HookResultMessage
131| | ToolUseSummaryMessage
132| | TombstoneMessage
133| | GroupedToolUseMessage
UserMessage 与 AssistantMessage
UserMessage 结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| type | 'user' | 判别字段 |
| message.content | string | block[] | 用户输入或粘贴的多模态内容 |
| message.* | unknown 扩展 | 与 API 请求体对齐 |
AssistantMessage 的 message 可选,content 为 unknown——streaming 过程中可能部分填充。toolUseResult 在 MessageBase 层,表示 tool 结果回写 transcript 时的附加 payload。
ProgressMessage 用于 tool streaming 中间态(bash 输出、MCP 进度),通常 isMeta 或 UI 特殊渲染。
源码引用: src/types/message.ts · 第 24–43 行(共 135 行)
24| export type UserMessage = MessageBase & {
25| type: 'user'
26| message: {
27| content: string | Array<{ type: string; text?: string; [key: string]: unknown }>
28| [key: string]: unknown
29| }
30| }
31|
32| export type AssistantMessage = MessageBase & {
33| type: 'assistant'
34| message?: {
35| content?: unknown
36| [key: string]: unknown
37| }
38| }
39|
40| export type ProgressMessage = MessageBase & {
41| type: 'progress'
42| progress?: unknown
43| }
源码引用: src/types/message.ts · 第 40–43 行(共 135 行)
40| export type ProgressMessage = MessageBase & {
41| type: 'progress'
42| progress?: unknown
43| }
System 消息 subtype 家族
SystemMessage 通过 subtype 承载本地命令、bridge、thinking、memory_saved、stop_hook_summary、compact_boundary、permission_retry 等语义。TypeScript 导出细粒度别名便于组件 narrow:
| 别名 | 典型用途 |
|---|---|
| SystemLocalCommandMessage | /memory、/compact 等 slash 命令系统行 |
| SystemMemorySavedMessage | extractMemories 或 Write 后提示 |
| SystemCompactBoundaryMessage | compact 前后边界标记 |
| SystemThinkingMessage | extended thinking UI |
| SystemAPIErrorMessage | API 失败展示,含 error 字段 |
SystemMessageLevel 为 info | warning | error | string,允许未来扩展级别而不改联合。
源码引用: src/types/message.ts · 第 47–72 行(共 135 行)
47| export type SystemMessage = MessageBase & {
48| type: 'system'
49| subtype?: string
50| level?: SystemMessageLevel
51| message?: string
52| }
53|
54| export type SystemLocalCommandMessage = SystemMessage & {
55| subtype: 'local_command'
56| }
57|
58| export type SystemBridgeStatusMessage = SystemMessage
59| export type SystemTurnDurationMessage = SystemMessage
60| export type SystemThinkingMessage = SystemMessage
61| export type SystemMemorySavedMessage = SystemMessage
62| export type SystemStopHookSummaryMessage = SystemMessage
63| export type SystemInformationalMessage = SystemMessage
64| export type SystemCompactBoundaryMessage = SystemMessage
65| export type SystemMicrocompactBoundaryMessage = SystemMessage
66| export type SystemPermissionRetryMessage = SystemMessage
67| export type SystemScheduledTaskFireMessage = SystemMessage
68| export type SystemAwaySummaryMessage = SystemMessage
69| export type SystemAgentsKilledMessage = SystemMessage
70| export type SystemApiMetricsMessage = SystemMessage
71| export type SystemAPIErrorMessage = SystemMessage & { error?: string }
72| export type SystemFileSnapshotMessage = SystemMessage
源码引用: src/types/message.ts · 第 74–84 行(共 135 行)
74| export type HookResultMessage = MessageBase & {
75| type: 'hook_result'
76| }
77|
78| export type ToolUseSummaryMessage = MessageBase & {
79| type: 'tool_use_summary'
80| }
81|
82| export type TombstoneMessage = MessageBase & {
83| type: 'tombstone'
84| }
StreamEvent 与 Compact 元数据
StreamEvent、RequestStartEvent 描述 SSE/stream 解析阶段的松散事件,使用 index signature 兼容未记录事件名。
CompactMetadata、PartialCompactDirection(older/newer/both)服务 compact 服务在 transcript 上打标。StopHookInfo 供 stopHooks 附加结构化信息。
这些类型 intentionally loose——compact 与 query 循环迭代快,严格 schema 会拖慢落地;运行时 zod 校验在 services 层完成。
源码引用: src/types/message.ts · 第 86–111 行(共 135 行)
86| export type StreamEvent = {
87| type?: string
88| [key: string]: unknown
89| }
90|
91| export type RequestStartEvent = StreamEvent
92|
93| export type StopHookInfo = {
94| [key: string]: unknown
95| }
96|
97| export type CompactMetadata = {
98| [key: string]: unknown
99| }
100|
101| export type PartialCompactDirection = 'older' | 'newer' | 'both' | string
102|
103| export type CollapsedReadSearchGroup = {
104| [key: string]: unknown
105| }
106|
107| export type GroupedToolUseMessage = MessageBase & {
108| type: 'grouped_tool_use'
109| }
110|
111| export type CollapsibleMessage = MessageBase
messageQueueTypes 与发送队列
messageQueueTypes.ts 定义主循环外的消息队列条目:待发送 user 消息、重试、speculation 接受等。队列类型引用 Message 或 content block 子集,避免 queue 模块 import 整个 utils/messages。
设计意图:当 REPL 在 tool 执行或 permission dialog 阻塞时,用户仍可排队输入;队列 flush 时构造标准 UserMessage 入 transcript。读 QueryEngine 或 handlePromptSubmit 时可对照队列类型与 Message 工厂。
源码引用: src/types/messageQueueTypes.ts · 第 1–2 行(共 2 行)
1| export type MessageQueueEntry = Record<string, unknown>
2|
utils/messages 工厂与类型对齐
utils/messages.ts 的 createUserMessage、createAssistantMessage、createMemorySavedMessage 等返回 Message 联合成员。工厂集中默认值(timestamp、uuid 生成),保证 transcript 磁盘 JSON 与类型一致。
createSystemMessage 族函数设置 subtype/level/message 三元组。extractMemories 完成后调用 createMemorySavedMessage,对应 SystemMemorySavedMessage 形状。
改 message.ts 后,先让 messages.ts 编译通过,再修 components 与 bridge 的 switch 分支——TypeScript 判别联合会在遗漏分支时报错。
源码引用: src/utils/messages.ts · 第 1–50 行(共 5513 行)
1| import { feature } from 'bun:bundle'
2| import type { BetaUsage as Usage } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
3| import type {
4| ContentBlock,
5| ContentBlockParam,
6| RedactedThinkingBlock,
7| RedactedThinkingBlockParam,
8| TextBlockParam,
9| ThinkingBlock,
10| ThinkingBlockParam,
11| ToolResultBlockParam,
12| ToolUseBlock,
13| ToolUseBlockParam,
14| } from '@anthropic-ai/sdk/resources/index.mjs'
15| import { randomUUID, type UUID } from 'crypto'
16| import isObject from 'lodash-es/isObject.js'
17| import last from 'lodash-es/last.js'
18| import {
19| type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
20| logEvent,
21| } from 'src/services/analytics/index.js'
22| import { sanitizeToolNameForAnalytics } from 'src/services/analytics/metadata.js'
23| import type { AgentId } from 'src/types/ids.js'
24| import { companionIntroText } from '../buddy/prompt.js'
25| import { NO_CONTENT_MESSAGE } from '../constants/messages.js'
26| import { OUTPUT_STYLE_CONFIG } from '../constants/outputStyles.js'
27| import { isAutoMemoryEnabled } from '../memdir/paths.js'
28| import {
29| checkStatsigFeatureGate_CACHED_MAY_BE_STALE,
30| getFeatureValue_CACHED_MAY_BE_STALE,
31| } from '../services/analytics/growthbook.js'
32| import {
33| getImageTooLargeErrorMessage,
34| getPdfInvalidErrorMessage,
35| getPdfPasswordProtectedErrorMessage,
36| getPdfTooLargeErrorMessage,
37| getRequestTooLargeErrorMessage,
38| } from '../services/api/errors.js'
39| import type { AnyObject, Progress } from '../Tool.js'
40| import { isConnectorTextBlock } from '../types/connectorText.js'
41| import type {
42| AssistantMessage,
43| AttachmentMessage,
44| Message,
45| MessageOrigin,
46| NormalizedAssistantMessage,
47| NormalizedMessage,
48| NormalizedUserMessage,
49| PartialCompactDirection,
50| ProgressMessage,
源码引用: src/utils/messages.ts · 第 200–240 行(共 5513 行)
200| export function deriveShortMessageId(uuid: string): string {
201| // Take first 10 hex chars from the UUID (skipping dashes)
202| const hex = uuid.replace(/-/g, '').slice(0, 10)
203| // Convert to base36 for shorter representation, take 6 chars
204| return parseInt(hex, 16).toString(36).slice(0, 6)
205| }
206|
207| export const INTERRUPT_MESSAGE = '[Request interrupted by user]'
208| export const INTERRUPT_MESSAGE_FOR_TOOL_USE =
209| '[Request interrupted by user for tool use]'
210| export const CANCEL_MESSAGE =
211| "The user doesn't want to take this action right now. STOP what you are doing and wait for the user to tell you how to proceed."
212| export const REJECT_MESSAGE =
213| "The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed."
214| export const REJECT_MESSAGE_WITH_REASON_PREFIX =
215| "The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). To tell you how to proceed, the user said:\n"
216| export const SUBAGENT_REJECT_MESSAGE =
217| 'Permission for this tool use was denied. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). Try a different approach or report the limitation to complete your task.'
218| export const SUBAGENT_REJECT_MESSAGE_WITH_REASON_PREFIX =
219| 'Permission for this tool use was denied. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). The user said:\n'
220| export const PLAN_REJECTION_PREFIX =
221| 'The agent proposed a plan that was rejected by the user. The user chose to stay in plan mode rather than proceed with implementation.\n\nRejected plan:\n'
222|
223| /**
224| * Shared guidance for permission denials, instructing the model on appropriate workarounds.
225| */
226| export const DENIAL_WORKAROUND_GUIDANCE =
227| `IMPORTANT: You *may* attempt to accomplish this action using other tools that might naturally be used to accomplish this goal, ` +
228| `e.g. using head instead of cat. But you *should not* attempt to work around this denial in malicious ways, ` +
229| `e.g. do not use your ability to run tests to execute non-test actions. ` +
230| `You should only try to work around this restriction in reasonable ways that do not attempt to bypass the intent behind this denial. ` +
231| `If you believe this capability is essential to complete the user's request, STOP and explain to the user ` +
232| `what you were trying to do and why you need this permission. Let the user decide how to proceed.`
233|
234| export function AUTO_REJECT_MESSAGE(toolName: string): string {
235| return `Permission to use ${toolName} has been denied. ${DENIAL_WORKAROUND_GUIDANCE}`
236| }
237| export function DONT_ASK_REJECT_MESSAGE(toolName: string): string {
238| return `Permission to use ${toolName} has been denied because Claude Code is running in don't ask mode. ${DENIAL_WORKAROUND_GUIDANCE}`
239| }
240| export const NO_RESPONSE_REQUESTED = 'No response requested.'
渲染与 bridge 消费模式
Ink 组件(MessageComponents、Transcript)对 Message 做 type switch:
message.type === 'user' → UserMessageBlock
message.type === 'assistant' → AssistantMessageBlock + tool blocks
message.type === 'system' → switch(subtype) 横幅/本地命令
message.type === 'progress' → ToolProgressRenderer
Bridge 出站可能 strip isMeta 或折叠 grouped_tool_use。NormalizedUserMessage 等别名供仅需文本内容的 API 适配层使用,避免误传 tombstone 进模型上下文。
宽松字段的阅读边界
读 Message 类型时要把“判别字段稳定”和“payload 宽松”分开理解。type、subtype、uuid、parentUuid 这些字段承担 transcript 树、渲染分支、compact 边界和 hook 回放的稳定契约;而 message.content、progress、toolUseResult、origin、CompactMetadata 等位置保留 unknown 或索引签名,是为了让 streaming、工具结果、外部 SDK 与历史 JSON 可以先落盘再由消费点做运行时窄化。这样新增一种 tool_use block 或 hook 附加字段时,不必立刻修改中央 Message 联合,也不会让旧 transcript 因多一个字段无法加载。
实践上应遵守两条规则:第一,新增 transcript 行种类才改 Message 联合,并同步 utils/messages 的工厂和所有渲染 switch;第二,若只是某个 assistant content block、progress payload 或系统消息附加信息变化,优先在对应工具、组件或 hook 解析处定义局部类型。SystemMemorySavedMessage 这类别名看似只是 SystemMessage,但它给组件和文档留下语义锚点:调用 createMemorySavedMessage 后,UI 可以通过 subtype=memory_saved 识别通知,而模型上下文仍只把它当系统元数据处理。
源码引用: src/types/message.ts · 第 6–17 行(共 135 行)
6| export type MessageBase = {
7| uuid?: string
8| parentUuid?: string
9| timestamp?: string
10| createdAt?: string
11| isMeta?: boolean
12| isVirtual?: boolean
13| isCompactSummary?: boolean
14| toolUseResult?: unknown
15| origin?: MessageOrigin
16| [key: string]: unknown
17| }
源码引用: src/types/message.ts · 第 47–72 行(共 135 行)
47| export type SystemMessage = MessageBase & {
48| type: 'system'
49| subtype?: string
50| level?: SystemMessageLevel
51| message?: string
52| }
53|
54| export type SystemLocalCommandMessage = SystemMessage & {
55| subtype: 'local_command'
56| }
57|
58| export type SystemBridgeStatusMessage = SystemMessage
59| export type SystemTurnDurationMessage = SystemMessage
60| export type SystemThinkingMessage = SystemMessage
61| export type SystemMemorySavedMessage = SystemMessage
62| export type SystemStopHookSummaryMessage = SystemMessage
63| export type SystemInformationalMessage = SystemMessage
64| export type SystemCompactBoundaryMessage = SystemMessage
65| export type SystemMicrocompactBoundaryMessage = SystemMessage
66| export type SystemPermissionRetryMessage = SystemMessage
67| export type SystemScheduledTaskFireMessage = SystemMessage
68| export type SystemAwaySummaryMessage = SystemMessage
69| export type SystemAgentsKilledMessage = SystemMessage
70| export type SystemApiMetricsMessage = SystemMessage
71| export type SystemAPIErrorMessage = SystemMessage & { error?: string }
72| export type SystemFileSnapshotMessage = SystemMessage
源码引用: src/utils/messages.ts · 第 223–247 行(共 5513 行)
223| /**
224| * Shared guidance for permission denials, instructing the model on appropriate workarounds.
225| */
226| export const DENIAL_WORKAROUND_GUIDANCE =
227| `IMPORTANT: You *may* attempt to accomplish this action using other tools that might naturally be used to accomplish this goal, ` +
228| `e.g. using head instead of cat. But you *should not* attempt to work around this denial in malicious ways, ` +
229| `e.g. do not use your ability to run tests to execute non-test actions. ` +
230| `You should only try to work around this restriction in reasonable ways that do not attempt to bypass the intent behind this denial. ` +
231| `If you believe this capability is essential to complete the user's request, STOP and explain to the user ` +
232| `what you were trying to do and why you need this permission. Let the user decide how to proceed.`
233|
234| export function AUTO_REJECT_MESSAGE(toolName: string): string {
235| return `Permission to use ${toolName} has been denied. ${DENIAL_WORKAROUND_GUIDANCE}`
236| }
237| export function DONT_ASK_REJECT_MESSAGE(toolName: string): string {
238| return `Permission to use ${toolName} has been denied because Claude Code is running in don't ask mode. ${DENIAL_WORKAROUND_GUIDANCE}`
239| }
240| export const NO_RESPONSE_REQUESTED = 'No response requested.'
241|
242| // Synthetic tool_result content inserted by ensureToolResultPairing when a
243| // tool_use block has no matching tool_result. Exported so HFI submission can
244| // reject any payload containing it — placeholder satisfies pairing structurally
245| // but the content is fake, which poisons training data if submitted.
246| export const SYNTHETIC_TOOL_RESULT_PLACEHOLDER =
247| '[Tool result missing due to internal error]'
本章小结与延伸
message-types = transcript 的 TypeScript 真相源。下一章 tool-permission-types,读 Tool 进度类型与 Permission/Command 契约。 继续学习: