Skip to content

[Bug]: 供应商切换破坏性重写 config.toml,清空其他 [model_providers.X] 段导致后续切换报「未检测到完整 custom provider」 #1563

Description

@no2ooo

影响范围

供应商切换 / 配置写入(apply_pure_api_injection

接入模式

纯 API

当前行为

在 Codex++ 设置界面点击切换供应商时,apply_pure_api_injection破坏性重写整个 ~/.codex/config.toml——只保留一个占位段(model_provider 当前指向的段),把目标供应商的部分字段写进去,清空其他所有 [model_providers.X]

后续切换其他供应商时,因目标段已被自己清空,verify 阶段失败,弹出:

供应商切换失败:纯 API 配置写入后未检测到完整 custom provider,请检查 config.toml 和供应商 API Key。

而且占位段的内容会被「部分覆盖」——段名 / display name / api_key 保留旧值,只有 wire_apibase_url 被更新,导致段内容变成不伦不类的混合状态。

预期行为

切换供应商时应该只更新 model_provider 字段 + 目标供应商段的内容(或直接改 model_provider 指向目标段名),保留其他所有 [model_providers.X] 段不动。这样用户可以自由切换任意供应商,不依赖段是否被自己清空。

复现步骤

  1. ~/.codex/config.toml 里配置多个供应商段(例如 7 个:zhipu / apikey_gork / one_api / agnes / deepseek / apikey_fun / custom)
  2. 启动 Codex++,设置界面显示全部 7 个供应商
  3. 点击切换到供应商 A(例如 apikey gork)
    • UI 显示切换成功("使用"标签移到 A 上)
    • config.toml 被整个重写,只剩 1 段(占位段,内容被污染为「旧段名 + A 的 base_url + 旧 api_key」的混合)
    • 其他 6 段全消失
  4. 点击切换到供应商 B(例如 apikey.fun)
    • 弹出错误「纯 API 配置写入后未检测到完整 custom provider」
    • 原因:[model_providers.apikey_fun] 段已在第 3 步被自己清空

证据

证据 1:切换前后 config.toml 段数对比

切换前(7 段):

[model_providers.custom]
[model_providers.zhipu]
[model_providers.apikey_gork]
[model_providers.one_api]
[model_providers.agnes]
[model_providers.deepseek]
[model_providers.apikey_fun]

切换到 apikey gork 后(只剩 1 段,且内容被污染):

[model_providers.zhipu]      # 段名仍是 zhipu(占位段)
name = "zhipu"               # 仍是 zhipu
wire_api = "responses"       # 被改成 responses
base_url = "https://api.apikey.fun"   # 被改成 apikey gork 的 url
api_key = "d3f4...(zhipu 的 key,未更新)"   # 仍是 zhipu 的 key

注意:段名 / display name / api_key 都没被正确更新为目标供应商的值,只有 wire_apibase_url 被覆盖。

证据 2:manager 二进制里的错误链

用 Node 脚本扫 codex-plus-plus-manager.exe 的 UTF-8 字符串,找到完整错误链:

manager.apply_pure_api_injection.before
manager.apply_pure_api_injection
manager.apply_pure_api_injection.generated
manager.apply_pure_api_injection.ok
  纯 API 配置写入后未检测到完整 custom provider,请检查 config.toml 和供应商 API Key。
  纯 API 模式已写入:config.toml 已写入 custom provider,auth.json 已切换为当前供应商。
manager.apply_pure_api_injection.failed
  写入纯 API 模式失败:{error}
manager.apply_pure_api_injection.profile
  切换纯 API 配置失败:{error}

说明 apply_pure_api_injection 的流程是:写入 → 再读回 verify。verify 失败就弹这个错。

证据 3:没有「保留所有段」的开关

扫 manager 二进制查找 preserve / merge / keep_providers / non_destructive 等关键词,没有找到任何相关开关。说明这是默认行为,用户无法通过配置关闭。

根因猜测

apply_pure_api_injection 的实现可能是:

  1. 从某个内部 store(UI 显示的供应商列表来源)读取目标供应商配置
  2. 重写整个 config.toml(而不是只更新 model_provider 和占位段)
  3. 重写时只输出「当前激活的占位段」,遗漏了其他 [model_providers.X]
  4. 写完后读回 config.toml 验证占位段完整

问题出在第 2-3 步:重写时应该保留所有段,只更新占位段的内容;或者根本不应该重写整个文件,只改 model_provider 字段 + 占位段。

修复建议

方案 A(推荐,最小改动)apply_pure_api_injection 改为只更新 model_provider 字段 + 目标供应商段的内容,不重写整个 config.toml。具体:

  • 读当前 config.toml
  • 用正则/TOML 库定位 [model_providers.<target>]
  • 更新该段的 base_url/api_key/wire_api 等字段
  • model_provider = "<target>"
  • 写回(保留其他所有段)

方案 B:如果一定要重写整个 config.toml,重写时应该输出所有 [model_providers.X](从内部 store 读取),而不是只输出占位段。

方案 C:如果占位段机制是设计意图(用一个固定段名作为占位),那么切换时把目标供应商配置写入占位段 + 保留其他所有原始段不动(作为「供应商库」供下次切换读取)。

临时解决方案

写了一个 PowerShell 脚本绕开这个 bug,只改 model_provider / model / auth.json 三处,不动其他段:

.\switch-provider.ps1 list                    # 列出所有供应商段
.\switch-provider.ps1 deepseek                # 切到 deepseek(默认模型)
.\switch-provider.ps1 deepseek deepseek-reasoner   # 切并指定模型

实测切换 agnes ↔ deepseek 双向通过,7 段全保留。

环境信息

  • Codex++ 版本:1.2.38(codex-plus-plus.exe + codex-plus-plus-manager.exe
  • Codex CLI 版本:26.609.41114
  • 操作系统:Windows 11
  • config.toml 路径:C:\Users\<user>\.codex\config.toml

相关 issue

提交前确认

  • 我已经脱敏了 API Key、Token、账号等敏感信息。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions