CC Switch 用一万行代码删掉文档里那句「不支持」

2026-08-31

一次改动,九个文件,净增一万行代码;其中三个文件是文档,每份各改两行。

这个比例本身就值得停下来看一眼。在 v3.20.1 的仓库里执行 git show --stat bdeaac75,最后一行是 9 files changed, 10041 insertions(+), 1691 deletions(-);而 docs/guides/claude-codex-routing-guide-zh.md-en.md-ja.md 三份路由攻略的改动量整齐地都是 2 +-——一加一减,也就是把其中一行换成了另一行。

那一行换掉的是什么,v3.20.0 的发布说明给了一句概括(docs/release-notes/v3.20.0-zh.md:92):「Claude-in-Codex 路由攻略已三语更新:『GPT 路由下没有网页搜索』在这些路径上不再成立。」这是发布说明对这次文档改动的转述,不是攻略里被删掉的那行原文。

一万行代码,只为把文档里的一句「不支持」删掉。这篇就拆这一万行花在哪,以及它顺手确立的一条更通用的原则:协议翻译层遇到「对面表达不了」的约束时,该报错还是该放宽。

先说清这篇的依据

本文对应的仓库快照是 3217f725(仓库内版本号 3.20.1),核对日 2026-08-31。文中所有行号、错误文案、测试名都来自静态阅读这份快照的源码与 docs/ 下的文档,没有编译、没有运行,也没有安装过这个桌面应用。凡是引用发布说明的地方我都会写明「发布说明写的是」,凡是实读源码得到的结论会给出文件与行号,你可以自己去仓库里翻到同一行。

一万行落在哪几个文件

git show --stat bdeaac75 的文件清单按改动量排下来是这样的:

文件改动量
src-tauri/src/proxy/providers/streaming_responses.rs7489
src-tauri/src/proxy/providers/transform_responses.rs3492
src-tauri/src/proxy/handlers.rs366
src-tauri/src/proxy/server.rs223
src-tauri/src/proxy/forwarder.rs107
src-tauri/src/proxy/providers/transform_codex_anthropic.rs49
docs/guides/claude-codex-routing-guide-{zh,en,ja}.md各 2

九个文件里有六个在 src-tauri/src/proxy/ 下,一个前端文件都没动。这个分布已经说明了一件事:这不是「加了一个搜索功能」,而是「在代理层把一种协议的搜索语义翻译成另一种」。前端不需要知道这件事发生过。

更能说明难点位置的是那个 7489 行的 streaming_responses.rs。发起一次搜索本身并不复杂,难的是上游把搜索过程以事件流的形式一段一段吐回来,代理要一边收一边把它翻译成 Anthropic 侧成对的搜索块,还要保持引用与结果的配对关系不错位。请求转换(transform_responses.rs)三千多行,流式响应转换将近七千五——这类桥接的成本,绝大部分压在响应侧而不是请求侧。

如果你还没看过这个本地代理整体是怎么串起来的,仓库里那条管线我们在 CC Switch 的本地代理做什么 里按顺序拆过一遍;协议适配器在管线里的位置则在 CC Switch 的格式转换层 里。这篇不重复那两块,只看 WebSearch 这一条支路。

变更前是什么样,变更后是什么样

三件套摆出来:

  • 变更前:v3.19.2 时,三份路由攻略里这条路径上的联网搜索被写成不可用。
  • 变更后:v3.20.1 快照里,那一行已被替换掉。发布说明对这次替换的措辞是「『GPT 路由下没有网页搜索』在这些路径上不再成立」(v3.20.0-zh.md:92)——注意这是发布说明的概括口径,不是攻略文档里被删掉的原句。
  • 从哪看出来:提交 bdeaac75 fix(proxy): support Codex Alpha Search and Claude hosted WebSearch (#5681),对应 issue #5363、#5378。发布说明称这条主线由一位外部贡献者完成(v3.20.0-zh.md:323)。

请注意发布说明那句话里的「在这些路径上」五个字,它不是修辞。按发布说明的描述(v3.20.0-zh.md:92),这次桥接的边界是:Claude Code 内置的 WebSearch 工具被桥接到 Responses 与 Codex OAuth 后端;搜索在上游执行而不是本地抓取;结果以成对的 Anthropic 搜索块返回,引用保留并合并;搜索次数计入用量。但桥接只覆盖 Responses 转换路径——Chat Completions 上游仍不支持托管 WebSearch。

所以更准确的读法是:那句「不支持」不是被全局删除,而是被收窄成了「在另外那些路径上仍然成立」。一万行代码打通的是一条支路,不是全部。

黑名单没有对应表达,于是它选择报错

真正值得学的部分在请求转换那一侧。

Anthropic 的 WebSearch 工具允许调用方给出域名白名单 allowed_domains,也允许给出黑名单 blocked_domains。而目标协议这一侧只有白名单,没有黑名单。翻译层撞上了一个经典处境:源协议能表达的东西,目标协议表达不了。

src-tauri/src/proxy/providers/transform_responses.rs:374-409 把两种情况分开处理。白名单走映射,落进 filters.allowed_domains:403-409);黑名单则在 :378 处直接拦下:

let blocked_domains = tool.get("blocked_domains") ... ;          // :374
if blocked_domains.is_some() {                                    // :378
    ... "Anthropic WebSearch blocked_domains cannot be represented by the Responses API"  // :382
}
if let Some(allowed_domains) = tool.get("allowed_domains") {      // :403
    ... "filters": {"allowed_domains": allowed_domains}           // :409
}

(上面是按行号摘出的骨架,省略号处是完整的构造与返回代码,以仓库里这一段的原文为准。)

那句错误文案本身就是设计意图:这个约束「无法被 Responses API 表达」,所以请求整个被拒绝,而不是丢掉约束继续跑。 如果翻译层选择「悄悄把 blocked_domains 丢掉、照常发起搜索」,调用方那边不会收到任何提示——它以为自己屏蔽了某几个域名,实际上搜索是全开的。这是一次静默的安全降级:功能看起来正常,保护却没了。 报错至少让调用方知道这个约束没被接受。

这个原则在测试名里被写死了:transform_responses.rs:3579test_hosted_web_search_blocked_domains_fails_closed。函数名里带 fails_closed,意味着它不是随手加的一个校验,而是被当作契约测试来守的。

顺带说一句:这里的不对称不是谁做得不好,而是两套协议的能力集合本来就不重合。翻译层能做的选择只有报错、放宽、或者自己实现一层过滤。它选了第一种。

max_uses 的三种命运

同一条工具定义上还有一个 max_uses,限制这次响应里最多允许几次搜索。它比黑名单更麻烦——因为在不同上游下,它有三种完全不同的落地方式。按发布说明与升级提醒(v3.20.0-zh.md:92:290):

上游情形max_uses 怎么处理
Responses API(原生支持)映射成 max_tool_calls 下发,由上游限额
Codex OAuth 后端 + 请求强制使用该工具该后端拒绝 max_tool_calls,改在响应流中截断,本地限额
Codex OAuth 后端 + 未强制却带了 max_uses显式报错

三条都能在源码里找到落点。原生映射在 transform_responses.rs:1933result["max_tool_calls"] = json!(max_uses);,配套测试是 :3384test_api_key_hosted_web_search_maps_max_uses_to_max_tool_calls,反向用例在 :3413 断言这个字段不存在。拒绝 max_tool_calls 的原因写在 :1909 的注释开头:// The ChatGPT Codex contract rejects max_tool_calls. Without a。流中截断的实现在 streaming_responses.rs:2042-2071max_uses: Option<u64> 一旦超限,就往对应的搜索 id 上插一个 web_search_max_uses_exceeded_error(),错误码字面量是 "error_code": "max_uses_exceeded"(见 streaming_responses.rs:6022handlers.rs:3438)。

这条路上还有两处边界,都单独写了文案,很能说明这一万行的密度都消耗在哪:

  • streaming_responses.rs:3045:4270"Responses upstream started a web search beyond max_uses while another content block was incomplete"——上游在另一个内容块还没收完的时候又开了一次超限搜索。这种交错情况没有被当作普通超限一并处理,而是有自己的分支。
  • handlers.rs:555"Cannot enforce Anthropic WebSearch max_uses on a compressed Codex SSE response ({encoding})"——响应流被压缩了就没法边解边截,于是直接拒绝,而不是放宽。 这是和黑名单完全相同的选择:宁可失败,不可悄悄放开一个调用方以为存在的限制。

把三种命运和这两处边界连起来看,「不可表达的约束一律显式报错」这句话就不是口号了,它在四五个不同位置都被具体实现了一遍。

一处需要提醒的文档口径差

发布说明列举「哪些约束不可表达」时,在两个章节里给的清单并不完全一样。新功能章(v3.20.0-zh.md:92)与升级提醒章(:290)都列了四项,前三项两处一致——blocked_domains、非直连调用方、response_inclusion第四项两处给的不是同一件事。

两处都在同一份文档里,第四项对不上。我们只逐条核对了 blocked_domains 在源码里的实现,没有核到哪一处列举更完整,因此这里只如实记下前三项一致、第四项两处不同这个事实,不去推断哪一处是对的。引用这份清单时,写前三项加一句「另有若干」比照抄任何一处都稳妥。

你可以自己核到这一行

想复现这篇里的每个数字,在你自己 clone 的仓库目录下按顺序跑这几条就够了:

git show --stat bdeaac75 | tail -12
git show --stat bdeaac75 -- docs/guides/
grep -n "blocked_domains cannot be represented" src-tauri/src/proxy/providers/transform_responses.rs
grep -n "fails_closed" src-tauri/src/proxy/providers/transform_responses.rs
grep -n "max_uses_exceeded" src-tauri/src/proxy/providers/streaming_responses.rs src-tauri/src/proxy/handlers.rs

Windows 下如果用的是 PowerShell,grep 换成 Select-String -Path <文件> -Pattern <关键词>;用 Git Bash 则上面几条可以原样跑。以上为按仓库中的路径与关键词组合的示例,未经实测,以你本地 git 与检索工具的实际输出为准。

最后提醒版本口径

这篇讲的所有行号都锚在 v3.20.1 快照上。bdeaac75 这次提交的 diff 规模是历史事实,重新 clone 也能数出同样的九个文件与一万行;但源码行号、错误文案措辞、以及桥接覆盖到哪些路径,都会随版本移动。这个项目在 v3.19.2 到 v3.20.1 之间就叠了一百个提交,任何「它就是这样」的说法都得挂上版本号才成立。

还有一点值得说在最后:这一万行改的是「协议之间怎么翻译」,不是「搜索质量如何」。它让一条原本走不通的路走通了,至于走通之后效果怎么样,那是另一个问题,源码里没有答案,这篇也不打算替它回答。


本文依据 CC Switch 官方仓库(github.com/farion1231/cc-switch)的 README、docs/ 下的用户手册、路由指南与发布说明,以及 src/src-tauri/tests/ 的源码整理,核对日 2026-08-31,对应仓库快照 3217f725(仓库内版本号 3.20.1)。本文内容为仓库源码与文档口径,我们没有安装或运行过这个桌面应用,因此不涉及界面外观、操作手感与切换速度的任何描述。文中出现的阈值与默认值均为源码中的默认配置,不构成对实际运行结果的保证。该项目仍在快速迭代,版本与默认值随时可能变动,请以仓库最新内容为准。

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

留言讨论

评论发布后会被人工复核,违规内容将被删除。

    还没有人评论,来说说你的看法

    如果发表没有反应,可以前往联系我们告诉我们。

    在 CC Switch 里加一个国内直连的供应商

    力达云网关,注册送 ¥5 额度,一期提供 DeepSeek。

    去添加

    这个页面有问题?

    提交时会附带当前页面地址和浏览器信息,帮助我们定位问题。不填联系方式即为匿名。