Skip to content

[Bug] Codex MultiRouter 自动重名别名残留:同一模型重复显示、删除联动,「仅选中的上游模型」下模型不可用(400) #100

Description

@sanjink

Self Checks / 自检

CC Switch Version / 版本号

3.20.2-19

Operating System / 操作系统

macOS

Related App / 涉及应用

Codex

Steps to Reproduce / 重现步骤

环境

  • macOS + CCSwitchMulti 3.20.2-19,Codex MultiRouter(codexRouting.schemaVersion = 2)已接管 Codex。
  • 只接入公司的 OpenAI-compatible 网关(下称 https://<company-gateway>/v1),没有把官方 OpenAI Provider 加入任何 route。
  • 公司 Provider 的显示名是 gpt5.6,其 modelCatalog 只有 3 个模型:gpt-5.6-luna、gpt-5.6-sol、gpt-5.6-terra。
  • 保存后该 Provider 的 route 配置如下(脱敏):
{
  "id": "router-fa1f8632-…",
  "label": "gpt5.6",
  "enabled": true,
  "targetProviderId": "fa1f8632-…",
  "modelSelection": { "mode": "all" },
  "matchPrefixes": ["gpt"],
  "aliases": {
    "gpt-5.6-luna-gpt5-6": "gpt-5.6-luna",
    "gpt-5.6-sol-gpt5-6": "gpt-5.6-sol",
    "gpt-5.6-terra-gpt5-6": "gpt-5.6-terra"
  },
  "authPolicy": { "source": "provider_config" }
}

重现步骤

  1. 用「配置多路模型」向导只保留公司 Provider 作为模型源(不勾选官方 Provider),生成 route,模型选择范围选 全部模型(自动接收新增)。
  2. 保存并让 Codex 重新读取模型目录后,打开 CCSwitchMulti 的「Codex 模型排序」:同一个模型会出现两条,显示名完全相同,只有括号里的模型 ID 不同:
1  gpt-5.6-sol   (gpt-5.6-sol)
2  gpt-5.6-sol   (gpt-5.6-sol-gpt5-6)
3  gpt-5.6-terra (gpt-5.6-terra)
4  gpt-5.6-terra (gpt-5.6-terra-gpt5-6)
5  gpt-5.6-luna  (gpt-5.6-luna)
6  gpt-5.6-luna  (gpt-5.6-luna-gpt5-6)
  1. 点击其中任意一条的「移除」:两条会被同时移除,无法只删掉别名那一条,也无法单独保留。
  2. 把同一条 route 的「模型选择范围」改成 仅选中的上游模型,勾选这 3 个模型并保存后:Codex 侧这 3 个模型全部不可用,请求返回 400。
  3. 打开「子 Agent」页面:同样会出现两套条目 —— gpt-5.6-luna / gpt-5.6-sol / gpt-5.6-terra 与 gpt-5.6-luna-gpt5-6 / gpt-5.6-sol-gpt5-6 / gpt-5.6-terra-gpt5-6,共 6 条,其中一套显示为「待配置 / 未启用」。

Expected Behavior / 期望行为

  1. 同一个 canonical 模型(无论是否带可见别名)在「Codex 模型排序」和 Codex 模型菜单里只应出现一条,或至少在 UI 上明确标注它们指向同一个上游模型。
  2. 删除某个可见模型时只应删除它自己,不应连带删除同一 canonical 模型的另一个条目。
  3. 「仅选中的上游模型」(modelSelection.mode = include)应当和 all 一样可用,不应该出现模型全部不可用 / 400 的情况。
  4. 子 Agent 页面不应长期同时保留 canonical 与别名两套 profile;重名冲突消失(例如官方 Provider 已不再参与路由)后,自动生成的别名应能收敛或清理,而不是永久留在 route contract 里。
  5. 自动别名的后缀来自 Provider 显示名,Provider 改名会改变 Codex 侧可见模型 ID;期望改名后旧 ID 不残留在模型排序列表、spawnAgentModels 和子 Agent profile 中。

Actual Behavior / 实际行为

  • 「全部模型(自动接收新增)」:每个模型显示两条(canonical ID 与 -gpt5-6 别名 ID),显示名相同。
  • 删除其中一条时两条一起消失。
  • 「仅选中的上游模型」:模型完全不可用,请求返回 400。
  • 子 Agent 页面:canonical 与别名两套条目并存,共 6 条。

Additional Context / 补充信息

1. 别名是自动生成的重名别名,后缀来自 Provider 显示名

主分支 src/lib/codexMultiRouterWizard.ts 中:

  • providerNameSuffix() 把 Provider 显示名清理成模型 ID 后缀:gpt5.6 → gpt5-6;
  • resolveWizardModelNameCollisions() 在多个模型源暴露同名模型时,官方源保留原名、非官方源生成 <model>-<providerNameSuffix> 可见别名,并把 upstreamModel 指向真实上游模型名;
  • buildWizardRoutesFromSources() 会把已有的 route.aliases 作为 route 契约长期保留(注释:「A generated collision alias becomes part of the route contract on first save.」)。

因此当官方 Provider 已经不再参与路由、重名冲突实际已经消失后,旧别名仍然留在 route 里,于是同一个上游模型在 catalog 里同时存在 canonical 与别名两个可见 ID。

2. 本机生成的目录文件同样只写入了别名 ID

~/.codex/cc-switch-model-catalog.json(脱敏,仅保留关键字段):

[
  { "model": "gpt-5.6-sol-gpt5-6",   "displayName": "gpt-5.6-sol" },
  { "model": "gpt-5.6-terra-gpt5-6", "displayName": "gpt-5.6-terra" },
  { "model": "gpt-5.6-luna-gpt5-6",  "displayName": "gpt-5.6-luna" }
]

即 Codex 侧可见模型 ID 已经是别名,而「Codex 模型排序」还会把公司 Provider 目录里的 canonical gpt-5.6-sol 一起列出来,形成重复行。

3. 子 Agent profile 与 spawnAgentModels 存在多代残留

codexRouting.subagentV2.profiles 中同时存在:

gpt-5.6-luna (enabled=false)  / gpt-5.6-luna-gpt5-6 (enabled=true)
gpt-5.6-sol  (enabled=false)  / gpt-5.6-sol-gpt5-6  (enabled=true)
gpt-5.6-terra(enabled=false)  / gpt-5.6-terra-gpt5-6(enabled=true)

spawnAgentModels 里还残留上两代别名:gpt-5.6-sol-longnows-5-6 等(见下条)。

4. 由于后缀跟随 Provider 显示名,历史上已产生三代可见 ID

本地 codex-router.log 中同一 Provider 的可见模型 ID 依次为:

gpt-5.6-sol-longnows       首次出现 2026-07-17
gpt-5.6-sol-longnows-5-6   首次出现 2026-07-24
gpt-5.6-sol-gpt5-6         首次出现 2026-09-20

说明 Provider 每次改名(longnows → longnows 5.6 → gpt5.6)都会生成新的可见模型 ID,旧 ID 不会自动清理。

5. 400 的日志证据(脱敏)

codex-router.log 中该 route 共 66 次 status=200、3 次 status=400:

2026-09-20 09:15:44  model=gpt-5.6-sol          status=400  body_summary=Upstream_request_failed
2026-09-20 09:18:09  model=gpt-5.6-sol          status=400  body_summary=Upstream_request_failed
2026-09-20 09:24:05  model=gpt-5.6-sol-gpt5-6   status=400  body_summary=Upstream_request_failed

这 3 条都已经走到 upstream_url=https://<company-gateway>/v1/responses(route_missed=false),不是本地路由未命中;不过我没有保存 Codex 端完整的 400 响应体,无法确认它与「仅选中的上游模型」是否是同一个失败原因,如果需要我可以补充完整报错文本和截图。

6. 相关代码位置(main 分支)

  • src/lib/codexMultiRouterWizard.ts
    • providerNameSuffix() / aliasModelName() / resolveWizardModelNameCollisions():自动别名的生成规则;
    • buildWizardRoutesFromSources():已有 alias 长期保留,冲突消失后也不会清理;
    • buildSubagentNameRedirector() / remapSubagentV2():旧可见名 → 新可见名的重定向(当前保留了新旧两套 profile)。
  • src/components/codex/CodexMultiRouterWizard.tsx、src/components/codex/CodexRouterWorkspacePage.tsx:向导与工作台对重名别名的展示/选择。

7. 隐私说明

本 issue 不包含 API Key、Token、Cookie、认证头、完整请求体或数据库备份;公司网关域名已用 https://<company-gateway>/v1 占位。截图(Codex 模型排序重复行、route 编辑页、子 Agent 页面重复条目)可按需补充。

Activity

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

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