Skip to content

feat(auth): 隔离原生登录与网关多账号订阅额度 - #2909

Open
Yuimi-chaya wants to merge 6 commits into
BigPizzaV3:mainfrom
Yuimi-chaya:codex/explicit-auth-subscription-quota
Open

Yuimi-chaya wants to merge 6 commits into
BigPizzaV3:mainfrom
Yuimi-chaya:codex/explicit-auth-subscription-quota

Conversation

@Yuimi-chaya

@Yuimi-chaya Yuimi-chaya commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

背景

供应商切换会改写 Codex 原生 auth.json。多账号场景需要分别管理原生登录身份与网关供应商的订阅凭证,避免主动选择的账号被其他供应商覆盖。本 PR 保留旧版默认切换行为,仅在用户明确选择原生账号或「不登录」后启用显式身份管理。

改动

  • 在「Agent 增强 → Codex → 连接与供应商」统一选择原生账号或「不登录」,移除供应商池内重复入口。选择不登录不会删除已保存账号,也不影响已接入网关的供应商。
  • 复用现有网关账号存储和凭证引用,不增加账号数据库。默认不把原生登录复制成网关账号;主动切换时新增的外部原生会话备份保持禁用、只读,不参加后台预取、自动借用或独立刷新。
  • 不增加订阅消费授权开关,账号接入并启用后即可请求。只有显式配置 subscription_policy 才按各官方窗口累计已用阈值拦截,并应用停止、下一个订阅账号或 API 回退策略;未知用量默认允许。
  • 未配置策略的旧供应商完全不受本地额度拦截,也不受同账号别名策略影响,保留「订阅在前、API 在后」的失败转移。旧账号仅改名不会自动创建策略;过期重置窗口继续按剩余比例 1.0 排序,排序与准入分别处理。
  • 同一真实账号共享成功取得的官方用量;配置了策略的供应商受同账号已启用别名中更严格的策略约束。查询合并和刷新失败退避按凭证会话隔离。额度读取不会轮换原生客户端拥有的 refresh token,独立网关登录仍可刷新。
  • 原生认证使用短发布锁;未开启显式选择时不争用慢网络刷新的互斥锁,不因其竞争导致供应商切换或断开恢复失败。首次显式选择与已开始的旧模式发布串行,避免刚选择的账号或「不登录」被覆盖。
  • 保留完整账号身份、凭据代际和写入后的归属校验;显式选择优先于旧自动借用标记。配置发布失败时只恢复本次写入的认证代际,不覆盖外部更新。
  • 恢复官方供应商名称、模型列表和代理编辑,保存时保留账号凭证及绑定;未启用显式身份管理时,纯 API 应用沿用旧版认证写入行为。
  • 根据官方实际窗口在供应商卡片显示独立的 5h、7d 红黄绿配额条;显式策略禁止派发时同步禁止可用性测试,不根据 Plus、Pro 或 Team 名称推断是否存在 5h。启动、导入和登录后自动预取,供应商页自动更新。
  • 官方模型目录与 Responses 请求的客户端版本、版本头和 UA 修复已拆到独立 PR #2955,本 PR 不重复包含这 3 个文件的差异。

验证

  • 已 rebase 到 main 的 daecde9e。
  • 前端测试:1689 通过、0 失败、2 项既有跳过;TypeScript 检查及 Vite 构建通过。
  • 真人验收(2026-11-07):已安装本 PR Setup,订阅供应商实际可正常使用。
  • Rust 核心库:2230 通过、13 项既有忽略、6 项本机环境测试筛选;核心集成测试:1780 通过、28 项既有忽略;独立 gateway_e2e、其他 4 个独立测试、data 500 项和 launcher 38 项通过。
  • 管理器其余测试和 workspace 文档测试通过。唯一未通过的未修改用例是 Windows 进程出生标识/端口归属检查 commands::tests::multi_instance_missing_receipt_uses_native_birth_scan_and_port_ownership_checks(实际 starting、期望 stopped);该测试及其调用的 commands.rs、multi_worker、windows_integration、watcher 文件相对 main 无差异,未修改断言或工作流。
  • 新增认证/额度回归覆盖:未配置策略的旧账号耗尽后仍保留 API 回退、同账号别名策略隔离、重置窗口排序、legacy 写入不等待刷新锁,以及首次显式选择与 legacy 发布串行。

边界

未配置策略的旧供应商保留原有可用性和 API 失败转移,不因升级自动增加额度拦截。

阈值拦截新网关请求,不预留或修改官方额度;上游读数延迟和在途请求仍可能导致超过阈值。本应用的互斥锁不能约束外部客户端,外部写入仍依赖凭据代际检查;同一旋转会话跨 Magpie、Codex 等客户端的并发同步不在本 PR 范围内。

@BigPizzaV3

Copy link
Copy Markdown
Owner

感谢这么大的投入!不过这个 PR 现在不能合,主要是会改变老用户的默认行为:

  1. CI 红:Linux / Windows 上 tests/gateway_e2e.rs 的 gateway_end_to_end 返回 502(期望 200)。main 自身是绿的,所以是这个 PR 引入的。
  2. 老用户的订阅账号升级后全部被拦:dispatch_decision_at 里,供应商没有订阅策略时直接返回 403「未明确允许网关使用此账号」。老的 gateway.json 都没有这个字段,所以所有已有的 ChatGPT 订阅供应商升级后都会被拦,用户得一个个手动授权。老供应商需要默认沿用原来的行为(视为已授权)。上面的 e2e 502 很可能就是这个原因。
  3. 原生登录被复制进 gateway.json:只要往默认的 ~/.codex 写 auth.json,就会把其中的 token(含 refresh_token)复制成一个关闭状态的 native-<uuid> 供应商,不管用户用没用网关。而额度预取也会去查这份复制件,access token 过期时会用复制件里的 refresh_token 自己刷新;OpenAI 的 refresh_token 是用一次就换新的,这有可能让用户 Codex 里的登录失效。
  4. 切到纯 API 供应商时不再改写 auth.json;ChatGPT 登录型供应商的编辑大多被忽略(改名、改模型、改代理都不生效);config.toml 写入失败后的恢复步骤被删了。这几处也都是对现有用户的行为变化。

建议拆小:先只做「订阅账号的额度阈值 / 自动预取」并且默认不拦,不复制原生登录;原生登录隔离另开一个 PR 单独讨论。这是新功能,合不合最终由维护者决定。

@Yuimi-chaya

Copy link
Copy Markdown
Contributor Author

感谢维护者详细指出这些兼容性问题,也感谢你的建议。

关于“网关授权”设置:这个开关是我在 agent 侧额外引入的控制项,并不是现有网关供应商模型所必需的设置。它确实会让旧版 gateway.json 中没有该字段的订阅供应商在升级后被错误地拦截为 403。现在已按意见移除这层额外授权要求:供应商接入并启用即可使用;额度窗口、使用阈值以及超额后的路由策略仍然保留,未知用量也默认沿用旧版可用行为。

关于 auth.json:目前 Codex++ 在切换供应商时确实存在覆盖原生登录凭证的实际风险,这也是我希望通过这个 PR 一并解决的原因。额度查询、原生登录选择和网关供应商共用了凭证解析/刷新路径,如果只修额度逻辑,仍可能出现复制原生凭证、轮换原生 refresh_token,或多个供应商相互覆盖的问题。当前修正包括:

  • 未显式选择原生账号时,不再把原生登录复制成可独立刷新的网关账号;
  • 额度读取不会轮换原生或借用凭证的 refresh_token;
  • 显式选择账号时只使用对应账号,多账号供应商的凭证彼此隔离;
  • 纯 API 切换和失败回滚保留现有 auth/config,不再无条件覆盖;
  • 恢复旧版默认 apply 行为,以及官方登录供应商的名称、模型和代理编辑兼容性。

所以我理解拆分 PR 的建议,但仍希望在本 PR 内完成这组兼容修复,因为它们共享同一条 auth/quota 路径,拆开会暂时保留 auth.json 覆盖和 refresh token 风险。上述问题已按意见逐项修正,麻烦后续再帮忙 review 检查。谢谢!

@BigPizzaV3

Copy link
Copy Markdown
Owner

感谢这么完整的实现和验证记录。合并前有几点想先对齐:

  1. 默认行为:route.rs 的 DispatchGate 对没有配置订阅策略的官方成员也生效,而且 SubscriptionLimitAction 默认是 Stop,遇到额度用满的订阅账号会直接 break,后面的 API 供应商进不了候选。老用户「订阅在前、API 在后」的池子,额度用完后会从「自动回退」变成「整单报错」。能否改成:没配 subscription_policy 时完全不拦截(或至少不 break,保持现有的失败转移)?remaining_at 对已过重置时间的窗口从 1.0 改成 0.0,也会影响现有的按额度排序,希望一起说明或保持原样。
  2. 写锁:native_auth_write_guard 用的是 refresh_lock().try_lock(),在 nativeAuthSelection 未设置时也会加在 write_codex_live_atomic、断开网关的恢复路径和借出 / 收回上。token 刷新期间用户切换供应商、断开网关会直接报错。能否只在显式选择开启时才加这把锁,或者改成短时间等待而不是立即失败?
  3. 拆分:「客户端版本、版本头和 UA 统一」这部分(chatgpt_backend::codex_version 加版本下限、fetch_official_login_model_ids 补版本和 UA)和 [Bug]:官方登录供应商使用异常 #2905 有关,也不依赖多账号功能。能否单独开一个小 PR?那部分我们可以先合,这个 PR 作为新功能再评估。
  4. 小问题:settings.rs 里有一处缩进被打乱;另外 PR 落后 main 十几个提交,合并前请 rebase 后再跑一轮 CI。

@Yuimi-chaya
Yuimi-chaya force-pushed the codex/explicit-auth-subscription-quota branch from c7cad89 to 0b09e54 Compare November 6, 2026 16:22

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants