Sessions

实时订阅

消费统一 SessionMutation 流,渲染流式文本、工具状态、Turn 与 Session 变化

实时订阅

session.subscribe() 是 Session 唯一的实时入口。它只广播订阅建立之后的变化,不缓存、不回放,也不提供背压控制。

const unsubscribe = session.subscribe((mutation) => {
  apply_mutation(mutation);
});

// 不再需要时必须取消,避免重复渲染和内存泄漏。
unsubscribe();

订阅回调可以是异步函数。单个订阅者抛错或 reject 不会回滚已持久化状态,也不会阻塞其他订阅者。

五类 Mutation

所有 Mutation 都有 mutation_idsession_idcreated_at

varianttype用途是否带 Message revision
message`userassistantaction
part`textreasoningtool
delta`textreasoning`新增的原始文本片段
turn`startfinish`Turn 执行生命周期
sessiontitleSession 自身属性变化

messagepartdelta 都在对应完整 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_idpart_idrevision

用完整快照纠正状态

网络、渲染批处理或 UI 切换可能让客户端错过自己维护的中间状态。messagepart 是纠正状态的完整快照:

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 的状态为 completedfailedstopped。它和 turn.finished 共同描述同一最终结果:前者适合订阅 UI,后者适合控制流。

工具 Part 的状态机

同一个 Tool Part 以同一个 part_id 更新:

input-streaming -> ready -> approval-required -> running -> completed
                                                   \-> failed
ready ----------------------------------------------> running

input-streaming 表示模型仍在输出工具参数;ready 表示输入完整但工具尚未执行。UI 应以 tool_call_idpart_id 做稳定 key,不要每次收到 Part 就新增一项。

审批的处理方式见 工具审批

生命周期边界

unsubscribe() 只停止未来事件,不会停止 Session、Turn 或工具。路由切换、组件卸载和 Session 切换时都应调用它;切换回该 Session 后重新加载快照并重新订阅。

断线恢复不能只靠重新订阅,见 重连与同步