universal 统一供应商:一份配置同步多个工具的边界在哪

2026-08-10

如果你用的是一个支持多种协议的自建 API 网关,那么在 CC Switch 里给三个 CLI 各配一遍同样的地址和密钥,是件很没必要的事。这套机制覆盖的正是 claude / codex / gemini 这三个应用。仓库里为这件事准备了一个叫 universal(统一供应商) 的东西。

先把名字理顺,因为 CC Switch 里有两个中文名长得很像、干的完全是两回事:

  • 统一供应商(universal provider):一份网关配置,向下派生出多个应用各自的供应商条目。本文讲的是这个。
  • 通用配置片段(common config snippet):把当前配置里非供应商专属的那部分抽出来复用,走的是另一套提取与回填逻辑。关于它我们另有一篇专门讲。

两者都带”通用/统一”,但由不同的文件与不同的后端函数处理:统一供应商见 src/config/universalProviderPresets.tsmod.rs 里 universal 那一组方法,通用配置片段见 mod.rsextract_common_config_snippet 那一组。查问题时先分清是哪一个,能省掉一大半弯路。

它到底同步给谁:三个布尔字段就是全部边界

统一供应商的能力上限,写在一个只有三个字段的类型里。UniversalProviderApps 只有 claude / codex / gemini 三个布尔字段(src/types.ts:537-541)。src/config/universalProviderPresets.ts 文件头的注释也是同样的口径:统一供应商是跨应用共享的配置,会同步到 Claude、Codex、Gemini 三个应用,适用于支持多种协议的 API 网关(src/config/universalProviderPresets.ts:1-7)。

对照一下应用侧的全集:src/types.ts 里的 VisibleApps 列了八个应用键——claude / claude-desktop / codex / gemini / grokbuild / opencode / openclaw / hermes。也就是说统一供应商覆盖其中三个,另外五个(Claude Desktop、Grok Build、OpenCode、OpenClaw、Hermes)不在这套机制里,它们的供应商仍然各配各的。

这条边界值得先记住:如果你的主力工具是那五个之一,统一供应商帮不上忙,不必再往下研究它的字段。特别是 OpenCode / OpenClaw / Hermes 这三个,后端 AppType::is_additive_mode() 对它们返回 true(src-tauri/src/app_config.rs:404-409),是所有供应商共存于同一份 live 配置的累加模式,与统一供应商派生子条目的模型本来就不是一回事。

后端把”保存”和”分发”拆成了两个方法

这是本篇最需要讲透的地方。

src/config/universalProviderPresets.ts 开头的注释写的是”修改后会自动同步到 Claude、Codex、Gemini 三个应用”(:1-7)。而后端保存统一供应商的入口 ProviderService::upsert_universal,函数上方的文档注释明写不自动同步,需手动调用 sync_universal_to_appssrc-tauri/src/services/provider/mod.rs:4610-4619)。真正做分发的是另一个函数 sync_universal_to_apps:4648-4720)。

这两句话描述的不是同一层:前端文件头注释是面向使用者的口径,后端函数上方的文档注释是面向调用方的口径。前端到底怎么串这两个后端方法,我们没有核实过这条调用链,事实卡也没有覆盖,所以不做任何推断。这一节能说的只有一件事:在后端 API 这一层,保存与分发写成了两个独立的方法。

怎么自己核这一处:在仓库里打开 src-tauri/src/services/provider/mod.rs,搜 fn upsert_universal,看它函数体里除了保存之外还调了什么;再搜 fn sync_universal_to_apps,看分发逻辑是不是全在这里。两个函数一前一后,读完不超过五分钟。前端注释那句在 src/config/universalProviderPresets.ts 的文件头,同一屏就能看到。

同步不是覆盖,是递归合并

sync_universal_to_apps 的行为分两支(src-tauri/src/services/provider/mod.rs:4648-4720):

  • 某个应用在 apps启用:如果该应用下已经存在对应的子供应商,就用 merge_json 把统一供应商生成的配置递归合并进已有配置,patch 覆盖同名字段,然后保存。
  • 某个应用在 apps被关闭:直接删除对应的子供应商。

这两句要分开体会。合并意味着子供应商上原先存在、而统一供应商这边没有同名键的那些配置会被保留下来;但只要键名撞上,这一次同步就会把它换成统一供应商这边的值。所以”我在某个应用下单独调过的东西,同步一次之后还在不在”,取决于它跟统一供应商生成的配置有没有键名交集——这不是一句”会不会被覆盖”能回答的问题,得看具体键。

第二支更需要注意:关掉一个应用的布尔,语义是删除那个子供应商,不是暂时停用。删除动作发生在同步这一步。同理,delete_universal 会先删统一供应商本体,再按 apps 里为真的那几项逐个删掉对应的子供应商(src-tauri/src/services/provider/mod.rs:4621-4646)。

子供应商的 id 是可预测的,这是你的核查抓手

派生出来的子供应商 id 有固定规则:universal-claude-{id} / universal-codex-{id} / universal-gemini-{id}src-tauri/src/services/provider/mod.rs:4622-4645)。

这条规则的价值在排查时:你想确认”同步到底跑没跑过”,不用去猜,直接按这三个前缀在数据里找对应记录就行。一个统一供应商如果三个应用都启用了,就应该能找到三条对应的子记录;找不到某一条,对照上一节的两支逻辑,要么是那个应用的布尔是关的,要么是同步这一步还没执行。

顺带一提,子供应商在各自应用下就是保存的一条供应商记录,这一点从上面那三条 id 规则本身就能看出来(src-tauri/src/services/provider/mod.rs:4622-4645)。

“一份配置”在模型这一层其实是三块

统一供应商的实体 UniversalProvider 字段是:id / name / providerType / apps / baseUrl / apiKey / models / websiteUrl / notes / icon / iconColor / meta / createdAt / sortIndexsrc/types.ts:569-585)。

真正”统一”的是 baseUrlapiKey 这两项——一处填写,三个应用共用。但 models 不是。UniversalProviderModels 分成三块,每块的粒度都不一样(src/types.ts:543-566):

应用模型字段
claudemodel / haikuModel / sonnetModel / opusModel
codexmodel / reasoningEffort
geminimodel

Claude 这一块是四档模型映射,Codex 那一块除了模型还带一个推理强度,Gemini 只有一个模型字段。所以”一份配置同步多个工具”这句话要打个折:地址与密钥是一份,模型配置是并列的三份,你仍然要为三个应用各自想清楚用哪个模型。

预设只有两条,别期待它像应用预设那样丰富

我们采集时(2026-08-10,仓库快照 c39c903),universalProviderPresets 数组一共 2 条(src/config/universalProviderPresets.ts:61),整个文件 132 行。两条的 providerType 分别是 newapicustom_gateway,后者带 isCustomTemplate: true:61-93)。预设接口 UniversalProviderPreset 的字段是 name / providerType / defaultApps / defaultModels / websiteUrl / icon / iconColor / description / isCustomTemplate:18-40)。

拿它跟八个应用各自的预设数组比一下就有概念了:那八个数组合计 448 条(我们用括号深度解析器逐文件数出来的,采集时间 2026-08-10),而统一供应商这边是 2 条。这不是”少”或”多”的问题,而是两者定位不同——应用预设是一张覆盖面很宽的候选清单,统一供应商更像是给自建多协议网关准备的一个模板位,isCustomTemplate: true 那条摆明了就是让你自己填。

还有一处值得对照:OAUTH_PROVIDER_TYPES 里只有 github_copilot / codex_oauth / xai_oauth 三个值(src/config/constants.ts:2-16),统一供应商这两条预设的 providerType 都不在其中。托管 OAuth 那条路由判定链是另一篇的主题,这里只需要知道它跟统一供应商是两套东西。

用之前先确认的四件事

  1. 你的工具在不在三个布尔里。不在(Claude Desktop / Grok Build / OpenCode / OpenClaw / Hermes)就别绕这条路。
  2. 后端把保存与分发拆成了两个方法upsert_universal 的文档注释写明它不自动同步,分发由 sync_universal_to_apps 完成。想核对某个子供应商在不在,可以用 universal-<app>-{id} 这条 id 规则去找。
  3. 你在某个应用下单独改过的键会不会被同名覆盖。合并是递归的、patch 覆盖同名字段,先把你改过的键名跟统一供应商生成的配置比一遍。
  4. 关掉某个应用的布尔等于删掉那条子供应商。如果你只是想临时不用,要意识到这个动作的语义。

最后是一句必须说的:统一供应商的 apiKey 是实实在在保存在你本机的凭据,同步动作会把它派生进多个应用各自的供应商记录里,覆盖面比单应用配置更广。这是本机敏感数据,共用同一份密钥意味着一处泄漏影响三个工具的配置面。备份、同步盘、截屏分享这些日常动作都要按敏感文件对待——这属于通用运维常识,不是该项目文档给出的建议,项目也没有对此给出任何保证。

至于这套机制该不该用、该配哪家网关,我们不给建议:仓库提供的是机制(预设怎么定义、配置怎么派生、同步什么时候发生),选谁是你自己的事。


本文依据 CC Switch 官方仓库(github.com/farion1231/cc-switch)的 README、docs/ 下的用户手册与发布说明、 src/config/ 的预设定义与 src-tauri/src/ 的后端源码整理,核对日 2026-08-10,对应仓库快照 c39c903。 本文内容为仓库源码与文档口径,我们没有安装或运行过这个桌面应用, 因此不涉及界面外观、操作手感与切换速度的任何描述。 文中出现的阈值与默认值均为源码中的默认配置,不构成对实际运行结果的保证。 该项目仍在快速迭代,版本与默认值随时可能变动,请以仓库最新内容为准。 安全相关做法请结合自身环境评估,本文不构成安全方案建议。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。