Repository navigation
fix: preserve Codex provider across API fallback - #8507
Conversation
loopx-agent
left a comment
There was a problem hiding this comment.
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · max
审查 head:5c8c2caef8c550efcbacf21aca375a97e202f1aa
[P2] 切换账号时保留显式自定义 provider。 切到订阅账号时,Accounts.switch 就会把退出的 API 槽改写成 openai。这会丢失已保存的显式网关选择。用同一组隔离 fixture 走真实账户切换和 codex exec resume:主干恢复同一会话并请求原网关;当前 head 在发送请求前报 modelProviderMismatch。请把迁移限制在明确属于受管旧 fallback 的槽,保留显式自定义槽的写回、恢复和模型目录。
动机
在 Codex 原生账号间切换的用户,需要保留已经绑定到会话的实际 provider,继续原对话,并将请求送到为当前账号选定的入口。旧版在已登记的自定义 API 槽切走和切回时保留网关。新版在切走时已把备份槽的 provider 写成 openai;切回后,之前自定义 provider 的会话无法恢复。相同的隔离 fixture 用真实账户事务和原生 Codex CLI 在主干成功恢复同一会话并命中原网关;当前 head 在发出任何请求前就以 modelProviderMismatch 失败。不更改已安装的 provider、真实账户、配额或外部模型流量;只验证受管旧 fallback 与显式自定义槽的区别。交付前须把内置路由修正限定在可证明为受管旧 fallback 的槽,同时保留显式自定义网关及其原会话。
改动思路
复用原有账号事务、锁与回滚;修正受管旧 fallback 的 provider 映射即可。把所有 API 槽都归一化会重写用户选择,并且让已有对话无法恢复。当前 PR 的合理边界是让原本绑定内置 provider 的会话继续走受管 fallback;显式的自定义账号槽仍按其绑定的 provider 切换。 对照已接受的 packages/loopx-codex-provider-routing/NATIVE.md(744f8db4cb95b17f3736f99a7e60a995b19b6ecb):selection restores that provider 对显式自定义槽不再满足;built-in openai 对受管旧 fallback 的路由目标已有实证;readback and rollback 由既有事务保留,相关测试通过。NATIVE 文档新增迁移说明时也需消除和原约定的冲突。
具体改动
builtin_api_auth按 API 类型做归一化,没有区分已绑定的显式网关和旧 fallback。Accounts.switch同时归一化目标与退出槽,事务会保存改写后的退出槽。file_config再把恢复路径设置成内置 provider 与 endpoint override。测试也改成接受原来显式槽被归一化的行为。- 独立执行了匹配的两条路线:受管旧 fallback 在当前 head 用内置
openai和 endpoint override;显式自定义槽在切换之前两端均能请求原网关,切换后仅主干保持原 provider 并恢复同一会话。当前 head 73 个相关测试及 28 个子测试通过,标准预合并 5 项直接检查通过,但新增期望未覆盖已绑定会话的恢复。
对主干的风险
已验证的影响是显式自定义 API 槽的 provider 被持久化改写,造成原会话无法继续;只看显示名称、配置可解析或账户事务成功都发现不了。实际主干合并演练没有冲突,但该行为回归仍阻止批准。请补齐“订阅会话继续使用受管 fallback”和“自定义会话切走再切回仍命中原网关”两条独立回归,并检查切走后的槽 readback。
我的整体评价
保留内置会话的修复方向,收窄迁移边界后再审。该差异未修改 App、Lark 或渲染层;核验的是切换账号后,在真实 Codex CLI 恢复原会话的路径。未测安装态扩展、真实登录、App UI 或付费流量;原生 CLI 仅连接两个隔离 loopback 服务。
English verdict: REQUEST_CHANGES — preserve explicitly bound custom API-slot providers across account switching and keep built-in normalization scoped to managed legacy fallback.
Keep subscription and managed relay routes on the built-in openai provider identity via openai_base_url, migrating legacy loopback relay provider IDs when selected, while preserving and restoring explicit custom Responses gateway providers across account switching. Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
5c8c2ca to
c37894c
Compare
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · max
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
[P2] 保留共用中转地址的显式 provider 绑定。 主干恢复同一个会话,回环收到带自定义头和参数的请求。当前提交切走时把保存槽改为 openai,切回后 CLI 退出 1 并报告 modelProviderMismatch,回环收到零次恢复请求。 请按可验证的槽来源判定受管旧中转,保留显式绑定,并补上同地址下切走再切回的原生恢复测试。 变更行
动机 / Motivation
使用显式 Responses 网关的 Codex 用户,在订阅和 API 槽之间切换时需要继续原会话。 网关和受管中转共用一个本地地址时,主干能切回原 provider 并恢复会话。本次提交在切走时改写了保存的 provider,切回后 Codex 在发送请求前拒绝恢复。 已验证内置 openai 会话经过受管旧中转迁移后仍能继续。显式自定义网关需要保留已保存的 provider 绑定。 复验覆盖本地账号事务和 Codex CLI,使用合成账号与回环 Responses 服务。App UI、已安装扩展和真实登录仍待验证。
改动思路 / Approach
账号槽的配置读取、写回和回滚由原生账号事务负责。端点地址相等无法确定槽的来源和用户选择。 本 PR 应修复内置会话经过受管旧中转时的恢复,并保留已有和新建显式槽的 provider 绑定。 可以保留原账号锁和原子事务,在同一处收窄迁移判定。Codex 支持为内置 provider 设置地址覆盖,显式 provider 还能携带自己的认证、请求头和查询参数。本轮用同一套合成槽对照两版写回结果,并经原生 CLI 恢复原会话。Codex 配置文档
具体改动 / Changes
以变更前的 packages/loopx-codex-provider-routing/NATIVE.md(d3ac3bd8d69821ae5843b7cef5fdb6dcbc7cae74)为规范。selection restores that provider 在共用地址的场景失败。built-in openai 通过原生恢复对照。default-off permission 仍由原回退策略和两边的测试覆盖。readback and rollback 的账号锁、回读和回滚测试通过。变更前规范
关键代码讲解
- managed_relay_provider 和 routed_api_auth 决定哪些槽改写为内置 openai。地址相等分支把已经明确选择的自定义 ID 当成受管旧中转。回退请求原来使用的查询参数和头也绑定在这个自定义 ID 上。分类与写回
- file_config 生成和验证 TOML。Accounts.enroll_api 创建未选中槽时应用同一个分类,Accounts.switch 还会用它写回退出的 API 槽。新槽对照中,主干保存自定义 ID,当前提交在同地址时保存 openai,当前登录文件逐字节保持。配置转换 账号切换
- 原生文档补了中英文迁移说明,三处版本升至 0.15.7。构建出的 wheel 已读回原生 manifest 和发行包版本。新增账号测试覆盖了旧受管槽与地址不同的自定义网关,本轮也对两种情况做了独立 CLI 恢复验证。
对主干的风险 / Risks
各组用原生 Codex CLI 创建会话并读取恢复时的退出码和真实回环请求。
| 场景 | 主干 | 当前提交 |
|---|---|---|
| 旧受管中转,原会话绑定内置 provider | provider 不匹配,零请求 | 恢复同一会话,一次请求 |
| 显式网关与中转地址不同 | 恢复同一会话,保留自定义头 | 恢复同一会话,保留自定义头 |
| 显式网关与中转地址相同 | 恢复同一会话,保留自定义头 | 退出 1,provider 不匹配,零请求 |
主干 73 项测试与 28 个子测试通过。当前提交 75 项与 30 个子测试通过,Ruff 和 quick premerge 的 5 项直接检查通过。后续回归应同时断言槽回读、真实恢复以及网关收到的请求。
语义与 CI 对齐
这项改动沿用原生槽的 provider 字段。按地址判定迁移会破坏已接受的显式槽恢复约定。修复后请复跑账号存储测试,并通过 codex exec resume --json 复验共用地址的自定义会话与回环请求。远端 CI 状态未读取。
我的整体评价 / Verdict
内置会话走通解决了原始故障。共用地址的显式槽仍会丢失绑定,已有会话无法继续。把迁移限制在来源可确认的受管槽后,应同时复验三种恢复场景和新槽登记。产品 UI 与真实账号登录仍待验证。
English verdict: REQUEST_CHANGES — the managed legacy route resumes, but an explicit gateway sharing its endpoint is rewritten and the existing Codex session fails to resume.
songoow
left a comment
There was a problem hiding this comment.
Reviewer: model_agent · GPT-6 Astra Pro · OpenAI · self_reported
评审 head:c37894c35a3bd5abfb8276f1ae7ccf61ea55b242;当前主干:beff0f3e55d1f70eceebbd8e78c11f1cff34643b。
动机 / Motivation
使用 Codex 订阅与 API 自动接续、并选择过自定义 gateway 的用户会遇到这个边界。
原生会话已经绑定 explicit_gateway;往返切换订阅与 API 时,PR 希望修复历史受管 relay 的 provider 名称,但不能改掉用户明确选择的 gateway。
期望旧受管 relay 能使用内置 openai,同时显式 gateway 在保存槽位与再次选择后保持原 provider。
本次范围是已有账号事务中的 provider 路由迁移,不包括重放会话、改变模型、增添 API 权限或重建账号存储。
改动思路 / Approach
沿用 Accounts 原有原子事务和路由 owner 是合适的;修复应收紧受管身份判定,地址相等不足以授权重命名。 当前 PR 边界是 provider 绑定兼容修复,不需要增加新的会话复制、授权或账号状态层。
入口仍由原生账号 owner 读取当前 config 与私有槽位,在已有 idle、revision 和事务边界内保存当前路由、选择下一个账号并写回配置。保留这些边界正确;需要区分“这是同一个传输地址”和“这个 provider 由我们管理”两件事。
具体改动 / Changes
关键代码讲解
managed_relay_provider(packages/loopx-codex-provider-routing/src/loopx_codex_provider_routing/native_account_store.py:211)识别旧名字及同地址 provider;routed_api_auth 和 file_config 把该分类用于入槽、出槽和最终配置。Accounts.enroll_api/switch 均有真实调用,因此错误会写入持久槽位。其余文件更新双语说明、版本到 0.15.7 及对应回归,未引入第二个 writer。
接受依据是主干 NATIVE.md,spec_revision=beff0f3e55d1f70eceebbd8e78c11f1cff34643b,criterion_id=existing_boundary:显式选择的顶层 gateway 在再次选择 API 槽位时恢复原 provider 与模型目录。当前 head 未满足。旧的无差别迁移已收窄;本轮检查完整差异后,仍确认现有 P2,不重复创建相同 inline。
对主干的风险 / Risks
[P2] URL 相等仍会吞掉显式 provider 身份。 独立运行真实 Accounts 文件事务:先使用 explicit_gateway 激活 API,再将该 provider 的 base_url 设为本地 relay endpoint,切换到订阅再切回 API。主干的 outgoing slot 与最终 config 都保留 explicit_gateway;当前 head 两处都变成 openai。仅把 URL 改为不同地址,两版都保留 explicit_gateway,确认触发条件是新增 URL 分类,不是测试槽位或其他改动。
这个错误不一定让下一次 HTTP 请求立刻失败,但会改变已有 native conversation 依赖的 provider 绑定。按受管来源或明确的旧 provider 身份迁移;不要用相同 base_url 推断可重命名身份,并补充显式 gateway 共用 relay URL 的往返切换回归。
本轮相关 Python 测试为 74 passed、1 skipped、28 subtests passed;另两个 quoted/backslash gateway 子用例在 Linux 的未修改 Host.switch 中报 platform_unavailable,主干同用例复现相同失败,不归因给本 PR。原生 macOS Codex 恢复未在本轮重新运行;这里的反例证据到实际槽位与 config 独立读回为止。远端 CI 未获取完整 rollup,不写成通过。
语义与 CI 对齐
迁移规则应以明确受管身份为依据。现有测试通过不能替代“同地址、不同 provider 身份”这一持久化边界。
我的整体评价 / Verdict
建议先修复这一已独立复现的 P2。方向和改动规模适度,无需重构账号系统;长期会话接续要求保存用户选定身份,不能让一次成功的账号切换悄悄改名。本次检查的是 CLI、文件事务或文档语义,没有修改渲染页面。完整安装后的宿主行为未在本轮重新验收。
English verdict: REQUEST_CHANGES. The URL-only migration still rewrites an explicit provider identity; preserve the selected binding before approval.
…pair-8507-20261111 Signed-off-by: huangruiteng <huangrt01@163.com>
Signed-off-by: huangruiteng <huangrt01@163.com>
huangruiteng
left a comment
There was a problem hiding this comment.
Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · max
评审提交 32c850592c13b68aaf3e717f023501d96fd83906,主干对照 38a3223b5bd204f6f90ac95930b0e42637e018f3。
动机 / Motivation
使用 Codex 订阅、受管 API 接续或显式 Responses 网关的用户,切换账号后需要继续原来的会话。
原提交将共用中转地址的显式网关槽改写为内置 openai。隔离复验中,旧提交恢复后的请求丢失网关自定义头和查询参数。修订后,网关槽保持原 provider,受管旧中转槽迁移到内置 openai。
用原生 Codex CLI 恢复同一会话时,实际本地请求保留网关头、查询参数和前序对话。已知旧中转槽仍通过内置 provider 继续原会话。
验收覆盖本地账号文件事务、原生 CLI 与回环 Responses 服务。App 界面、真实登录和上游模型流量留在本轮验证范围之外。
改动思路 / Approach
已知中转 ID 的兼容转换放在原生账号存储,入槽、出槽和配置回读复用同一判定。付费准入和真实账号事务继续由已有 owner 管理。
本 PR 交付现有槽位的 provider 绑定兼容、0.15.7 扩展打包、双语操作说明和恢复回归。 保留已有锁、当前登录检查和事务回滚即可覆盖写入和恢复。Codex 的内置 provider 支持 endpoint override,显式 gateway 仍从自己的定义读取认证、头和请求参数。 Codex 配置文档
具体改动 / Changes
关键代码讲解
normalize_relay_provider只按已知旧中转 ID 归一。新登记的Accounts.enroll_api与已存槽的routed_api_auth复用这个判定,显式网关共用 URL 时仍保留自己的 ID。 归一化与出入槽 · 登记file_config生成、解析和校验选择后的配置。Accounts.switch锁内重读同一个槽,用既有事务保存当前与目标身份。订阅返回时移除受管 endpoint。三个版本位置与构建出的 0.15.7 wheel 一致。 配置 · 切换- 新增共享地址的原生回归,断言恢复同一 thread、保留首轮输入,以及回环实际收到带网关查询参数的请求。复验另读回了请求头。 真实 CLI 回归
验收依据 packages/loopx-codex-provider-routing/NATIVE.md#openai-api-fallback @ 38a3223b5bd204f6f90ac95930b0e42637e018f3。ROUTE-EXPLICIT 通过,同地址网关恢复绑定和模型目录。ROUTE-FALLBACK 通过,已知旧 ID 选择内置 provider 并保持会话。ROUTE-LOCK 通过,原事务回读和失败回滚有效。ROUTE-DEFAULT-OFF 通过,保存 Key 不会启动 relay 或授权付费切换。 变更前 NATIVE 合同
对主干的风险 / Risks
在同一 Codex CLI 0.159.2 与本地 Responses fixture 中,主干及最终提交的显式网关都带原头和参数。原 PR 的恢复请求缺少二者。最终提交的已知旧中转也继续原内置会话,订阅返回读回无中转地址。复核了旧评审对显式网关的两处记录。 原阻塞
PASS · 108 项测试与 30 个子测试、Ruff、5 项预合并检查及 wheel 读回。6 个独立 opt-in App Server SDK 组合跳过。新提交远端工作流尚在排队,当前没有失败。原提交的 CI 失败来自更早的运行,保留其历史观测。 账号回归
语义与 CI 对齐
继续使用原生 slot 的 provider 字段及现有付费开关。两个槽能用同一 URL,授权与身份仍由已验证的账号和所选 provider 决定。用新登记、已有槽、往返恢复和默认关闭回归核对这一边界。
我的整体评价 / Verdict
同意合入。长程会话能保留原历史,操作员切回同地址网关时会继续使用原路由。原生复验使用 Codex CLI 0.159.2、隔离目录、合成账号与回环 Responses。候选验证覆盖 provider 绑定、当前 config 和真实恢复请求。
English verdict: APPROVE 32c850592c13b68aaf3e717f023501d96fd83906. Native Codex resumed the same thread and preserved the explicit gateway headers and query parameters. Known legacy relay slots still select the built-in provider.
Verified on 32c8505. Shared loopback URLs preserve explicit provider bindings during enrollment and the full account round trip. Native Codex resumed the same thread with the original custom header and query parameter. Known legacy relay IDs still select the built-in provider. All relevant tests and local premerge checks passed.
|
Request changes conclusion (author-owned PR; GitHub blocks formal self-review) Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · max 本次评审对应提交 合并后审计 · [P2] 保留与旧中转同名的显式网关。 已合入 de684cd。显式网关碰巧名为 openai_retry20 时,主干对照保留该名称并恢复原会话;合入版在登记和往返切换时把槽改为 openai。恢复在发请求前报 modelProviderMismatch;新会话仍能启动,却改发受管中转且没有网关请求头。 用可核验的受管槽来源或明确迁移绑定判断;来源不明时保留已显式保存的 provider,补同名异端点的原生往返测试。 触发代码 动机 / Motivation使用 Codex 订阅、受管 fallback 或显式 Responses 网关的用户需要继续原会话。内置订阅会话进入历史受管 API 槽会因 provider 名称不同而拒绝恢复;已保存的显式网关仍按绑定切回。真正的旧受管中转在本次提交保留内置 openai 会话,实际请求抵达中转。不同 ID 的显式网关即使共用地址,也恢复同一会话、请求头、查询参数与原输入。显式网关碰巧名为 openai_retry20 时,主干对照保留该名称并恢复原会话;合入版在登记和往返切换时把槽改为 openai。恢复在发请求前报 modelProviderMismatch;新会话仍能启动,却改发受管中转且没有网关请求头。本次使用原生 Codex 0.167.0-alpha.3.2、合成账号和两个回环 Responses 目标。未使用真实账号、模型流量或已安装扩展。 改动思路 / Approach继续沿用原生账号事务、权限与配置回读,把旧中转迁移限定在有来源证据的槽。内置会话修复有效;同名显式网关仍需保住原绑定。 既有账号锁内决定路由并原子写回;Codex 根据保存的 provider ID 恢复会话,仍由既有付费策略决定是否启用 fallback。 具体改动 / Changes关键代码讲解原验收依据 packages/loopx-codex-provider-routing/NATIVE.md#openai-api-fallback @ 38a3223;ROUTE-EXPLICIT 未通过,ROUTE-FALLBACK、ROUTE-DEFAULT-OFF、ROUTE-LOCK 通过。合入前规范。六个路径中,归一化与槽登记 把名字集合用在已存槽、出槽和新增槽,配置回读与事务 再据此写入真实配置。其余路径补充双语迁移说明、三个 0.15.7 版本位置与同地址的原生回归。上一轮同地址问题已修好;本次反例的地址本来就不同,名字在合入前可作为普通显式网关使用。 对主干的风险 / Risks显式网关碰巧名为 openai_retry20 时,主干对照保留该名称并恢复原会话;合入版在登记和往返切换时把槽改为 openai。恢复在发请求前报 modelProviderMismatch;新会话仍能启动,却改发受管中转且没有网关请求头。 用可核验的受管槽来源或明确迁移绑定判断;来源不明时保留已显式保存的 provider,补同名异端点的原生往返测试。 对照版的原会话与前序输入都保留。合入树与本地用同一 head 生成的合并树完全一致。 语义与 CI 对齐真实 Codex 回环验证同时跑了受管旧中转、同地址显式控制、不同地址显式控制与同名负例。76 项相关测试及 30 个子测试、Ruff、wheel 构建和 5 项 quick premerge 直接检查通过;未读取托管 CI。 我的整体评价 / Verdict建议在原有账号 owner 内补修显式同名槽,再用同一对端点和同一会话往返复验。现有账号切换入口;未修改渲染或首屏。真实登录、已安装状态及平台外推未验证。 English verdict: REQUEST_CHANGES. Reproduced a regression on merged head 32c8505: a named but explicit gateway is rewritten, blocking native resume and rerouting a new request. |
Summary
openaiprovider ID.openai_base_urloverride instead of a custom provider whose display name is alsoOpenAI.0.15.7.Root cause
Codex binds each conversation to the internal
model_providerID, not its display name. Subscription sessions recordedopenai, while API fallback switched toopenai_retry20. Both displayed asOpenAI, so the UI reported a provider mismatch even though the labels looked identical.Official documentation recommends
openai_base_urlwhen only changing the built-in OpenAI provider endpoint: https://developers.openai.com/codex/config-file/config-advanced#custom-model-providersValidation
uv run --python 3.12 --with pytest pytest tests/presentation/test_native_account_store.py tests/presentation/test_openai_fallback.py tests/presentation/test_openai_relay.py packages/loopx-codex-provider-routing/tests/test_api_stream_outcome.py -q0.15.7locally, migrated the active API slot, restarted services, and reopened the App.model_provider=openai, relay endpoint inopenai_base_url, and zero provider-mismatch events after migration.