Message 与 Part

理解 canonical Message、Part、分页与流式 Mutation

session.messages() 按 Message 边界分页读取 SQLite 中的稳定快照。实时变化由 subscribe() 补充。

const page = await session.messages({ limit: 50 });
for (const message of page.items) render_message(message);
interface SessionMessagePage {
  items: SessionMessage[];
  total: number;
  start_sequence?: number;
  end_sequence?: number;
  next_before_sequence?: number;
  has_more: boolean;
}

total 是 Session 已创建的真实 Message 总数,不是当前 items.length。limit 默认为 50,最大为 200。

两类顶层 Message

type SessionMessage = SessionUserMessage | SessionAgentMessage;
role表示什么顶层状态
user用户输入或内部恢复提示无状态字段,写入即完整
agentAgent 输出与运行记录state: "streaming" | "done"

User Message 不区分 prompt 和 steer。它属于新 Turn 还是当前 Turn,由 turn_id 和 SessionLoop 决定,不重复编码在消息内容中。

Agent Message 的 state 只回答“还能否继续追加 Part”。成功、停止与失败属于 Turn 结果;需要持久化的停止或失败原因使用 error Part 表达。

Session 内只有一条按 sequence 排序的双主体消息序列。Tool、Interaction、Action 和 Error 都是 Agent Message 的 Part,不是独立 Message。普通 Tool Loop 的多个模型 Step 可以继续写入同一 Agent Message;每个模型产生的 Part 用 step_id 记录来源。

Action 的归属规则:目标 Turn 仍有正在流式写的 Agent Message 时,Action Part 直接追加为该 Message 的 Part,与正文共享同一条 canonical Message(例如执行中修改模型或审批模式)。只有当 Session 空闲、目标 Turn 尚未产生 Agent Message,或已有 Message 已收口时,Action 才回退为只含 Action Part 的独立 Agent Message。

User Part

type关键字段说明
texttext用户正文
contexttag, context带语义标签的模型上下文
fileurl, media_type, filename用户提交的文件
datadata_type, data, data_id仅供宿主/UI 使用,不发送给模型

User Part 没有流式状态。context 进入模型上下文时才安全转换成带标签文本:

// canonical User Part
{ part_id: "user-context:1", sequence: 1, type: "context", tag: "reference", context: "此前消息" }

// Model Content
{ type: "text", text: "<reference>此前消息</reference>" }

visibility: "internal" 使用同一个 User Message 格式。恢复提示等仅供下一 Step 使用的输入也会先持久化,再由 Composer 从 canonical history 读取;运行时没有另一套临时输入结构。

Agent Part

type关键字段说明
texttext, state用户可见输出,streaming -> done
reasoningtext, state推理输出
tooltool_call_id, tool_name, state, input, output工具完整生命周期
interactioninteraction_id, status, request, response审批、提问等异步交互
fileurl, media_type, filenameAgent 文件结果
datadata_type, data, data_id结构化 UI 数据
actionaction_id, action_type, state, titleSession 操作状态
errorscope, code, message, recoverableSession 或 Turn 错误

每个 Part 有稳定 part_id 和不可变 sequence。Tool 状态变化更新同一个 Part,不创建“请求”和“结果”两条重复消息。Tool 与关联 Interaction 的状态在一个检查点事务中提交。

流式与持久化边界

事件负责实时展示
内存负责流式组装
数据库只保存稳定语义检查点

模型 stream 的 start、delta 和 finish 先更新当前 Agent Message 的内存投影,并发布 part / delta Mutation;普通 chunk 不写 SQLite。一个模型 Step 完成时,SDK 校验 Part identity,并用一次事务提交完整 Step 快照。

Tool 执行前后、Interaction 状态变化、Action/Error 和 Message 收口属于不可丢失的语义检查点,可以独立提交。数据库提交后的完整 message Mutation 会替换流式投影。

顺序、版本与重连

每条 Message 有稳定 message_id、不可变 sequence 和递增的 revision。客户端应:

  1. 先订阅并缓冲 Mutation,再读取初始快照。
  2. 对完整 message 快照按 message_id + revision 合并。
  3. 在流连续时应用 part 与 delta。
  4. 断线后重新读取快照,不假定 Delta 可以重放。

未完成 Step 的内存投影在进程中断后可能丢弃;Session 会把遗留的 streaming Agent Message 收口为 done 并追加 Error Part。因此 SQLite 快照始终是重连后的权威事实。

加载更早历史

let page = await session.messages({ limit: 50 });
while (page.has_more && page.next_before_sequence !== undefined) {
  page = await session.messages({
    before_sequence: page.next_before_sequence,
    limit: 50,
  });
  prepend_messages(page.items);
}

默认只返回 visibility: "visible" 的 Message;调试或审计时传入 include_internal: true。摘要属于 Composer Policy 的派生数据,不占 Message sequence,也不会出现在 messages() 中。

完整同步算法见 重连与同步,存储细节见 元数据与落盘。