连接可以跨 Turn 复用,粘性路由令牌却绝不能跨 Turn 复用。Codex 用 Session 级 ModelClient、Turn 级 ModelClientSession 和可转移 WebsocketSession 把这两个看似冲突的需求分开。
具体问题与启用条件
本节回答状态放在哪里以及何时创建、转移和清空。具体增量请求条件在 5.10,Turn State Header 在 5.5。
| 条件来源 | 决定字段或状态 | 对本机制的影响 |
|---|---|---|
| Session 创建 | 有效 Provider、认证、Thread ID | 构造长期 ModelClient |
| 每个 Turn | new_session() | 创建空 OnceLock turn_state |
| 传输复用 | 缓存 WebsocketSession 且连接健康 | 取出连接和请求基线 |
协议、类型与状态所有权
| 状态或协议 | 所有者 | 生命周期 | 关键不变量 |
|---|---|---|---|
| Provider/auth/fallback flag | ModelClientState | 整个 Codex Session | HTTP 回退对后续 Turn 粘滞 |
turn_state | ModelClientSession | 恰好一个 Turn | 一旦设置不变且不跨 Turn |
| 连接、last request/response | WebsocketSession | 可跨 Turn 转移 | 连接关闭时基线失效 |
| Prompt cache key override | ModelClient | Session | 与 Response ID 分离 |
机制调用链如下:
Session 创建 ModelClient
→ Turn 调用 new_session
→ 从共享缓存 take WebsocketSession
→ Turn 内多次 stream/prewarm
→ Drop ModelClientSession
→ 将传输状态放回 ModelClient 缓存
→ 下一 Turn 获得新 turn_state
机制怎样工作
new_session() 本身不联网。它克隆长期客户端、原子地取走缓存中的 WebsocketSession,并新建空 OnceLock<String>。第一个请求才懒建立连接。Turn 内一旦响应头或 metadata 给出 x-codex-turn-state,OnceLock 只接受第一次值,后续请求原样回送。
Turn 结束时 Drop 把 WebsocketSession 放回共享缓存,但不会把 turn_state 一起放回。这样物理连接、上次完整请求和完成响应可被下一 Turn 评估是否复用,同时粘性路由语义重新开始。若回退 HTTP,长期 disable_websockets 原子标志被置位,当前 WebsocketSession 清空,后续 Turn 也不再试同一失败路径。
Python 风格伪代码
这段伪代码保留生产实现中会改变结果的状态、分支和异步边界;认证 SDK、遥测字段和 Rust 所有权样板被折叠为明确的领域对象。
class ModelClient:
def __init__(self, provider: Provider, auth: Auth):
self.provider = provider
self.auth = auth
self.http_fallback = False
self._cached_ws = WebsocketState.empty()
self._lock = Lock()
def new_turn_session(self) -> "ModelTurnSession":
with self._lock:
ws, self._cached_ws = self._cached_ws, WebsocketState.empty()
return ModelTurnSession(self, ws, turn_state=None)
class ModelTurnSession:
async def close(self) -> None:
# turn_state 故意不归还
with self.client._lock:
self.client._cached_ws = self.ws_state
失败、取消与恢复
| 故障或边界 | 已发生的状态 | 对上层的结果 | 能否直接重试 | 恢复动作 |
|---|---|---|---|---|
| 跨 Turn 复用 ModelClientSession | 旧 turn_state 仍存在 | 路由合同被破坏 | 否 | 每 Turn 新建 session |
| 连接已关闭 | last request 可能仍在 | 重新连接 | 是 | 同时清空增量基线 |
| WebSocket 回退 | Session flag 已置位 | 改走 HTTP | 按 HTTP 规则 | 丢弃 WS 状态 |
| Drop 时缓存锁中毒 | 传输状态待归还 | 恢复 mutex 内值 | 不适用 | 避免丢失长期客户端 |
设计取舍与验证
把可复用状态从 Turn 对象中拆出来,比每次重连复杂,也要求 Drop 路径正确。收益是生命周期成为类型边界:连接复用不再意味着所有语义状态都复用。当前只缓存一个 WebsocketSession,避免并发 Turn 争用同一串行连接,但也限制并行连接复用。
| 可验证契约 | 证据方式 | 预期结果 |
|---|---|---|
| 同一物理连接跨 Turn 复用 | 请求捕获统计握手 | 握手一次、Turn 请求多次 |
| turn_state 不跨 Turn | 连续 Turn Header 断言 | 第二 Turn 首请求无旧值 |
| 回退对后续 Turn 生效 | WebSocket fallback 集成测试 | 后续请求直接 HTTP |
Mini Codex 对照
Mini Codex 应实现显式 close() 或异步上下文管理器,不依赖 Python 析构时机。只缓存健康连接;Turn token 存在 Turn session 中。可以先不做跨 Turn last-request 增量,但生命周期分离不能省。
本节边界
本节只确定状态归属。Responses 与 Lite 如何改变请求形状,由 5.4 展开。
评论
登录后即可评论