- use OpenAI fork WebSocket transport and aligned request headers - persist refresh/account failure states and dedupe account imports by identity - document real-chain and risk-control audit results Co-authored-by: Codex <noreply@openai.com>
26 KiB
上游请求链路审计
日期:2026-06-20
状态:已完成审计,并按确认方向推进实现代码。
目标
本轮只审计 OpenAI/Codex 上游请求链路:
/backend-api/codex/responsesHTTP/SSE/backend-api/codex/responsesWebSocket/backend-api/codex/responses/compact/backend-api/codex/models/backend-api/wham/usage这类账号/额度辅助入口
不把项目扩展成通用 proxy、VPN 或多 provider 网关;Anthropic、Gemini、OpenRouter、Ollama 等旧项目内容不进入本轮设计。
结论
- WebSocket 连接应使用 OpenAI 原版锁定的
openai-oss-forks/tokio-tungstenite完整握手,不再手写实际 opening request。 - WebSocket 帧压缩不应长期维护自写
PerMessageDeflateStream,已切到 fork 内建permessage-deflate协商和解压/压缩状态。 opening_request_text()继续作为审计快照保留;真实发送字节由 fork 根据 request headers 和WebSocketConfig生成。- HTTP/SSE header 不应按 WebSocket 抓包删到最小集合。原版和旧 TS 都会带 Codex compatibility headers,例如
x-codex-turn-state、x-codex-window-id、x-codex-parent-thread-id、x-openai-subagent、x-codex-installation-id。 - 默认指纹先按 TS
config/default.yaml与config/fingerprint.yaml对齐;真实 Linux 抓包和 macOS 解包信息只作为后续验证证据,不直接覆盖 TS 基线。 - TLS 层当前 Rust 和原版 Rust 都走 rustls/native roots/custom CA 思路,不能从 HTTP 抓包直接证明 TLS 指纹完全一致。先对齐原版 Rust 依赖和请求头,再用真实链路继续验证。
- Chat 请求的模型后缀、默认 reasoning、默认 service tier、
fast -> priority和展示模型名先以 TS 当前实现为准,不从抓包或经验补规则。 - 账号导入时的
chatgpt-account-id补齐先按 TS:导入/持久化从 JWT claim 提取,发送链路只消费已解析出的 account id;不能在请求头层随意发空值或测试占位值。
基线规则
本轮不再“猜着写”。所有请求链路变更必须能对应到以下至少一个来源:
- TS 当前实现:
/home/zyy/桌面/Codes/codex-proxy - OpenAI 原版 Rust:
/tmp/openai-codex-src/codex-rs - 新的真实链路证据,并且已和 TS / 原版对比过
docs/openai-res.txt 的真实抓包用于验收和发现差异,但不能单独反推出全局白名单。比如该抓包的 WS opening 没有 x-codex-installation-id,而 TS 当前 HTTP/WS 统一 header map 会发送它;本轮先保持 TS 行为,再用新抓包验证。
证据索引
真实抓包
docs/openai-res.txt 中 WebSocket opening header 的顺序和集合是:
Host
Connection
Upgrade
Sec-WebSocket-Version
Sec-WebSocket-Key
chatgpt-account-id
authorization
user-agent
originator
openai-beta
x-client-request-id
session-id
thread-id
x-codex-window-id
x-codex-turn-metadata
sec-websocket-extensions
其中 sec-websocket-extensions 为:
permessage-deflate; client_max_window_bits
抓包中 /backend-api/wham/usage 只有较少 HTTP header:user-agent、authorization、chatgpt-account-id、accept、cookie。这说明不同上游入口的 header 集合不同,不能把 WS opening 的最小集合直接套到所有 HTTP 请求。
当前 Rust 实现
调整前 workspace 依赖:
tokio-tungstenite = 0.29.0,未启用 fork 的deflate能力。reqwest = 0.12.28,使用 rustls native roots,启用 gzip/brotli/zstd/deflate/http2。rustls = 0.23.36,tokio-rustls = 0.26.4。
当前 WebSocket opening 审计快照仍按真实抓包顺序生成:
crates/adapters/src/codex/websocket/connect.rsCodexWebSocketConnection::responsesopening_request_bytes
当前 WS business headers:
chatgpt-account-idauthorizationuser-agentoriginatoropenai-betax-client-request-idsession-idthread-idx-codex-window-idx-codex-turn-metadatax-openai-subagent,仅在 request metadata 提供合法 subagent 时发送
调整前仍有自写 deflate 层:
crates/adapters/src/codex/websocket/deflate.rsPerMessageDeflateStreamflate2::Decompress- 支持 server context takeover 后,真实 WS stream 已跑通
这说明“旧修复可用”,但维护面仍然偏大:自写代码需要自己处理 frame parsing、RSV1、continuation、context takeover、错误传播和后续 tungstenite 行为变化。当前实现已删除这层自写协议代码,并改用 fork 的 connect_async_tls_with_config 完成握手、扩展协商和 response 校验。
OpenAI 原版 Rust
原版路径:
/tmp/openai-codex-src/codex-rs/Cargo.toml/tmp/openai-codex-src/codex-rs/codex-api/src/endpoint/responses_websocket.rs/tmp/openai-codex-src/codex-rs/core/src/client.rs/tmp/openai-codex-src/codex-rs/core/src/responses_metadata.rs
原版锁定 fork:
tokio-tungstenite = { version = "0.28.0", features = ["proxy", "rustls-tls-native-roots"] }
tungstenite = { version = "0.27.0", features = ["deflate", "proxy"] }
[patch.crates-io]
tokio-tungstenite = { git = "https://github.com/openai-oss-forks/tokio-tungstenite", rev = "132f5b39c862e3a970f731d709608b3e6276d5f6" }
tungstenite = { git = "https://github.com/openai-oss-forks/tungstenite-rs", rev = "9200079d3b54a1ff51072e24d81fd354f085156f" }
原版 WebSocket 连接:
- 使用
connect_async_tls_with_config WebSocketConfig.extensions.permessage_deflate = Some(DeflateConfig::default())false表示不禁用 Nagle- 自定义 CA 存在时给 WebSocket 显式 rustls connector,避免 WS 和 HTTP 的 CA 策略不一致
原版 Responses header 语义:
x-client-request-id使用 thread idsession-id/thread-id来自 session metadatax-codex-window-id、x-codex-turn-metadata、x-codex-parent-thread-id属于 compatibility headersx-codex-turn-metadata的 canonical 来源是client_metadata["x-codex-turn-metadata"],HTTP/ws 直接 header 是兼容投影x-openai-subagent支持 review、compact、memory、collab 等内部语义
旧 TS 项目
旧项目路径:
/home/zyy/桌面/Codes/codex-proxy/src/proxy/codex-api.ts/home/zyy/桌面/Codes/codex-proxy/src/proxy/ws-transport.ts/home/zyy/桌面/Codes/codex-proxy/src/fingerprint/manager.ts/home/zyy/桌面/Codes/codex-proxy/config/fingerprint.yaml/home/zyy/桌面/Codes/codex-proxy/src/proxy/cookie-jar.ts
有价值的差异线索:
src/fingerprint/manager.ts:91-113的buildHeaders明确只做这些事:写Authorization,用传入accountId或 JWT claim 补ChatGPT-Account-Id,写originator,合并buildRawDefaultHeaders,最后按fingerprint.header_order排序。buildRawDefaultHeaders只展开User-Agent、sec-ch-ua,再合并fingerprint.yaml的default_headers。src/proxy/codex-api.ts:createResponseViaHttp和createResponseViaWebSocket在统一 base headers 上追加OpenAI-Beta、x-openai-internal-codex-residency、x-client-request-id、x-codex-installation-id、session/window/context headers。ws-transport.ts使用 Nodews完整握手,业务 headers 通过构造参数传入;没有手写 raw opening,也没有手写Sec-WebSocket-Extensions。buildWsConstructorOpts只传{ headers }和可选 agent;ws@8.19.0客户端默认perMessageDeflate: true,因此扩展 offer 由库生成。ws@8.19.0的WebSocketServer默认perMessageDeflate: false。旧 TS 测试 helper 若未显式打开 server deflate,不代表真实客户端不会发送扩展 offer。- WebSocket 升级响应头通过
upgrade事件采集后转回 SSE response headers。 - WebSocket 打开后发送
response.createJSON 文本帧,并用 25s ping 保活。 buildConversationIdentity用账号作用域派生cp_...conversation id 和cw_...window id。- HTTP 与 WS 都会补
OpenAI-Beta: responses_websockets=2026-02-06。 - HTTP/SSE 会带
Accept: text/event-stream,compact 不带。 x-codex-installation-id同时出现在 header 和client_metadata。x-codex-turn-metadata、x-codex-beta-features、x-responsesapi-include-timing-metrics、Version、x-codex-parent-thread-id都是条件性透传。- cookie 自动捕获只允许
cf_clearance,不自动持久化__cf_bm。 CodexApi.captureCookies()的调用点在 transport response 返回后立即执行:HTTP SSE 和 compact 都在状态码判断前 capture;WebSocket 升级响应 header 由ws-transport.ts采集后随 response 返回;warmup 路径也会 capture,但当前账号导入链路禁用 warmup,普通getUsage()不做 capture。fingerprint.yaml是 HTTP header 指纹基线,不是 TLS/JA3 指纹;其中定义 UA 模板、认证域白名单、header 顺序和默认浏览器态 header。auth_domains/auth_domain_exclusions在当前 TS HEAD 只进入 schema,未找到请求发送链路使用点。src/auth/account-registry.ts:addAccount和src/auth/account-persistence.ts:normalizeAccountEntries都从 JWT 中回填accountId、userId、planType、email。因此 Rust 导入阶段刷新并解析 claims 后写入account_id,是和 TS 一致的方向;请求发送阶段不需要引入proxyApiKey、proxy_api_key等 sub2api 私有字段。
不应迁移的内容:
- 多 provider 适配逻辑。
- 通用 proxy/VPN 语义。
- 旧迁移兼容测试。
- 与 OpenAI/Codex 无关的历史抽象。
三方对比
WebSocket opening
| 项目 | 生成方式 | 风险 |
|---|---|---|
| 真实抓包 | Codex Desktop 发送的最小 opening header | 当前最可信的行为目标 |
| 当前 Rust | fork tokio-tungstenite 完整握手 |
与原版一致;header 顺序由库生成 |
| OpenAI 原版 Rust | fork tokio-tungstenite 自动握手 |
deflate 协商和状态可靠 |
| 旧 TS | Node ws transport |
同样由库生成 opening 并处理 permessage-deflate |
判断:TS 版和 OpenAI 原版都不是手写 raw opening。为了拿到 fork 内部 negotiated extension state,Rust 当前也应使用 fork 完整握手;审计快照只用于排查差异。
WebSocket permessage-deflate
| 方案 | 优点 | 风险 |
|---|---|---|
当前自写 PerMessageDeflateStream |
已真实跑通;不改依赖 | 协议维护面大,容易漏 frame/extension 边界 |
OpenAI fork tungstenite |
与原版同源;支持协商和上下文 | 引入 git patch;版本从当前 0.29 回到原版 0.27/0.28 组合 |
旧 TS ws@8.19.0 |
客户端默认开启 perMessageDeflate,无需项目手写扩展头 |
测试 server 默认关闭 deflate,不能用测试 helper 默认值反推真实客户端 opening |
判断:已切 fork,删除自写 deflate。运行时风险小于继续维护自写协议层。
HTTP/SSE header
当前 Rust HTTP/SSE 已覆盖主要 Codex 头:
authorizationchatgpt-account-idoriginatoruser-agentcontent-typeaccept: text/event-streamopenai-betax-openai-internal-codex-residencyx-client-request-idx-codex-installation-idsession_idx-codex-window-idx-codex-turn-statex-codex-turn-metadatax-codex-beta-featuresx-responsesapi-include-timing-metricsversionx-codex-parent-thread-idx-openai-subagentcookie
注意:
session_id是 TS 当前 WebSocket/HTTP SSE 都会发送的兼容字段;v2.0.78-beta.15c49d6和当前dev/v2.0.80都没有把它转换为session-id/thread-id。- WS opening 真实抓包和 OpenAI 原版 Rust 用的是
session-id/thread-id,不是session_id。这是后续真实链路验证项,本轮不在 Rust 侧猜测改写。 - 当前 WS opening 按 TS
buildHeaders+applyCodexContextHeaders语义从统一 header map 投影:过滤 HTTP/SSE 专用的content-type、accept,保留session_id、fingerprint 默认头、cookie、x-openai-internal-codex-residency、x-codex-installation-id和各类 Codex context 条件头。 docs/openai-res.txt的一次真实 WS opening 抓包是最小集合,但 TS 当前实现会发送更多兼容/条件头;本轮基线先与 TS 保持一致,不用手写白名单猜测。x-codex-turn-metadata、x-codex-beta-features、x-responsesapi-include-timing-metrics、x-codex-window-id、x-codex-parent-thread-id的 canonical 来源允许是client_metadata;Rust 已按 TSfirstRequestString语义补齐“直接字段优先,metadata 兜底”。这会影响 account-scoped window id、HTTP header 投影和 WS business header 投影。x-openai-subagent在 TS WebSocket 和 HTTP 路径都会从client_metadata规范化后写入 header;OpenAI 原版 Responses stream 也会写该 header。Rust 已补齐 WS header 投影。
Chat 模型选项
TS 参照:
src/translation/openai-to-codex.ts在 Chat 翻译阶段解析模型名后缀,并按“显式请求字段 > 模型后缀 > 配置默认值”的顺序设置reasoning.effort和service_tier。src/routes/chat.ts在翻译后判断codexRequest.reasoning?.effort,因此后缀或配置默认 reasoning 也会影响 Chat stream 输出格式。src/routes/chat.ts使用buildDisplayModelName(parseModelName(req.model))作为 OpenAI Chat 响应中的展示模型名。src/routes/shared/proxy-request-preparation.ts在带 reasoning 且 include 为空时补["reasoning.encrypted_content"]。src/proxy/codex-api.ts的normalizeServiceTierForUpstream在真正发 HTTP/WS 前把fast映射为priority。
Rust 对齐结果:
/v1/chat/completionshandler 在translate_chat_to_codex后立即调用apply_response_model_options,让 suffix/config/default 对include_reasoning和 Chat stream 可见。ChatDispatchService::complete也调用同一个函数兜底,避免非 handler 调用绕过模型选项。- Chat stream 响应模型名改为
ModelCatalog::build_display_model_name(parsed_model),与 TS display model 规则一致。 apply_response_model_options继续复用 Responses 路径的fast -> priority和 reasoning include 规则,避免 Chat/Responses 分叉。
WebSocket payload
TS src/proxy/codex-api.ts:createResponseViaWebSocket 的 wsRequest 构造顺序是:
type
model
instructions
input
store
stream
previous_response_id?
reasoning?
tools?
tool_choice
parallel_tool_calls
text?
service_tier?
prompt_cache_key?
include?
client_metadata
对齐判断:
instructions使用 TS 语义request.instructions ?? "",空字符串也必须发送;真实上游会在缺失该字段时返回 400。tool_choice和parallel_tool_calls即使客户端未传也按 TS 补默认值:auto、true。reasoning、tools、include不再发送旧 Rust 快照里的null/ 空数组;TS 只在有值或非空数组时赋值。generate不属于 TS 当前WsCreateRequest,WebSocket payload 不按 Rust 旧测试快照额外发送。docs/openai-res.txt中原版抓包的 payload 字段集合可作为后续原版对照证据,但本轮先以 TS 当前实现为 Rust 基线。
指纹审计
TS 当前 config/fingerprint.yaml:
user_agent_template: "Codex Desktop/{version} ({platform}; {arch})"
auth_domains: ["chatgpt.com", "*.chatgpt.com", "openai.com", "*.openai.com"]
auth_domain_exclusions: ["ab.chatgpt.com"]
default_headers:
Accept-Encoding: "gzip, deflate, br, zstd"
Accept-Language: "en-US,en;q=0.9"
sec-ch-ua-mobile: "?0"
sec-ch-ua-platform: '"macOS"'
sec-fetch-site: "same-origin"
sec-fetch-mode: "cors"
sec-fetch-dest: "empty"
对应 TS 发送逻辑:
buildHeaders/buildHeadersWithContentType负责展开 UA、补sec-ch-ua、合并默认 headers 并按header_order排序。sec-ch-ua来自client.chromium_version,不是写在fingerprint.yaml里。auth_domains/auth_domain_exclusions当前只在 config schema/loading 里出现,没有发现实际发送链路使用点,不能作为 Rust 侧必须新增的运行时逻辑。
对 Rust 的影响判断:
- 这个 YAML 可以作为“默认 HTTP header 指纹基线”的证据,不应被理解成 TLS/JA3/ALPN 指纹配置。
- Rust 侧当前不需要引入同名 YAML 运行时配置;先保持
Fingerprint::default_codex_desktop()这类强类型默认值,减少外部配置漂移面。 header_order在 TS 配置里是原始大小写,Rust 内部统一用小写 key 排序;这不影响实际 HTTP header 语义,但真实抓包排查时要按 wire 输出核对。auth_domains/auth_domain_exclusions如果后续要启用,应该只用于 cookie/auth header 作用域校验,不能自动扩大成通用 proxy 域名白名单。
TS 默认指纹基线:
originator: Codex Desktop
app_version: 26.519.81530
build_number: 3178
platform: darwin
arch: arm64
chromium_version: 146
user-agent: Codex Desktop/{version} ({platform}; {arch})
sec-ch-ua-platform: "macOS"
真实 Linux 抓包中的 UA:
Codex Desktop/0.140.0 (Debian 13.0.0; x86_64) gnome-terminal (Codex Desktop; 26.609.71450)
macOS Codex Desktop 解包与探测来源:
/home/zyy/桌面/Codes/codex-desktop-linux/codex-app/resources/app.asar/tmp/codex-desktop-fingerprint/fingerprint-report.md
确认到的 macOS arm64 UA:
Codex Desktop/26.609.71450 (Macintosh; Intel Mac OS X; arm64)
风险:
- TS 默认指纹、真实 Linux 抓包和 macOS 解包信息不一致,不能在没有 TS 证据时自行混合。
- HTTP 默认 header 里仍有 macOS
sec-ch-ua-platform,而真实 WS opening 不带sec-ch-*。 - 如果上游把 UA、account id、cookie、TLS、IP、timing 关联起来,指纹漂移会增加风控风险。
当前对齐原则:
- 先以 TS
config/default.yaml+config/fingerprint.yaml为 Rust 默认基线:版本26.519.81530、构建3178、平台darwin、架构arm64、Chromium146。 - 后续仍应支持从真实导入/配置中持久化当前使用的 Codex Desktop 指纹。
- WS opening 继续不带
sec-ch-*。 - HTTP/SSE 是否保留
sec-ch-*需要用真实抓包继续确认;在没有 HTTP/codex/responses完整抓包前,不做大删减。
Rust 当前状态:
crates/core/src/gateway/fingerprint.rs已硬编码同一组默认 headers 和 header 顺序。- 默认 UA 已对齐 TS 展开结果:
Codex Desktop/26.519.81530 (darwin; arm64)。 - auto-update 没有 extracted Chromium 时保留已有 Chromium;首次无历史值时使用 TS 默认
146,不再回退到旧142。 crates/adapters/src/codex/client.rs的 HTTP/SSE 路径会发送sec-ch-ua和fingerprint.default_headers;WS opening 路径按 TS 当前实现从统一 Codex header map 投影,过滤 HTTP/SSE 专用content-type、accept,保留session_id,不按抓包最小集合手写白名单。
TLS 与网络安全面
当前 Rust HTTP:
reqwest- rustls native roots
- 自定义 CA 支持
.no_proxy()- 可配置
force_http11 - 默认保留 HTTP/2
当前 Rust WS:
- fork
connect_async_tls_with_config - rustls native roots/custom CA
- 不禁用 Nagle
- opening response 和 permessage-deflate 协商由 fork 校验
OpenAI 原版 WS:
- fork
tokio-tungstenite - rustls native roots
- 自定义 CA connector
- 不禁用 Nagle
- 内建 permessage-deflate
审计判断:
- 目前与原版 Rust 的 WS/TLS 策略方向一致。
.no_proxy()符合本项目“不做 proxy/VPN”的定位。- 仅凭
docs/openai-res.txt不能判断 TLS ClientHello/JA3/ALPN 是否完全一致。 - 如果后续真实链路仍被风控,需要单独抓 TLS 指纹和 ALPN,而不是继续猜 header。
Cookie 审计
当前 Rust 与旧 TS 的关键策略一致:
- 自动捕获只持久化
cf_clearance。 - 不自动持久化
__cf_bm。 - 请求时按 domain/path/expiry 过滤 cookie。
- Cloudflare challenge 或 path-block 时会清理账号 cookie 并进入冷却/禁用路径。
- HTTP/SSE、compact、stream 和 WebSocket 升级成功响应都会把上游
Set-Cookie交给 runtime capture。 - HTTP/SSE 和 compact 的非 2xx 响应会通过
CodexClientError::Upstream.set_cookie_headers带回 cookie,再由 runtime capture;这对齐 TS “收到 transport response 先 capture,再按状态码分类”的时机。 - WebSocket 错误帧本身没有 HTTP response headers,按空 cookie 列表处理;WebSocket opening 失败如果带
Set-Cookie,会从握手响应头传回。
这个方向合理。__cf_bm 与 IP、UA、TLS、时间强绑定,重放可能比不带 cookie 更差。
使用 OpenAI fork 的风险
收益:
- 与 OpenAI 原版 Codex Rust 同源。
- 解决真实 WS
permessage-deflate的长期维护问题。 - fork 已有 extension negotiation、context takeover、压缩/解压测试。
- 避免手写 opening 后无法访问 fork 私有 negotiated extensions 状态的问题。
风险:
- 引入 git patch,依赖供应链需要固定 rev。
- 当前项目
tokio-tungstenite是 0.29,原版组合是tokio-tungstenite 0.28+tungstenite 0.27,存在 API 细节回退风险。 [patch.crates-io]会影响整个 workspace 内所有tungstenite/tokio-tungstenite使用点。- fork 不是 crates.io 正式发布版本,后续升级需要跟 OpenAI 原版同步审计。
风险控制:
- 固定到原版相同 rev。
- 只改 WS transport 相关代码,不顺手改账号、导入、日志。
- 保留 WS opening 审计快照测试。
- 新增最小的 deflate context takeover 行为测试,替代自写 deflate helper 测试。
- 跑真实
gpt-5.5HTTP/SSE + WS stream 复测。
本轮实现项
- Cargo 依赖切到 OpenAI 原版 fork。
connect.rs改用 forkconnect_async_tls_with_config完整握手。- 通过
WebSocketConfig.extensions.permessage_deflate = Some(DeflateConfig::default())让 fork 生成 offer 并校验上游 agreed extensions。 - 删除
crates/adapters/src/codex/websocket/deflate.rs和相关 type alias。 - 保留 WS opening 审计快照测试。
- 适配器层 deflate 行为由 fork 内建握手覆盖,不再维护自写压缩帧。
- 默认 Codex Desktop 指纹先对齐 TS 配置:
26.519.81530 / 3178 / darwin / arm64 / Chromium 146。 - OpenAI Responses 翻译层补齐 TS
firstRequestString语义:直接字段为空时从 string-onlyclient_metadata读取 Codex context 字段。 - WebSocket header 投影回到从统一 Codex header map 派生,过滤 HTTP/SSE 专用头,保留 TS
createResponseViaWebSocket会发送的session_id、x-codex-turn-state、x-codex-installation-id、x-openai-internal-codex-residency、x-openai-subagent等条件头。 /v1/chat/completions的stream:true复用 Responses stream 调度链路,并按 TSstreamCodexToOpenAI语义输出 OpenAIchat.completion.chunkSSE。- WebSocket
response.createpayload 按 TS 字段顺序和可选字段规则序列化:保留空instructions,省略空tools/include和reasoning:null。 /v1/chat/completions的模型后缀、reasoning、service tier 和 display model 已按 TS Chat 路径对齐;定向测试固定了gpt-5.5-high-fast上游发送为model=gpt-5.5、service_tier=priority、reasoning.effort=high,并验证 stream reasoning delta 会转为 OpenAIreasoning_content。- Chat 非流和 compact 成功路径已接入结构化事件日志,复用现有
record_response_event,按 route 区分v1.chat/v1.response并记录 accountId、usage、rate-limit headers;chat 非流失败耗尽和 compact 终态失败路径补充level=error、failureClass、exhaustedCount、upstreamError。 - Responses stream 启动阶段终态失败已接入结构化事件日志:账号耗尽、dirty quota 校验达到最大次数、模型不支持重试耗尽、首帧前空响应 / 缺 completion / SSE 解析失败都会记录
stream=true、transport、failureClass,与 TSrecordStreamCloseEvent的排查目标保持一致,但落到本项目现有event_logs。 - cookie capture 时机按 TS 补齐:业务响应成功、stream 启动成功、compact 成功、HTTP/compact upstream error、WebSocket opening error 都会消费上游
Set-Cookie,底层仍只允许自动持久化cf_clearance。
验证顺序
cargo fmt。cargo check -p codex-proxy-runtime -p codex-proxy-server。cargo test -p codex-proxy-adapters --test codex。cargo test -p codex-proxy-server --test openai_chat_upstream openai_chat_routes::。cargo test -p codex-proxy-core --test protocol protocol_openai_chat:: -- --nocapture。- 重启服务,用自然请求文本和
gpt-5.5跑真实 HTTP/SSE、WS 和 chat stream。
已完成的真实验证:
/v1/chat/completions+stream:true:请求9805212d-401f-4a10-a8f1-3d5bc422299e返回 HTTP 200、text/event-stream、OpenAIchat.completion.chunk、final usage 和data: [DONE],无 error SSE。/v1/chat/completions+stream:true日志复测:请求dc22ea52-9f74-428f-8fe5-3b1a3def0114返回 HTTP 200、text/event-stream、OpenAIchat.completion.chunk、final usage 和data: [DONE],管理端事件为kind=v1.chat、route=/v1/chat/completions、metadata.apiKind=chat、metadata.completed=true。
真实请求验证时避免 test、probe、real_chain 这类显眼测试语义,请求 ID 使用 UUID 形态,请求文本使用正常用户表达。
后续仍需审计
- HTTP
/codex/responses的真实抓包 header,需要和当前 Rust 的 HTTP/SSE 逐项比对。 - 原版 attestation header 何时生成;当前项目是否需要支持,不能盲目伪造。
x-openai-internal-codex-residency在当前真实账号链路中的必要性。versionheader 的真实来源和是否仍被上游使用。- TLS ClientHello/ALPN 是否需要与原版二进制进一步对齐。
- 账号导入时是否能补齐真实
account_id、指纹、installation id、cookie 状态。
当前建议
- WS opening/deflate 使用 OpenAI fork 完整握手。
- HTTP/SSE 保留 Codex compatibility headers,不按 WS 抓包全删。
- 后续真实链路继续以
gpt-5.5、自然请求文本、真实账号状态为准。