不改一行代码,零网络劫持,它凭什么能接管 Claude Code、Codex 和 Gemini CLI 的全部 API 请求?
一句话介绍:CC Switch 是一款基于 Tauri 2 的桌面应用,核心功能是统一管理 Claude Code、Codex、Gemini CLI 等 7 个 AI 编程工具的 API 供应商,支持一键切换、自动故障转移、用量统计和 Skill/MCP 多端同步。
听起来平平无奇?但当你知道它没有用任何网络层劫持技术,却能让所有 AI 工具的 API 请求从它这里过一遍时,事情就变得有意思了。
本文是第一篇,聚焦两个核心问题:它是怎么拦截请求的?它怎么做到不拦截也能统计用量?
一、秘密藏在一个字段里
Claude Code 的配置文件在 ~/.claude/settings.json,里面有一个关键的字段:
json
{
"env": {
"ANTHROPIC_BASE_URL": "https://api.anthropic.com"
}
}
Codex 的配置在 ~/.codex/config.toml:
ini
base_url = "https://api.openai.com/v1"
Gemini CLI 的配置在 ~/.gemini/.env:
ini
GOOGLE_GEMINI_BASE_URL=https://generativelanguage.googleapis.com
你大概已经猜到关键了------这些 AI 工具都有一个共同的设计:BASE_URL 写在配置文件里,而不是硬编码进二进制。 这不是巧合,是企业市场的要求:大公司需要通过自己的代理访问 API,不提供这个配置项就没法卖企业客户。
而 CC Switch 的做法朴素到令人意外:
找到每个工具的配置文件,把
BASE_URL改成http://127.0.0.1:代理端口,然后在自己本地跑一个 HTTP 代理来转发请求。
零网络劫持,零 hosts 篡改,零进程注入。就是改配置文件。
二、逐个拆解:对每个工具做了什么
2.1 Claude Code
项目
细节
被修改的文件
~/.claude/settings.json
改了什么
ANTHROPIC_BASE_URL → http://127.0.0.1:47832
API Key
替换为 PROXY_MANAGED 占位符,真实 Key 存在 CC Switch 的 SQLite 里
模型名
写入 Claude 角色别名(claude-sonnet-4-6、claude-opus-4-8[1M] 等),由代理映射到供应商实际模型 ID
删除的字段
ANTHROPIC_MODEL、ANTHROPIC_REASONING_MODEL、ANTHROPIC_DEFAULT_SONNET_MODEL_NAME 等 12 个旧字段,避免冲突
2.2 Claude Desktop
与 Claude Code 类似,但走独立路由前缀 /claude-desktop/,以便区分两个客户端的请求来源。Claude Desktop 的模型列表需要单独处理,代理要模拟 /claude-desktop/v1/models 端点。
2.3 Codex (OpenAI)
项目
细节
被修改的文件
~/.codex/auth.json + ~/.codex/config.toml
改了什么
TOMP 中的 base_url → http://127.0.0.1:47832
额外操作
生成 model catalog JSON 文件,列出当前供应商支持的模型列表
Codex 的配置是 TOML 格式,CC Switch 用了 toml_edit 库来修改------这个库保留了原文件中的注释和格式,不会"格式化暴力覆盖"。
2.4 Gemini CLI
项目
细节
被修改的文件
~/.gemini/.env
改了什么
GOOGLE_GEMINI_BASE_URL → http://127.0.0.1:47832
Gemini 还有一个 ~/.gemini/settings.json,CC Switch 会修改其中的 security.auth.selectedType 字段,在 gemini-api-key 和 oauth-personal 之间切换,以适配不同供应商的认证模式。
2.5 代理服务器做了什么
CC Switch 内嵌了一个基于 Axum(Rust HTTP 框架)的代理服务器,监听 127.0.0.1:代理端口。路由表覆盖了所有主流 API:
bash
POST /v1/messages → Anthropic Messages API
POST /v1/chat/completions → OpenAI Chat API
ANY /v1beta/*path → Gemini API
POST /v1/responses → OpenAI Responses API
POST /codex/v1/chat/completions → Codex 专用路由
...
每个请求到达后的处理流程:
- 识别来源:根据 URL 路径前缀判断来自哪个应用
- 选供应商:从 SQLite 数据库查当前应用绑定的供应商
- 熔断检查:检查该供应商是否处于熔断状态
- 替换认证信息 :把
PROXY_MANAGED占位符替换为真实 API Key - 模型名映射 :把
claude-sonnet-4-6映射成供应商的实际模型 ID - 构建上游请求:用供应商的真实地址和 Key 发 HTTP 请求
- 透明返回:把上游响应原样传回给客户端
三、接管流程:不只是改文件
代码里的 set_takeover_for_app 方法是一个多步骤的严谨流程(services/proxy.rs:624):
- 检查代理是否运行 → 没启动则自动启动
- 幂等检查 → 如果已经是接管状态,直接返回
- 备份原始配置 → 把
settings.json/config.toml/.env存到 SQLite 的live_backup表 - 同步 API Key → 把当前配置文件中的真实 Key 提取到供应商配置中
- 写入接管配置 → 改
BASE_URL、替换 Key、写入模型别名 - 标记已接管 → 数据库记录状态,支持后续"关闭接管"时恢复
- 失败回滚 → 如果任一步骤失败,自动从备份恢复原始配置
关闭接管时反过来:从 live_backup 表取出备份,写回原始配置文件,删除备份记录。整个过程对用户来说是一个开关的操作。
四、更惊艳的部分:不接管也能统计用量
前面说的全是"接管"模式------改配置文件,让请求经过本地代理。但如果用户没用代理模式呢?假设 Codex 直连 OpenAI,CC Switch 还能统计到用量吗?
能。而且不需要任何拦截。
4.1 Codex 自己会写日志
Codex CLI 在运行过程中,会把每个会话的详细事件写入 ~/.codex/sessions/ 下:
javascript
~/.codex/sessions/
├── 2026/
│ └── 07/
│ └── 14/
│ ├── abc123.jsonl
│ └── def456.jsonl
这些 .jsonl 文件里记录了完整的事件流:
css
{"type": "session_meta", "payload": {"id": "thread-abc", ...}}
{"type": "turn_context", "payload": {"model": "claude-sonnet-4-6", ...}}
{"type": "event_msg", "type": "token_count", "total_token_usage": {"input": 15000, "output": 3200, ...}}
每次 API 调用完成后,Codex 自己就把 token 用量写进去了。
4.2 CC Switch 的做法:纯读文件,不拦截
services/session_usage_codex.rs 的注释写得很直白:
javascript
从 ~/.codex/sessions/ 下的 JSONL 会话文件中提取精确 token 使用数据,
替代原有的 state_5.sqlite 估算方案。
数据流:
~/.codex/sessions/YYYY/MM/DD/*.jsonl → 增量解析 → delta 计算 → 费用计算 → proxy_request_logs 表
流程:
- 扫描
~/.codex/sessions/下所有 JSONL 文件 - 查
sync_state表:每个文件上次读到了第几行?文件修改时间变了没? - 增量解析:从上次断点继续读,只解析新增的行
- 找
token_count事件:提取累计 token 用量 - 算 delta:当前累计值 - 上次累计值 = 本次调用的消耗
- 算费用:根据当前供应商的模型定价,计算美元费用
- 写日志 :写入 CC Switch 的
proxy_request_logs表 - 更新
sync_state:记录当前行号和文件 mtime - 去重 :同一个
message_id不重复写入
4.3 不止 Codex,Claude Code 和 Gemini 也一样
应用
被读取的目录
解析方式
Claude Code
~/.claude/projects/*/ JSONL 文件
解析 assistant message 中的 usage 字段
Codex
~/.codex/sessions/YYYY/MM/DD/*.jsonl
解析 event_msg(type=token_count)
Gemini CLI
Gemini 原生日志
解析 Gemini 格式的 token usage
OpenCode
OpenCode 会话目录
增量子代理处理
4.4 同步频率
代码在 lib.rs:1080-1139,应用启动时 spawn 一个后台 tokio 任务:
- 启动时立即执行一次全量同步
- 之后每 60 秒执行一次增量同步
- 用户也可以在前端手动点击刷新触发同步
每次同步只读增量部分,不会重复统计。
五、这背后要求什么?
要做到以上这些,CC Switch 的开发团队需要彻底搞清楚 7 个 AI 工具的以下细节:
- 配置文件在哪、什么格式(JSON / TOML / YAML / .env / 混用)
- BASE_URL 和环境变量的确切键名(每个工具都不一样)
- 认证机制的差异(API Key / OAuth / 托管账号)
- 模型名的命名约定和映射规则
- 会话日志的目录结构、文件格式、事件语义、字段含义
- 子代理 session 的父子关系(
session_id指向父线程) - Codex 版本升级后 SQLite 文件名的变化(
state_5.sqlite) - 不同操作系统下的路径差异(Hermes 在 Windows 下是
%LOCALAPPDATA%\hermes)
这些都不是官方文档会写清楚的东西。 每一个字段、每一条路径、每一种特殊情况,都是逆向工程和踩坑踩出来的。版本号到了 3.17,issue 数量可想而知。
六、一个关键的技术判断
CC Switch 之所以能存在,依赖于一个行业共识:所有 AI 编程工具都通过配置文件暴露 BASE_URL。
这个共识不是为了让中间人工具存活------是为了企业市场。Anthropic、OpenAI、Google 都有自己的企业版 API Gateway,大公司内部的开发者需要走内网代理,不支持自定义 BASE_URL 就没法做企业生意。
CC Switch 只是利用了这个设计,做了一套企业级的多供应商管理工具。它的竞争对手不是某个小工具,而是"每次手动改配置文件"这种原始操作。
下一篇将深入 Skill 和 MCP 模块:一个 3100 行的 Rust 代码如何实现跨 5 个 AI 工具的 Skill 包管理,以及 MCP 配置的 JSON/TOML/YAML 多格式适配器是怎么设计的。