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 | 用户输入或内部恢复提示 | 无状态字段,写入即完整 |
agent | Agent 输出与运行记录 | 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 | 关键字段 | 说明 |
|---|---|---|
text | text | 用户正文 |
context | tag, context | 带语义标签的模型上下文 |
file | url, media_type, filename | 用户提交的文件 |
data | data_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 | 关键字段 | 说明 |
|---|---|---|
text | text, state | 用户可见输出,streaming -> done |
reasoning | text, state | 推理输出 |
tool | tool_call_id, tool_name, state, input, output | 工具完整生命周期 |
interaction | interaction_id, status, request, response | 审批、提问等异步交互 |
file | url, media_type, filename | Agent 文件结果 |
data | data_type, data, data_id | 结构化 UI 数据 |
action | action_id, action_type, state, title | Session 操作状态 |
error | scope, code, message, recoverable | Session 或 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。客户端应:
- 先订阅并缓冲 Mutation,再读取初始快照。
- 对完整
message快照按message_id + revision合并。 - 在流连续时应用
part与delta。 - 断线后重新读取快照,不假定 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() 中。