3.3 消息历史管理
Claude Code 不是简单地将所有历史消息发送给 API。它通过一系列机制管理消息列表,确保发送给 API 的消息格式合法、内容精简。
压缩边界(Compact Boundary)
当 autocompact 发生后,消息列表中会插入一个 compact_boundary 标记。之后的 API 调用只发送边界之后的消息:
let messagesForQuery = [...getMessagesAfterCompactBoundary(messages)]当 HISTORY_SNIP Feature 启用时,还会在此基础上投影一个"剪裁视图"——将被标记为 snipped 的消息从 API 请求中隐藏。
消息规范化(normalizeMessagesForAPI)
normalizeMessagesForAPI()(src/utils/messages.ts,约 380 行)是消息发送前的关键处理步骤。它解决了一个核心问题:Claude Code 内部的消息格式和 API 要求的消息格式不完全一致。
// src/utils/messages.ts
export function normalizeMessagesForAPI(
messages: Message[],
tools: Tools = [],
): (UserMessage | AssistantMessage)[] {
const availableToolNames = new Set(tools.map(t => t.name))
// 1. 附件重排序 + 过滤虚拟消息
const reorderedMessages = reorderAttachmentsForAPI(messages)
.filter(m => !((m.type === 'user' || m.type === 'assistant') && m.isVirtual))
// 2. 构建错误→块类型映射(PDF太大、图片太大等)
const errorToBlockTypes: Record<string, Set<string>> = { ... }
const stripTargets = new Map<string, Set<string>>() // userUUID → 需剥离的块类型
// 3-7. 遍历消息,逐条处理:
// - 剥离 tool_reference、advisor blocks、错误媒体项
// - 处理 thinking/signature 块
// - 合并同 ID 的分裂 AssistantMessage
// - 验证和修复 tool_use/tool_result 配对
...
}下面是每个处理步骤及其解决的问题:
附件重排序(
reorderAttachmentsForAPI):附件消息在内部可能出现在任意位置,但 API 要求它们在语义上关联的消息之前。此步骤将附件消息向上冒泡,直到遇到tool_result或assistant消息为止。如果不做这一步,API 可能看到一个孤立的图片块,却不知道它与哪条消息相关。过滤虚拟消息:标记为
isVirtual的消息(如 REPL 内部工具调用的临时消息)被移除。这些消息的存在仅为了 UI 展示——例如自动触发的内部操作在界面上需要显示进度,但它们不应进入 API 请求。构建错误→块类型映射:某些 API 错误(如"PDF 太大"、"图片太大")需要从后续消息中剥离对应的媒体块。系统构建一个映射表
errorToBlockTypes,将错误文本映射到需要剥离的块类型(document、image)。如果不做这一步,同一个过大的 PDF 会在每次请求中被发送,每次都触发同样的错误。剥离内部元素:从消息中移除
tool_reference(工具引用标记)、advisor blocks(顾问指令)、因 API 错误而需要剥离的媒体项。tool_reference是延迟工具加载系统(Tool Search)的内部跟踪标记,API 对此毫无概念——它们的存在会导致 API 返回格式错误。thinking/signature 块处理:根据模型要求处理思考块。某些模型不支持
thinking或redacted_thinking块——发送它们会直接导致 API 返回 400 错误。Signature 块用于验证思考块的完整性,也需要在不支持的模型上剥离。合并分裂消息:流式解析器可能将一个 API 响应拆分为多条具有相同
message.id的AssistantMessage(当并行工具调用产生多个 content block 时)。API 期望一个响应对应一条消息,多条同 ID 消息会违反消息交替规则。验证和修复配对:API 要求每个
tool_useblock 都有对应的tool_result,反之亦然。会话崩溃、压缩、中途中断都可能破坏这种配对关系。此步骤检测并修复孤儿 block——为缺失结果的tool_use生成错误类型的tool_result,为缺失请求的tool_result注入合成的tool_use。没有这一步,恢复一个崩溃的会话几乎必然会因为配对不完整而报错。
为什么这么复杂? 因为 Claude API 对消息格式有严格要求:用户/助手消息必须交替出现、tool_use/tool_result 必须配对、thinking 块不能出现在不支持的位置。而 Claude Code 的内部消息列表可能因为会话崩溃恢复、压缩操作、用户中断等原因违反这些约束。normalizeMessagesForAPI 是防御层——确保无论内部状态多混乱,API 始终收到合法的消息序列。