实时订阅
消费统一 SessionMutation 流,渲染流式文本、工具状态、Turn 与 Session 变化
实时订阅
session.subscribe() 是 Session 唯一的实时入口。它只广播订阅建立之后的变化,不缓存、不回放,也不提供背压控制。
const unsubscribe = session.subscribe((mutation) => {
apply_mutation(mutation);
});
// 不再需要时必须取消,避免重复渲染和内存泄漏。
unsubscribe();订阅回调可以是异步函数。单个订阅者抛错或 reject 不会回滚已持久化状态,也不会阻塞其他订阅者。
五类 Mutation
所有 Mutation 都有 mutation_id、session_id 与 created_at。
variant | type | 用途 | 是否带 Message revision |
|---|---|---|---|
message | `user | assistant | action |
part | `text | reasoning | tool |
delta | `text | reasoning` | 新增的原始文本片段 |
turn | `start | finish` | Turn 执行生命周期 |
session | title | Session 自身属性变化 | 否 |
message、part 和 delta 都在对应完整 Message 或 Assistant 草稿持久化成功后才发布。Mutation envelope 本身不写入 active.jsonl 或 Segment。
用 Delta 流式显示文本
session.subscribe((mutation) => {
if (mutation.variant !== "delta" || mutation.type !== "text") return;
append_text({
message_id: mutation.message_id,
part_id: mutation.part_id,
delta: mutation.delta,
});
});Delta 是新增片段,不是累计全文。不要把 delta 当成完整 Part 覆盖 UI;也不要根据文本内容推断顺序,使用 message_id、part_id 与 revision。
用完整快照纠正状态
网络、渲染批处理或 UI 切换可能让客户端错过自己维护的中间状态。message 与 part 是纠正状态的完整快照:
function apply_mutation(mutation: SessionMutation): void {
if (mutation.variant === "message") {
upsert_message(mutation.message, mutation.revision);
return;
}
if (mutation.variant === "part") {
upsert_part(mutation.message_id, mutation.part, mutation.revision);
return;
}
if (mutation.variant === "delta") {
append_part_delta(mutation.message_id, mutation.part_id, mutation.delta);
}
}客户端应保存每个 Message 已应用的最大 revision。低于或等于当前 revision 的 message / part 快照可以忽略,避免旧事件覆盖新状态。
Turn 与标题
session.subscribe((mutation) => {
if (mutation.variant === "turn" && mutation.type === "start") {
set_turn_running(mutation.turn_id);
}
if (mutation.variant === "turn" && mutation.type === "finish") {
set_turn_finished(mutation.turn_id, mutation.status, mutation.error);
}
if (mutation.variant === "session" && mutation.type === "title") {
set_session_title(mutation.title);
}
});turn.finish 的状态为 completed、failed 或 stopped。它和 turn.finished 共同描述同一最终结果:前者适合订阅 UI,后者适合控制流。
工具 Part 的状态机
同一个 Tool Part 以同一个 part_id 更新:
input-streaming -> ready -> approval-required -> running -> completed
\-> failed
ready ----------------------------------------------> runninginput-streaming 表示模型仍在输出工具参数;ready 表示输入完整但工具尚未执行。UI 应以 tool_call_id 或 part_id 做稳定 key,不要每次收到 Part 就新增一项。
审批的处理方式见 工具审批。
生命周期边界
unsubscribe() 只停止未来事件,不会停止 Session、Turn 或工具。路由切换、组件卸载和 Session 切换时都应调用它;切换回该 Session 后重新加载快照并重新订阅。
断线恢复不能只靠重新订阅,见 重连与同步。