一个 Responses 请求同时需要认证、会话关联、Turn 归属、兼容投影和粘性路由,但这些字段不能由多个调用点各自拼装。Codex 先建立一份规范化 metadata 快照,再投影到 HTTP Header 与 WebSocket client_metadata。
具体问题与启用条件
本节关注请求身份和 Header 的所有权与优先级,不展开具体登录刷新算法;401 恢复归入 5.22。
| 条件来源 | 决定字段或状态 | 对本机制的影响 |
|---|---|---|
| 认证方式 | API Key、ChatGPT auth、Provider 自定义认证 | 生成 Authorization/账户等 Header |
| 请求种类 | Turn、Prewarm、Compaction、Memory | 决定 metadata 中是否包含 Turn 身份 |
| Turn 状态 | 已收到 x-codex-turn-state | 后续同 Turn 请求回送 |
协议、类型与状态所有权
| 状态或协议 | 所有者 | 生命周期 | 关键不变量 |
|---|---|---|---|
| CodexResponsesMetadata | TurnContext metadata state | 一次请求快照 | 规范事实源 |
| 认证 Header | AuthProvider | 一次尝试,可刷新 | 调用方不能伪造核心认证字段 |
| 兼容 Header | ModelClient 投影函数 | 一次请求 | 由规范 metadata 生成 |
| turn_state | ModelClientSession | 一个 Turn | 首次值写入后不变 |
机制调用链如下:
TurnContext/Session 身份
→ CodexResponsesMetadata 快照
→ 过滤调用方 reserved keys
→ AuthProvider 添加认证
→ Provider/default/extra headers 按优先级合并
→ HTTP Header 或 WS client_metadata 投影
→ 响应捕获 turn_state
机制怎样工作
规范快照包含 installation、session、thread、turn、window、request kind、父子 Agent、sandbox、workspace、Code Mode 工具名和开始时间。完整 blob 放入 client_metadata["x-codex-turn-metadata"];扁平 client_metadata 和直接 Header 只是兼容投影。调用方额外 metadata 会被过滤,不能覆盖 Codex 保留键。
HTTP 与 WebSocket 的承载不同,但来源相同。HTTP 可直接发送有界的兼容 Header;Code Mode 工具映射可能无限增长,因此只放 client_metadata,不塞进 Header。x-codex-turn-state 是响应给出的粘性路由值:第一次捕获进 OnceLock,后续同 Turn 同时进入请求 Header/WS metadata;新 Turn 创建新锁。Header 合并也有明确优先级,避免 Provider 默认值意外覆盖请求级兼容信息。
Python 风格伪代码
这段伪代码保留生产实现中会改变结果的状态、分支和异步边界;认证 SDK、遥测字段和 Rust 所有权样板被折叠为明确的领域对象。
RESERVED = {"session_id", "thread_id", "turn_id", "x-codex-turn-metadata"}
def build_request_identity(turn: TurnSnapshot, extra: dict[str, str]) -> Identity:
safe_extra = {k: v for k, v in extra.items() if k not in RESERVED}
canonical = canonical_turn_metadata(turn, safe_extra)
return Identity(
client_metadata=project_flat(canonical) | {
"x-codex-turn-metadata": compact_json(canonical)
},
headers=project_bounded_headers(canonical),
)
async def authorize_and_send(identity: Identity, turn_token: str | None):
headers = await auth_provider.headers()
headers.update(provider_headers)
headers.update(identity.headers)
if turn_token is not None:
headers["x-codex-turn-state"] = turn_token
return await transport.send(headers, identity.client_metadata)
失败、取消与恢复
| 故障或边界 | 已发生的状态 | 对上层的结果 | 能否直接重试 | 恢复动作 |
|---|---|---|---|---|
| 调用方覆盖 reserved key | 规范快照未改变 | 额外值被过滤 | 否 | 使用非保留扩展键 |
| Header 值非法 | 该投影字段未插入 | 请求继续或缺兼容信息 | 视字段而定 | 修正编码 |
| 跨 Turn 回送 turn_state | 路由语义污染 | 可能命中错误后端状态 | 否 | 每 Turn 新建 session |
| 401 | 请求未被服务接受 | 进入一次认证恢复 | 有条件 | 刷新认证并重建尝试 |
设计取舍与验证
单一 metadata 快照避免 Header 与 body 各维护一份事实,却需要维护兼容投影。把大型映射限制在 client_metadata 可防止 Header 尺寸失控。保留键过滤牺牲了调用方自由度,换来可相信的会话和审计身份。
| 可验证契约 | 证据方式 | 预期结果 |
|---|---|---|
| 额外 metadata 不能覆盖核心字段 | 构造冲突键并捕获请求 | 服务端看到 Core 值 |
| HTTP 与 WS metadata 对齐 | 双传输请求捕获 | 规范 blob 内容一致 |
| Turn state 只在同 Turn 回放 | 连续请求 Header 测试 | 首次无值、后续有值、新 Turn 清空 |
Mini Codex 对照
Mini Codex 应用不可变 RequestIdentity 保存规范字段,并只允许白名单扩展。认证适配器返回 Header,不应读取或修改对话 History。可以省略产品专用字段,但必须保留 session/thread/turn 与 request kind。
本节边界
身份信息已可安全投影到传输。5.6 将同一请求中的 Prompt、工具与输出控制编译成 JSON body。
评论
登录后即可评论