跳转至

Deep Dive | utils/messages.ts 5512 行消息工具库拆解

重要性:⭐⭐⭐⭐⭐(所有"和消息打交道"代码的中心真实位置src/utils/messages.ts5512 行,项目第二大文件) 核心组成: - 创造(20+ createXxxMessage 函数) - 转换(10+ xxxToMessageParam / normalize) - 工具(20+ helper:denial、interrupt、synthetic、rejection) - 常量(30+ *_MESSAGE 字符串) - 验证(zod schema 推测) - 序列化(toJSON / fromJSON 推测)

关联phase-03-state.mdtopics/big-files-untold-stories.md


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)

export function deriveShortMessageId(uuid: string): string {
  return uuid.slice(0, 8)  // 简化版
}

把 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 行)

export function createSyntheticUserCaveatMessage(): UserMessage {
  // 注入 caveat(警告)消息
}

Caveat 消息 —— 提示用户某些事情("我们假设 X")。

3.15 formatCommandInputTags(行 576-589,14 行)

export function formatCommandInputTags(input: string): string {
  // 把 /command 后面跟的 tags 格式化
}

斜杠命令 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

export function messagesEqual(a: Message, b: Message): boolean {
  return a.uuid === b.uuid
}

用 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

// 业务代码
if (isAssistantMessage(msg)) {
  // TS 自动知道 msg 是 AssistantMessage
  // 不需要 cast
}

没有类型守卫 = 业务代码 80% 是 if (msg.role === 'X') { ... }有类型守卫 = 业务代码 80% 是直接 .field

9.4 "常量字符串"集中管理

20+ *_MESSAGE 常量 + 模板函数 AUTO_REJECT_MESSAGE(name)
LLM 看到的"错误文本"统一管理i18n 友好 + A/B 测试友好

9.5 "深度不可变" 一致性

type DeepImmutable<T> = { readonly [K in keyof T]: DeepImmutable<T[K]> }

所有消息类型用 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. 阅读清单

  1. ✅ 完整通读 src/utils/messages.ts(5512 行)
  2. ✅ 读 topics/big-files-untold-stories.md
  3. ✅ 读 phase-03-state.md
  4. 📌 读 src/types/message.ts(消息类型定义)
  5. 📌 读 src/services/api/claude.ts:588(消息转换调用)

12. 练习任务

  1. createXxxMessage 函数总数 —— 应该 20+
  2. xxxToMessageParam 转换函数 —— 应该 10+
  3. *_MESSAGE 常量总数 —— 应该 30+
  4. 思考:5512 行的 messages.ts 是不是"巨型文件"?有没有拆分的好方法?