Headroom + cc-switch + GLM:给 Claude Code 省钱的代理链路搭建指南

把 Claude Code 的 API 调用链路改成 Claude Code → Headroom → cc-switch → GLM,用国产模型替代 Claude 的同时,靠 Headroom 压缩上下文进一步降低 token 消耗。本文记录完整的搭建过程。


为什么要这么折腾

Claude Code 用起来很爽,但 Claude 的 API 价格摆在那里——尤其是长上下文对话,token 消耗很快。国内智谱 GLM 提供了 Anthropic 兼容接口,可以完全替代 Claude 的模型层,价格低很多。当然claude也可以用,同理。

但光替换模型还不够。Claude Code 默认会把完整对话历史每次都发上去,如果能在中间加一层压缩代理,剪掉冗余的上下文,token 消耗还能再省一笔。

所以最终链路设计成这样:

1
2
3
Claude Code → Headroom (:8787,压缩上下文)
→ cc-switch (:15721,注入 GLM key)
→ https://open.bigmodel.cn/api/anthropic (glm-5.2)

三个组件各司其职:

  • Headroom:HTTP 代理,负责压缩发往上游的上下文,省 token。
  • cc-switch:GUI 应用,本地代理 :15721,保管真实 GLM API key,并把 Claude 的各模型名映射成 glm-5.2
  • GLM:智谱开放平台提供的 Anthropic 兼容接口。

前置条件

  • macOS(Apple Silicon 或 Intel 均可)。
  • 已安装 cc-switch/Applications/CC Switch.app)并配置好一个 GLM provider(本文用 Anthropic 兼容模式)。
  • 已有智谱 API key(在 open.bigmodel.cn 获取)。
  • 系统自带 Python 3.9 不够用,Headroom 需要 Python ≥ 3.10,所以需要用 uv 单独建一个 Python 3.12 的虚拟环境。

第一步:安装 uv

Headroom 通过 uv 安装。如果还没装 uv:

1
curl -LsSf https://astral.sh/uv/install.sh | sh

安装后 uv 在 ~/.local/bin/uv,确认一下:

1
~/.local/bin/uv --version

如果提示找不到命令,把 ~/.local/bin 加进 PATH,写进 ~/.zshrc

1
export PATH="$HOME/.local/bin:$PATH"

第二步:用 uv 创建 Python 3.12 环境并安装 Headroom

系统 Python 3.9 太旧了,用 uv 自带的 Python 管理功能建一个新环境:

1
2
3
uv venv ~/.headroom-venv --python 3.12
source ~/.headroom-venv/bin/activate
uv pip install headroom

验证安装:

1
2
~/.headroom-venv/bin/headroom --version
# 期望输出类似:headroom, version 0.28.0

第三步:配置 cc-switch

打开 CC Switch.app,添加一个 provider,类型选 Anthropic 兼容

  • Base URL:https://open.bigmodel.cn/api/anthropic
  • API Key:你的智谱 key
  • 模型映射:把 Opus / Sonnet / Haiku / Fable 全部映射到 glm-5.2

然后开启本地代理enableLocalProxy: true),cc-switch 会在 127.0.0.1:15721 启动一个代理。点应用后,cc-switch 会写入 ~/.claude/settings.json,内容大致长这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"env": {
"ANTHROPIC_AUTH_TOKEN": "PROXY_MANAGED",
"ANTHROPIC_BASE_URL": "http://127.0.0.1:15721",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "claude-opus-4-8",
"ANTHROPIC_DEFAULT_OPUS_MODEL_NAME": "glm-5.2",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-4-6",
"ANTHROPIC_DEFAULT_SONNET_MODEL_NAME": "glm-5.2",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-haiku-4-5",
"ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME": "glm-5.2",
"ANTHROPIC_DEFAULT_FABLE_MODEL": "glm-5.2",
"ANTHROPIC_DEFAULT_FABLE_MODEL_NAME": "glm-5.2"
}
}

注意 ANTHROPIC_AUTH_TOKEN: PROXY_MANAGED 是 cc-switch 的占位符——真实的 key 存在 cc-switch 内部的 SQLite 数据库里(~/.cc-switch/cc-switch.db),不会以明文出现在 settings.json 中。

此时链路已经是 Claude Code → cc-switch → GLM 了,但还没有压缩。下一步把 Headroom 插到中间。


第四步:编写 Headroom 启动脚本

新建 ~/.headroom-start.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cat > ~/.headroom-start.sh <<'EOF'
#!/usr/bin/env zsh
source "$HOME/.headroom-venv/bin/activate"

# Upstream = cc-switch 的本地代理(负责注入 GLM key)
export ANTHROPIC_TARGET_API_URL="http://127.0.0.1:15721"
export HEADROOM_HOST=127.0.0.1

# 输出压缩:默认关掉,设为 1 启用
export HEADROOM_OUTPUT_SHAPER="${HEADROOM_OUTPUT_SHAPER:-0}"

# 跳过上游可达性检查——cc-switch 是 loopback 代理,
# Headroom 的 /readyz 探活可能误报
export HEADROOM_SKIP_UPSTREAM_CHECK=1

exec headroom proxy --port 8787 --host 127.0.0.1
EOF

chmod +x ~/.headroom-start.sh

几个关键环境变量:

  • ANTHROPIC_TARGET_API_URL 指向 cc-switch(:15721),而不是直连 GLM——key 注入仍由 cc-switch 负责,Headroom 只管压缩和转发。
  • HEADROOM_SKIP_UPSTREAM_CHECK=1:cc-switch 是 loopback 代理,Headroom 对它做 /readyz 探活会误报,关掉换手动验证。
  • HEADROOM_OUTPUT_SHAPER:默认 0(不压缩输出),想连模型回包一起压缩就设 1

先手动跑一次,确认能起来:

1
2
3
~/.headroom-start.sh
# 另开终端验证:
curl -s http://127.0.0.1:8787/livez

确认无误后 Ctrl+C 停掉,下一步交给 launchd 托管。


第五步:用 launchd 设置开机自启

创建 ~/Library/LaunchAgents/com.headroom.proxy.plist

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
cat > ~/Library/LaunchAgents/com.headroom.proxy.plist <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.headroom.proxy</string>

<key>ProgramArguments</key>
<array>
<string>/bin/zsh</string>
<string>__HOME__/.headroom-start.sh</string>
</array>

<key>RunAtLoad</key>
<true/>

<key>KeepAlive</key>
<true/>

<key>ThrottleInterval</key>
<integer>10</integer>

<key>StandardOutPath</key>
<string>/tmp/headroom-proxy.log</string>

<key>StandardErrorPath</key>
<string>/tmp/headroom-proxy.log</string>

<key>ProcessType</key>
<string>Background</string>
</dict>
</plist>
EOF

sed -i '' "s|__HOME__|$HOME|" ~/Library/LaunchAgents/com.headroom.proxy.plist

加载并启动:

1
2
launchctl load ~/Library/LaunchAgents/com.headroom.proxy.plist
launchctl list | grep headroom

看到 com.headroom.proxy 出现在列表里就对了。plist 的几个关键配置:

  • RunAtLoad=true:开机或加载时自动启动。
  • KeepAlive=true:进程意外退出会自动拉起。
  • ThrottleInterval=10:崩溃重启的最小间隔 10 秒,防止重启风暴。
  • 日志统一写到 /tmp/headroom-proxy.log

日常管理命令:

1
2
3
4
5
6
7
8
# 停止
launchctl unload ~/Library/LaunchAgents/com.headroom.proxy.plist

# 重启
launchctl unload ... && launchctl load ...

# 看日志
tail -f /tmp/headroom-proxy.log

第六步:把 Claude Code 的 base URL 指向 Headroom

cc-switch 默认会把 ANTHROPIC_BASE_URL 写成 http://127.0.0.1:15721(直连 cc-switch)。要插入 Headroom,需要改成 :8787

1
2
3
4
5
6
7
8
python3 - <<'EOF'
import json, pathlib
p = pathlib.Path.home() / ".claude" / "settings.json"
cfg = json.loads(p.read_text())
cfg.setdefault("env", {})["ANTHROPIC_BASE_URL"] = "http://127.0.0.1:8787"
p.write_text(json.dumps(cfg, indent=2, ensure_ascii=False))
print(p.read_text())
EOF

改完后必须重启 Claude Code——当前会话用的还是改之前的 base URL,不重启不生效。


第七步:验证整条链路

直接请求 Headroom,确认能拿到 GLM 的回复

1
2
3
4
5
curl -s -X POST http://127.0.0.1:8787/v1/messages \
-H "x-api-key: PROXY_MANAGED" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"model":"glm-5.2","max_tokens":16,"messages":[{"role":"user","content":"say ok"}]}'

正常应该返回带 msg_... id 的 JSON,content 是 GLM 的回复内容。

Headroom 自检

1
~/.headroom-venv/bin/headroom doctor --port 8787

注意 doctor/livez 探针对 Headroom 自己的端口有效,不要让它去探 cc-switch 的 15721

检查 launchd 状态

1
launchctl list | grep headroom

第二列是 0 表示运行正常;非零说明崩了,看 /tmp/headroom-proxy.log 排查。


一个重要的大坑

cc-switch 有一个很坑的行为:如果你在 GUI 里切换 provider 或重新点「应用」,它会重写 ~/.claude/settings.json,把 ANTHROPIC_BASE_URL 改回 http://127.0.0.1:15721直接绕过了 Headroom

所以:

  • 每次在 cc-switch 里动过之后,重新执行第六步把 base URL 改回 :8787,然后重启 Claude Code。
  • 或者干脆配好之后就不要再动 cc-switch,只在 GLM key 过期需要更新时才进 GUI。

文件清单

路径 作用
~/.local/bin/uv uv 安装器
~/.headroom-venv/ Headroom 专用 Python 3.12 venv
~/.headroom-start.sh Headroom 启动脚本
~/Library/LaunchAgents/com.headroom.proxy.plist launchd 自启配置
/tmp/headroom-proxy.log Headroom 运行日志
~/.claude/settings.json Claude Code 配置(base URL 指向 :8787
~/.cc-switch/ cc-switch 数据库与配置(含真实 GLM key)

常见问题

Q: Claude Code 启动报连接 :8787 失败?
A: Headroom 没起来。launchctl list | grep headroom 看进程在不在,不在就 launchctl load ~/Library/LaunchAgents/com.headroom.proxy.plist;在但崩了就看 /tmp/headroom-proxy.log

Q: 改了 cc-switch 后 Claude Code 不压缩了?
A: 检查 ~/.claude/settings.jsonANTHROPIC_BASE_URL 是不是被改回 :15721 了,改回 :8787 并重启 Claude Code。

Q: 想连模型输出也压缩?
A: 编辑 ~/.headroom-start.sh,把 HEADROOM_OUTPUT_SHAPER 改成 1,然后 launchctl unload && load 重启 Headroom。

Q: 怎么临时停掉压缩直连 GLM?
A: 把 ANTHROPIC_BASE_URL 临时改回 http://127.0.0.1:15721 并重启 Claude Code,用完再改回来。Headroom 进程本身不用停。


最后的效果

全部配好之后,每次 Claude Code 发请求都会经过 Headroom 压缩再转发到 cc-switch,最后由 cc-switch 注入 GLM key 送到智谱。实际使用下来,上下文压缩能省掉相当可观的 token 量,再加上 GLM 本身比 Claude 便宜很多,这套链路长期用下来确实能省不少。

唯一需要留意的就是那个 cc-switch 覆写 base URL 的坑,记住每次动过 cc-switch 后改回来就行。如果你只是稳定用同一个 GLM provider,配好之后几乎不用再管它。