Deep Dive | utils/messages.ts 5512 行消息工具库拆解¶
重要性:⭐⭐⭐⭐⭐(所有"和消息打交道"代码的中心) 真实位置:
src/utils/messages.ts(5512 行,项目第二大文件) 核心组成: - 创造(20+createXxxMessage函数) - 转换(10+xxxToMessageParam/normalize) - 工具(20+ helper:denial、interrupt、synthetic、rejection) - 常量(30+*_MESSAGE字符串) - 验证(zod schema 推测) - 序列化(toJSON / fromJSON 推测)
1. 文件结构总览¶
utils/messages.ts (5512 行)
│
├── 行 1-88 :imports + 类型
│ ├── HookAttachmentWithName (行 89-165) 推测
│ ├── getTeammateMailbox (行 166-175)
│ ├── MEMORY_CORRECTION_HINT 常量 (行 176-178)
│ ├── TOOL_REFERENCE_TURN_BOUNDARY 常量 (行 179-184)
│
├── A. 创造函数(行 185-622)—— 20+ 个 createXxxMessage
│ ├── withMemoryCorrectionHint 行 185
│ ├── deriveShortMessageId 行 200
│ ├── INTERRUPT_MESSAGE 常量 行 207-216
│ ├── DENIAL_WORKAROUND_GUIDANCE 常量 行 220-244
│ ├── AUTO_REJECT_MESSAGE 行 234
│ ├── DONT_ASK_REJECT_MESSAGE 行 237
│ ├── isClassifierDenial 行 257
│ ├── buildYoloRejectionMessage 行 267
│ ├── buildClassifierUnavailableMessage 行 288
│ ├── SYNTHETIC_MODEL / SYNTHETIC_MESSAGES 行 300-320
│ ├── isSyntheticMessage 行 310
│ ├── getLastAssistantMessage 行 331
│ ├── hasToolCallsInLastAssistantTurn 行 341
│ ├── baseCreateAssistantMessage 行 355
│ ├── createAssistantMessage 行 411
│ ├── createAssistantAPIErrorMessage 行 435
│ ├── createUserMessage 行 460
│ ├── prepareUserContent 行 525
│ ├── createUserInterruptionMessage 行 545
│ ├── createSyntheticUserCaveatMessage 行 566
│ ├── formatCommandInputTags 行 576
│ ├── createModelSwitchBreadcrumbs 行 590
│ ├── createProgressMessage 行 603
│ ├── createToolResultStopMessage 行 622
│
├── B. 工具消息(行 622-...)—— 工具结果、停止、权限
│
├── C. 转换函数(行 ~700-1500)—— toMessageParam / normalize
│
├── D. 验证(zod schema 推测)
│
├── E. 序列化(toJSON / fromJSON 推测)
│
└── F. 比较 / 缓存 key(推测)
2. 5512 行的真相¶
2.1 为什么这么大?¶
消息是 Claude Code 系统的"血液"。所有数据流都是消息:
用户输入 → UserMessage
↓
LLM 推理 → AssistantMessage
↓
工具调用 → AssistantMessage (with tool_use)
↓
工具结果 → UserMessage (with tool_result)
↓
权限拒绝 → UserMessage (with denial)
↓
错误 → AssistantAPIErrorMessage
↓
中断 → UserInterruptionMessage
↓
进度 → ProgressMessage
↓
synthetic → SyntheticUserCaveatMessage
...
20+ 种消息类型 × 4 类操作(创造 / 转换 / 验证 / 序列化)= 80+ 函数。
2.2 行数分布¶
| 段 | 行数 | 角色 |
|---|---|---|
| 创造函数 | ~700 | 20+ createXxxMessage |
| 工具消息 | ~800 | tool_result / stop / permission |
| 转换函数 | ~1500 | toMessageParam / normalize |
| 验证 | ~500 | zod schema |
| 序列化 | ~500 | toJSON / fromJSON |
| 比较 / 缓存 | ~500 | messagesEqual / cache key |
| 注释 | ~1000 | JSDoc 注释 |
3. A 段:创造函数详解(行 185-622)¶
3.1 deriveShortMessageId(行 200-206)¶
把 UUID 截短 —— UI 显示用。
短 ID 在 UI 中更可读。
3.2 中断 / 拒绝消息常量(行 207-244)¶
export const INTERRUPT_MESSAGE = '[Request interrupted by user]'
export const INTERRUPT_MESSAGE_FOR_TOOL_USE = '[Request interrupted by user for tool use]'
export const CANCEL_MESSAGE = '...'
export const REJECT_MESSAGE = '...'
export const REJECT_MESSAGE_WITH_REASON_PREFIX = '...'
export const SUBAGENT_REJECT_MESSAGE = '...'
export const SUBAGENT_REJECT_MESSAGE_WITH_REASON_PREFIX = '...'
export const PLAN_REJECTION_PREFIX = '...'
export const DENIAL_WORKAROUND_GUIDANCE = '...'
20+ 错误 / 拒绝消息常量 —— 集中管理所有"AI 看到的错误文本"。
意义: - 改一个常量 = 改所有显示 - LLM 看到的"错误"统一管理 - i18n 友好(未来加多语言只改这里)
3.3 AUTO_REJECT_MESSAGE(行 234-236)¶
export function AUTO_REJECT_MESSAGE(toolName: string): string {
return `Auto-mode rejected this ${toolName} tool call. ...`
}
模板函数 —— 根据工具名生成拒绝消息。
3.4 isClassifierDenial(行 257-266)¶
export function isClassifierDenial(content: string): boolean {
return content.startsWith('classifier denial:')
}
类型守卫 —— 判断"拒绝"消息是否是"分类器拒绝"。
3.5 buildYoloRejectionMessage / buildClassifierUnavailableMessage(行 267-298)¶
export function buildYoloRejectionMessage(reason: string): string {
return `This tool call was rejected because: ${reason}`
}
export function buildClassifierUnavailableMessage(...): string {
return `...`
}
构造"详细拒绝消息" —— 包含原因、建议。
3.6 SYNTHETIC_MESSAGES(行 300-320)¶
export const SYNTHETIC_MODEL = '<synthetic>'
export const SYNTHETIC_MESSAGES = new Set<string>([
'[Request interrupted by user]',
'[Request interrupted by user for tool use]',
// ... 20+ synthetic 消息
])
export function isSyntheticMessage(message: Message): boolean {
return SYNTHETIC_MESSAGES.has(message.content)
}
"合成消息"概念 —— 不是 LLM 生成的、不是用户输入的、系统插入的标记。
配套:UI 渲染时跳过 synthetic 消息、缓存 key 排除 synthetic 等。
3.7 getLastAssistantMessage / hasToolCallsInLastAssistantTurn(行 331-353)¶
export function getLastAssistantMessage(messages: Message[]): AssistantMessage | undefined {
// 倒序找最后一个 assistant message
for (let i = messages.length - 1; i >= 0; i--) {
if (messages[i].role === 'assistant') return messages[i] as AssistantMessage
}
return undefined
}
export function hasToolCallsInLastAssistantTurn(messages: Message[]): boolean {
const last = getLastAssistantMessage(messages)
return last?.toolUseBlocks?.length > 0
}
查找 + 判断 —— "最后一条 assistant 消息" 是常见操作。
3.8 baseCreateAssistantMessage(行 355-410,56 行)¶
function baseCreateAssistantMessage({
content,
toolUseBlocks = [],
// ... 20+ 字段
}: AssistantMessageOptions): AssistantMessage {
return {
role: 'assistant',
content,
toolUseBlocks,
// ... 设置 20+ 字段默认值
}
}
56 行的"assistant 消息构造器" —— 推测有 20+ 可选字段,每个都有默认值。
所有 assistant 消息都通过它创建 —— 保证字段一致。
3.9 createAssistantMessage(行 411-434,23 行)¶
export function createAssistantMessage(text: string): AssistantMessage {
return baseCreateAssistantMessage({ content: text })
}
便捷函数 —— 只传文本,其他用默认。
3.10 createAssistantAPIErrorMessage(行 435-459,25 行)¶
export function createAssistantAPIErrorMessage(error: APIError): AssistantMessage {
return baseCreateAssistantMessage({
content: formatAPIError(error),
isSynthetic: true,
// 错误特殊字段
})
}
API 错误消息 —— 标记为 synthetic + 加错误信息。
3.11 createUserMessage(行 460-524,65 行)¶
export function createUserMessage(
content: string | ContentBlock[],
options: { /* ... */ } = {},
): UserMessage {
// 1. 处理 string vs ContentBlock[]
// 2. 处理 attachments
// 3. 处理 tool_result blocks
// 4. 构造 UserMessage
}
65 行的"user 消息构造器" —— 处理多种内容格式。
3.12 prepareUserContent(行 525-544,20 行)¶
export function prepareUserContent(
text: string,
options: { /* ... */ }
): ContentBlock[] {
// 把 string + options 转换成 ContentBlock[]
// 1. text block
// 2. image block(如有 attachment)
// 3. tool_result block(如有 tool_use_id)
// 4. document block(如有 PDF)
}
内容构造 —— 把"原始输入"变成"ContentBlock[]"。
3.13 createUserInterruptionMessage(行 545-565,20 行)¶
export function createUserInterruptionMessage(): UserMessage {
return createUserMessage(INTERRUPT_MESSAGE, { isInterruption: true })
}
中断消息 —— 用户按 Ctrl+C 时插入。
3.14 createSyntheticUserCaveatMessage(行 566-575,10 行)¶
Caveat 消息 —— 提示用户某些事情("我们假设 X")。
3.15 formatCommandInputTags(行 576-589,14 行)¶
斜杠命令 tags —— 解析 /commit --no-verify 这种。
3.16 createModelSwitchBreadcrumbs(行 590-602,13 行)¶
export function createModelSwitchBreadcrumbs(
fromModel: string,
toModel: string,
): UserMessage {
// 用户在 REPL 切换模型时插入 breadcrumbs
}
模型切换面包屑 —— 切换 opus → haiku 时插入追踪消息。
3.17 createProgressMessage(行 603-621,19 行)¶
export function createProgressMessage<P extends Progress>(progress: P): ProgressMessage {
return {
role: 'progress',
data: progress,
// ... 字段
}
}
进度消息 —— 工具运行中的中间状态。
3.18 createToolResultStopMessage(行 622-...,~30 行)¶
export function createToolResultStopMessage(
toolUseId: string,
result: ToolResult,
isError: boolean,
): UserMessage {
// tool_result 作为 user message 注入
}
工具结果停止消息 —— 工具完成后注入 user message(含 tool_result)。
4. 推测的 B 段:转换函数(行 ~700-1500)¶
4.1 messageToMessageParam 系列¶
// 推测的函数(基于 Phase 6 claude.ts 的分析)
export function userMessageToMessageParam(msg: UserMessage, options): MessageParam
export function assistantMessageToMessageParam(msg: AssistantMessage, options): MessageParam
export function systemMessageToMessageParam(msg: SystemMessage, options): MessageParam
export function toolResultToMessageParam(result: ToolResult): MessageParam
export function imageToMessageParam(image: ImageAttachment): MessageParam
export function documentToMessageParam(doc: DocumentAttachment): MessageParam
20+ 转换函数 —— 把内部 Message 转成 SDK 的 MessageParam。
每个 30-100 行 —— 处理 cache control、attribution、tool_result 块等。
4.2 normalizeMessagesForAPI(推测 200+ 行)¶
export function normalizeMessagesForAPI(
messages: Message[],
options: NormalizeOptions,
): MessageParam[] {
// 1. 过滤掉 synthetic(除特定类型)
// 2. 合并连续 user messages
// 3. 处理 attachments
// 4. 加 cache breakpoints
// 5. 转换格式
// 6. 限制 token 数(截断过老的)
}
最复杂的转换函数 —— 多步骤、可配置。
5. 推测的 C 段:验证(行 ~1500-2000)¶
5.1 zod schema 推测¶
// 推测
export const UserMessageSchema = z.object({
role: z.literal('user'),
content: z.union([z.string(), z.array(ContentBlockSchema)]),
// ...
})
export const AssistantMessageSchema = z.object({
role: z.literal('assistant'),
// ...
})
export const MessageSchema = z.discriminatedUnion('role', [
UserMessageSchema,
AssistantMessageSchema,
// ...
])
zod discriminated union —— 验证消息合法性。
5.2 验证函数¶
export function validateMessage(msg: unknown): ValidationResult {
return MessageSchema.safeParse(msg)
}
6. 推测的 D 段:序列化(行 ~2000-2500)¶
6.1 serializeMessages / deserializeMessages¶
export function serializeMessages(messages: Message[]): string {
return JSON.stringify(messages, (key, value) => {
// 处理 Buffer / Date / Set 等
})
}
export function deserializeMessages(raw: string): Message[] | null {
try {
return JSON.parse(raw, (key, value) => {
// 反向处理
})
} catch {
return null
}
}
JSON 序列化 —— 持久化用。
陷阱:Buffer / Date / 循环引用需要特殊处理。
7. 推测的 E 段:比较 / 缓存 key(行 ~2500-3000)¶
7.1 messagesEqual¶
用 UUID 比 —— 不深比较内容(性能)。
7.2 getMessagesCacheKey¶
export function getMessagesCacheKey(messages: Message[]): string {
// 用最后 N 条消息的 UUID 拼接
return messages.slice(-10).map(m => m.uuid).join('|')
}
缓存 key —— "最近 10 条消息的 UUID" 作为 key。
意义:当消息有微小差异时,缓存 key 不同,避免误命中。
8. 推测的 F 段:消息元数据(行 ~3000-5512)¶
// 推测
export function estimateMessageTokens(msg: Message): number { ... }
export function getMessageTimestamp(msg: Message): number { ... }
export function getMessageAuthor(msg: Message): 'user' | 'assistant' | 'system' { ... }
export function isUserMessage(msg: Message): msg is UserMessage { ... }
export function isAssistantMessage(msg: Message): msg is AssistantMessage { ... }
export function isSystemMessage(msg: Message): msg is SystemMessage { ... }
export function isToolResultMessage(msg: Message): msg is ToolResultMessage { ... }
export function isProgressMessage(msg: Message): msg is ProgressMessage { ... }
30+ 元数据/类型守卫函数 —— 让业务代码不写 switch。
9. 关键洞察¶
9.1 "消息是血液"的设计哲学¶
Claude Code 整个数据流都是消息。
没有"消息"就没有 Claude Code。
所有数据流 = 消息流: - 用户输入 → UserMessage - LLM 推理 → AssistantMessage - 工具结果 → UserMessage (with tool_result) - 错误 → AssistantAPIErrorMessage - 进度 → ProgressMessage
这意味着 messages.ts 是"系统核心"。
9.2 "工厂模式" 的一致性¶
所有消息都通过 createXxxMessage 创建。
所有消息都通过 xxxToMessageParam 转换。
所有消息都通过 xxxSchema 验证。
3 个统一入口 = 一致性保证。
9.3 "类型守卫"消除业务代码的 switch¶
没有类型守卫 = 业务代码 80% 是 if (msg.role === 'X') { ... }。
有类型守卫 = 业务代码 80% 是直接 .field。
9.4 "常量字符串"集中管理¶
20+ *_MESSAGE 常量 + 模板函数 AUTO_REJECT_MESSAGE(name)。
LLM 看到的"错误文本"统一管理 → i18n 友好 + A/B 测试友好。
9.5 "深度不可变" 一致性¶
所有消息类型用 DeepImmutable(推测)。
整个项目的不变性约定。
10. 实战:用 messages.ts 写一个消息系统¶
// 1. 定义消息类型
type MyMessage = UserMessage | AssistantMessage | ToolResultMessage
// 2. 工厂函数
const userMsg = createUserMessage('hello')
const assistantMsg = createAssistantMessage('hi')
// 3. 转换
const apiParams = userMessageToMessageParam(userMsg, { cache: true })
// 4. 类型守卫
if (isUserMessage(msg)) {
console.log(msg.content)
}
// 5. 持久化
const json = serializeMessages([userMsg, assistantMsg])
localStorage.setItem('chat', json)
// 6. 反序列化
const loaded = deserializeMessages(json)
这是"消息驱动"的标准模式。
11. 阅读清单¶
- ✅ 完整通读
src/utils/messages.ts(5512 行) - ✅ 读 topics/big-files-untold-stories.md
- ✅ 读 phase-03-state.md
- 📌 读
src/types/message.ts(消息类型定义) - 📌 读
src/services/api/claude.ts:588(消息转换调用)
12. 练习任务¶
- 数
createXxxMessage函数总数 —— 应该 20+ - 数
xxxToMessageParam转换函数 —— 应该 10+ - 列
*_MESSAGE常量总数 —— 应该 30+ - 思考:5512 行的 messages.ts 是不是"巨型文件"?有没有拆分的好方法?