Install
openclaw skills install @oracis/claude-code-third-party-model将本地 Claude Code 连接到第三方兼容 Anthropic 或 OpenAI 模型端点,支持实测模型名、环境变量优先级判定和多配置源排查。
openclaw skills install @oracis/claude-code-third-party-model开源仓库:https://github.com/oracis/claude-code-third-party-model 本技能自带两个零依赖脚本(在
scripts/目录下):ccproxy.py—— Anthropic ↔ OpenAI 协议转换网关(纯标准库,约 640 行);ccswitch.py—— 多 profile 一键切换 + 网关启停协调(跨平台,约 430 行)。 另有examples/profiles/提供开箱可用的settings.json模板。
不要相信文档里的模型名,也不要猜哪份配置生效 —— 两件事都实测。
第三方文档经常出现旧模型名 / 别名混写(例如 DeepSeek 官方两版文档分别写
deepseek-flash 和 deepseek-v4-flash)。以服务端报错为准。
先看账号可用清单:
curl -s https://api.deepseek.com/models -H "Authorization: Bearer $KEY"
再拿候选名逐个打 Anthropic 兼容端点(这才是权威,/models 常漏别名):
KEY=$(python -c "import json;print(json.load(open(r'C:/Users/<u>/.claude/settings.json'))['env']['ANTHROPIC_AUTH_TOKEN'])")
for M in "deepseek-flash" "deepseek-flash[1m]" "deepseek-v4-flash" "deepseek-chat" "deepseek-v4.1-flash"; do
printf '%-24s => ' "$M"
curl -s -m 45 https://api.deepseek.com/anthropic/v1/messages \
-H "x-api-key: $KEY" -H "anthropic-version: 2023-06-01" -H "content-type: application/json" \
-d "{\"model\":\"$M\",\"max_tokens\":8,\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}" \
| python -c "import sys,json;d=json.load(sys.stdin);print('OK ->',d['model'] if 'model' in d else 'ERR '+str(d.get('error',{}).get('message'))[:160])"
done
要点:
--noproxy 声明直连目标
(如 --noproxy api.deepseek.com),或按第五部分把目标列入 no_proxy 环境变量。model 字段是服务端归一化后的真名,能看出哪些是别名
(如 deepseek-chat → 回 deepseek-v4-flash)。已知别名关系(DeepSeek,2026-09 实测):
deepseek-flash=deepseek-v4-flash=deepseek-chat(同一模型,即 V4.1 Flash);deepseek-v4.1-flash不存在。
[1m] 后缀是什么[1m] 不是模型名的一部分,是 Claude Code 本地的「上下文窗口声明」后缀,服务端会剥离
(发 deepseek-flash[1m] 回体是 "model":"deepseek-flash")。
modelUsage.contextWindow 变 1000000。[claude-code:unrecognized_model] 到 stderr,
无害、不影响功能,CLAUDE_CODE_DISABLE_UNKNOWN_MODEL_WINDOW_ENFORCEMENT=1 也消不掉,
别在这上面浪费时间。CLAUDE_CODE_AUTO_COMPACT_WINDOW(DeepSeek 官方建议 786432)。~/.claude/settings.json(用户级、跨项目,是唯一真相源):
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"env": {
"ANTHROPIC_BASE_URL": "https://api.deepseek.com/anthropic",
"ANTHROPIC_AUTH_TOKEN": "<KEY>",
"ANTHROPIC_MODEL": "deepseek-flash[1m]",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "deepseek-flash[1m]",
"ANTHROPIC_DEFAULT_OPUS_MODEL_NAME": "deepseek-flash",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "deepseek-flash[1m]",
"ANTHROPIC_DEFAULT_SONNET_MODEL_NAME": "deepseek-flash",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "deepseek-flash",
"ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME": "deepseek-flash",
"ANTHROPIC_DEFAULT_FABLE_MODEL": "deepseek-flash[1m]",
"ANTHROPIC_DEFAULT_FABLE_MODEL_NAME": "deepseek-flash",
"CLAUDE_CODE_SUBAGENT_MODEL": "deepseek-flash[1m]",
"CLAUDE_CODE_EFFORT_LEVEL": "max",
"CLAUDE_CODE_AUTO_COMPACT_WINDOW": "786432",
"API_TIMEOUT_MS": "600000",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
},
"model": "deepseek-flash[1m]",
"tui": "fullscreen"
}
_MODEL_NAME 是显示名(不带 [1m]);_MODEL 是真正发给 API 的 ID。
DEFAULT_*_MODEL 让 /model opus|sonnet|haiku 都落到同一个模型。
Claude Code 现在是单文件 native 二进制(不是 js),直接从里面提取权威变量名:
B="$APPDATA/npm/node_modules/@anthropic-ai/claude-code/bin/claude.exe"
strings -n 6 "$B" | grep -ohE "ANTHROPIC_[A-Z_]+|CLAUDE_CODE_[A-Z0-9_]+|API_TIMEOUT_MS" | sort -u
(Windows 的 $APPDATA 在 Git Bash 下即 C:/Users/<u>/AppData/Roaming。)
值得注意的真实变量:ANTHROPIC_DEFAULT_FABLE_MODEL、
CLAUDE_CODE_{MAX_CONTEXT_TOKENS, AUTO_COMPACT_WINDOW, SUBAGENT_MODEL, EFFORT_LEVEL,
DISABLE_1M_CONTEXT, MODEL_CATALOG, MODEL_OVERRIDES...}。
优先级实测结论:settings.json 的 env 块 > 从 shell/系统继承的进程环境变量。
(官方文档那句「shell 导出的 ANTHROPIC_MODEL 优先于文件里的 model 键」说的是
model 键,不是 env 块,别被绕进去。)
验证方法(唯一可靠手段):跑一次带 JSON 输出的查询,读 modelUsage。
cd <项目目录> && claude -p "hi" --output-format json < /dev/null 2>&1 \
| tr -d '\r' | grep -o '"modelUsage":{.*' | head -c 600
看 canonicalModel 和 contextWindow 就知道真实生效的是什么。
验证子代理:提示里让它起一个 Agent,再 grep "canonicalModel":"..."。
依次查这些地方,任何一处都会造成「改了不生效」:
# 1) 用户/系统级持久环境变量(用 Python winreg 读,最省事)
python -c "
import winreg
for root,lab in ((winreg.HKEY_CURRENT_USER,'HKCU'),(winreg.HKEY_LOCAL_MACHINE,'HKLM')):
try:
k=winreg.OpenKey(root,r'Environment'); n=winreg.QueryInfoKey(k)[1]
for i in range(n):
nm,v,_=winreg.EnumValue(k,i)
if 'ANTHROPIC' in nm.upper() or 'CLAUDE' in nm.upper(): print(lab,nm,'=',v)
except FileNotFoundError: pass
"
# 2) 项目级覆盖(优先级高于用户级)
cat .claude/settings.json .claude/settings.local.json 2>/dev/null
# 3) ~/.claude.json 里的 model / env 字段
python -c "import json;d=json.load(open(r'C:/Users/<u>/.claude.json'));print({k:d[k] for k in ('model','env','primaryApiKey') if k in d})"
改 HKCU 环境变量后必须广播,否则已开着的资源管理器/终端读不到新值:
import ctypes
ctypes.windll.user32.SendMessageTimeoutW(0xFFFF, 0x1A, 0, ctypes.c_wchar_p('Environment'),
0x0002, 5000, ctypes.byref(ctypes.c_ulong()))
cp ~/.claude/settings.json ~/.claude/backups/settings.json.bak-$(date +%Y%m%d-%H%M%S);
若要动 HKCU,先把变量导出一份 json 到同目录。| 现象 | 根因 |
|---|---|
报 invalid_request_error 且列出支持名 | 模型名错/不存在,用报错里列的名字 |
每次启动一行 [claude-code:unrecognized_model] | 正常,第三方模型都不在 CC 目录里 |
contextWindow 只有 200000 | 模型名忘了加 [1m] |
| 改了 settings.json 不生效 | 有 HKCU/项目级/.claude.json 覆盖,或没重启 CLI |
| 命令行读注册表不方便 | 直接用 Python winreg,无需外部程序 |
| PowerShell 有时 exit 0 但无输出 | 该环境下 stdout 未必回显,换 bash + python 排查更可靠 |
export ANTHROPIC_MODEL=... 后没变化 | settings.json 的 env 块优先级更高,见 Step 7 |
不要猜。 慢只有四个来源:启动开销 / 网络 / 模型推理 / 交互感知。按下面顺序逐层排掉。
claude --model / --effort 等命令行参数
> ~/.claude/settings.json 的 env 块
> shell / 系统 继承来的环境变量
推论(最容易踩):export ANTHROPIC_MODEL=xxx 再启动 claude 不生效,因为
settings.json 的 env 块会覆盖它。所以:
claude --effort high --model deepseek-flash[1m] -p "..."/effort、/model 斜杠命令判据校验:
claude --effort bogus -p hi会打印合法值清单,比翻文档快。 实测合法值:low / medium / high / xhigh / max。off不在其中,会被静默忽略。
搭一个 127.0.0.1 上的透明转发,把 ANTHROPIC_BASE_URL 指过去,
就能录到 Claude Code 真实发出的 JSON(含 thinking 配置、工具数、body 大小)。
关键实现点:
http.server.ThreadingHTTPServer + http.client.HTTPSConnection,纯标准库。transfer-encoding: chunked),否则会破坏 SSE 流。ttfb / first_chunk / total / 请求体里的 thinking / 响应里的 usage。tag.txt 给每一轮实验打标签,避免多次运行串味。ANTHROPIC_BASE_URL 支持 http://,指向 127.0.0.1 时记得把 NO_PROXY 加上 127.0.0.1。⚠️ 最大坑:在 Bash 工具里用 nohup ... & 起的中继会随该次命令的进程组一起被杀,
表现为「Claude Code 连不上 → 一直重试 → 看起来像卡死」,
而且因为 SIGTERM 打断管道,连报错都看不到。
必须用工具自带的后台运行能力(run_in_background)启动长驻进程。
改坏 settings.json 后要立刻还原,别让它停在指向死端口的状态。
CLAUDE_CODE_EFFORT_LEVEL 对第三方模型通常是空操作实测(DeepSeek,2026-09 复核):在 low / medium / high / xhigh / max 五档下, Claude Code 发出的请求体完全一致:
"thinking": {"type": "adaptive", "display": "omitted"}
body_bytes 五档完全相同(66008)。因为 Claude Code 判定该模型走
adaptive thinking(模型自己决定想多久,不设 token 预算),
所以根本没有 effort 这个旋钮可拧。
跑出来的对照数据(同一道逻辑题,重复 3 次):
| effort | wall 均值 | 输出 token 均值 |
|---|---|---|
| high | 15.3s | 829 |
| max | 15.0s | 830 |
差异 0.3s / 1 个 token,纯噪声。 结论:对 DeepSeek 别在 effort 上调优,
它不改变任何东西。(low 更快的前提是模型真的收到小预算;adaptive 下不成立。)
判断某模型是否 adaptive:R 代码里 CCn({runtimeOverride, resolvedModel, canonicalModel})==="adaptive"
就读 ANTHROPIC_DEFAULT_*_MODEL_SUPPORTED_CAPABILITIES 里的能力声明。
有了中继记录(total = 纯 API 耗时)和 claude -p 的 wall time,一减就出来了:
Claude Code 开销 = wall_s − 中继记录的 total
实测样例(本机):
| 项 | 数值 |
|---|---|
| 中继测得的纯 API 耗时 | 2.9–5.6s(164–221 tok/s) |
| 裸 curl 直连同一道题 | 3.1s(181 tok/s) |
claude -p wall time | 13.4–17.8s |
claude --version(零模型调用) | 4.9–5.5s |
→ API 侧完全正常,瓶颈是 Claude Code 每次进程启动约 5 秒。
反过来说,如果 claude --version 都要好几秒,那「换模型/调 effort」全都白费。
启动慢的常见原因与验证:
for i in 1 2 3; do S=$(date +%s.%N); claude --version >/dev/null 2>&1; E=$(date +%s.%N); \
python -c "print(f'{$E-$S:.2f}s')"; done # 走 npm shim
EXE=<npm>/node_modules/@anthropic-ai/claude-code/bin/claude.exe
$EXE --version # 直接调二进制 —— 关键对照!
cat <claude.exe> > /dev/null # 纯磁盘读 227MB 要多久
echo 'exit 0' > /tmp/n.sh; bash /tmp/n.sh # 空 sh 脚本 —— 量 shell 层开销
旧结论(错误):「node -e "" 也要 2s → 杀软实时扫描在拖后腿,加 Defender 排除项」。
实测打脸:加了排除项(路径 + 进程)后毫无变化,而真正的差异在别处:
| 路径 | 耗时 |
|---|---|
claude.exe --version(直接调二进制) | 1.14–1.34s |
claude --version(经 npm 的 shim) | 4.06–5.66s |
| 空 sh 脚本(什么都不做) | 1.10–1.41s |
| 读 227MB 二进制 | 1.41s(161 MB/s,磁盘正常) |
→ 多出来的 3–4 秒是「多一层 shell 包装」的开销(claude 实际是
npm/claude 这个 #!/bin/sh 脚本,exec 到真正的 exe;在 MSYS 等 POSIX 兼容层下每层 sh
启动约 1.2s)。不是 Defender,不是磁盘,不是网络。
判断方法:bash /tmp/空脚本.sh 就要 1.2s 的话,说明是当前执行环境的进程创建开销,
用户在自己真实终端里不会有这个损耗 —— 别拿受限环境里的 wall time 当用户的真实体验。
node -e "" 慢不等于杀软:claude 是 native 二进制,根本不经 node 启动,
用 node 当对照组本身就是错的对照。
2026-09-23 的惨痛教训:先入为主地认为「慢 = 杀软」,加了一堆排除项, 最后 A/B/A 一测发现全部是噪声。具体数据:
| 项目 | ①无排除 | ②有排除 | ③撤销后 |
|---|---|---|---|
| 建 100 个文件 | 1391ms | 1432ms | 660ms(最快!) |
| 建 60 个目录 | 652ms | 464ms | 761ms(最慢) |
| python 启动(同一状态连测 3 轮) | 790 / 730 / 1303 ms |
同一状态下 python 启动能在 335–1303ms 间波动(4 倍!), 而排除项带来的差异只有几十毫秒 —— 完全淹没在噪声里。
必守规则:
du -sh 会超时被 SIGTERM,用 Python os.walk 加时间上限。最终结论:Defender 排除项在这台机器上无可测量收益,代价却是 若干目录脱离实时扫描 → 不加。 只有在「单个操作涉及成千上万小文件」(npm install / git clone / 解压大包) 且实测收益稳定 >30% 时,才值得针对那个具体目录临时加。
Defender 排除项若要加(需管理员 + 必须先问用户):
Add-MpPreference -ExclusionPath "<npm>\node_modules\@anthropic-ai\claude-code"
(读排除项也要管理员:HKLM ...\Windows Defender\Exclusions\Paths 普通权限 PermissionError)
加完必须复测;无改善就撤销(Remove-MpPreference),别留着白降防护。
会话内上下文缓存是生效的。同一 session 的第 2 个请求:
cache_read_input_tokens: 16384,input_tokens 从 16359 掉到 174。
别被假象骗了:每次 claude -p 都是全新会话(冷缓存),
单个请求的 cache_read 恒为 0 —— 这不代表缓存坏了。
要验证缓存,必须让一次会话发出 ≥2 个请求(例如给它一个需要调用工具的提示)。
display: "omitted" 时思考过程不渲染,用户盯着空白等 = 主观上「好慢」。
交互模式下让思考可见的设置键是 showThinkingSummaries: true
(源码:showThinkingSummaries ?? false → 为真则 display: "summarized")。
合法 display 值:summarized / omitted / highlights。
同一道题的裸 API 对照(DeepSeek):
| 模型 | 简单推理 | 多步推理 | 吞吐 |
|---|---|---|---|
deepseek-flash | 2.7–3.5s ✅ | 4.6–10.6s ✅ | 164–204 tok/s |
deepseek-v4-pro | 24.9–32.9s ✅ | 67.6–74.0s ⚠️ | 41–52 tok/s |
| 现象 | 先查什么 |
|---|---|
| 每次启动都慢 | claude --version 计时;杀软实时扫描;二进制大小 |
claude -p 慢但交互不慢 | 每次调用都要付一次进程启动成本,用会话复用/--continue |
| 第一个字出来很慢 | 中继的 ttfb vs first_chunk;adaptive thinking 在想 |
| 每轮都慢 | 中继看 total;对比裸 curl 同题 |
| 换 pro 后更慢 | 正常,见 Step 13 |
| 调 effort 没变化 | 正常,见 Step 9 |
结论:配置只写在 ~/.claude/settings.json,不要同时在 Windows 环境变量里放一份。
理由(实测):
settings.json 的 env 块优先级更高(Step 5),
两处同值时行为一致,两处不同值时以 settings.json 为准。settings.json 被删、或 CLAUDE_CONFIG_DIR 变了,
环境变量那份会静默接管并指向另一个模型(可能更贵)。ANTHROPIC_AUTH_TOKEN 进了系统环境,
任何 npm postinstall、构建脚本都能读到你的 API Key。settings.json 只有 CC 自己读。清理流程(清理前先确认没有别的工具依赖这些变量):
import winreg, json, os, ctypes
# 1) 先备份
k = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'Environment', 0, winreg.KEY_QUERY_VALUE)
hits = {winreg.EnumValue(k,i)[0]: winreg.EnumValue(k,i)[1]
for i in range(winreg.QueryInfoKey(k)[1])
if 'ANTHROPIC' in winreg.EnumValue(k,i)[0].upper()
or 'CLAUDE_CODE' in winreg.EnumValue(k,i)[0].upper()}
json.dump(hits, open(backup_path,'w',encoding='utf-8'), indent=2)
# 2) 删除
k = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'Environment', 0, winreg.KEY_SET_VALUE)
for name in hits: winreg.DeleteValue(k, name)
# 3) 广播,否则已开的终端仍持有旧值
ctypes.windll.user32.SendMessageTimeoutW(0xFFFF, 0x1A, 0,
ctypes.c_wchar_p('Environment'), 0x0002, 5000, ctypes.byref(ctypes.c_ulong()))
清理前必查:有没有别的消费者(aider / codex / 自己写的 curl 脚本 / MCP 配置里的
${ANTHROPIC_*})。查法:在用户目录里 grep ANTHROPIC_AUTH_TOKEN|ANTHROPIC_BASE_URL。
⚠️ grep 坑:WorkBuddy 工作区里若有名为
nul的文件,ripgrep 会直接os error 1崩掉退出码 2;大目录还会 30s 超时。改用 Pythonos.walk自己过滤后缀和跳过大目录。
验证删干净了没有(关键:必须在干净环境里跑,否则当前 shell 还继承着旧值):
import os, subprocess, re
env = {k:v for k,v in os.environ.items()
if not (k.upper().startswith("ANTHROPIC") or k.upper().startswith("CLAUDE_CODE"))}
p = subprocess.run([r"C:/Users/<u>/AppData/Roaming/npm/claude.cmd",
"-p", "hi", "--output-format", "json"],
capture_output=True, text=True, env=env, timeout=240,
stdin=subprocess.DEVNULL)
print(re.search(r'"canonicalModel"\s*:\s*"([^"]*)"', p.stdout).group(1))
能正常返回 canonicalModel 就证明 settings.json 单独撑得住。
subprocess 坑:Windows 上传
"claude"会FileNotFoundError,必须给claude.cmd的完整路径。
配套要求:如果你写了自己的切档工具,它必须只写 settings.json。 否则下次切档又把环境变量写回来,两份配置死灰复燃。
| 现象 | 说明 |
|---|---|
subprocess.run(["claude", ...]) → FileNotFoundError | Windows 上必须给 claude.cmd 的完整路径 |
| ripgrep/Grep 工具全目录搜索失败 | 工作区里若有名为 nul 的文件会 os error 1(退出码 2);大目录 30s 超时。改用 Python os.walk 过滤后缀 |
| 需要跑管理员权限的命令 | 用标准的 UAC 提权方式(Start-Process -Verb RunAs 或 ShellExecuteEx+runas),执行前先向用户说明并征得同意 |
智谱官方原生支持 Anthropic 协议,文档里直接给了 Claude Code 配置,接入最省心。
https://open.bigmodel.cn/api/anthropic
/v1——Claude Code 会自动拼 /v1/messages,拼成
https://open.bigmodel.cn/api/anthropic/v1/messages(这正是智谱的正确路径)。https://api.z.ai/api/anthropic(Key 用 z.ai 平台发的)。https://open.bigmodel.cn/usercenter/apikeys 创建。
填进 ANTHROPIC_AUTH_TOKEN(即 Key 本身,CC 会带 x-api-key 头,别加 Bearer )。glm-4.6v-flash(128K,9B 轻量,免费档仅 1 并发)glm-4.7-flash / glm-4.5-flashglm-5.3-flash(1M 上下文,约 ¥1.07/¥3.55)、glm-5.2glm-5{
"env": {
"ANTHROPIC_BASE_URL": "https://open.bigmodel.cn/api/anthropic",
"ANTHROPIC_AUTH_TOKEN": "你的智谱API Key",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "glm-4.7-flash",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "glm-5.3-flash[1m]",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "glm-5.3-flash[1m]",
"CLAUDE_CODE_AUTO_COMPACT_WINDOW": "1000000",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
"API_TIMEOUT_MS": "600000"
}
}
glm-4.6v-flash 免费档只有 1 并发请求(ayautomate 实测)。Claude Code 会并行发
子代理/工具调用请求,极易触发限流排队 → 实际体验卡顿。当主模型不推荐,更适合走
「视觉理解 MCP Server」(glm-4.6v) 或 lobehub 的 glm-4.6v-flash-mcp 只处理图片/视频任务。[1m] 后缀(如 glm-4.6v-flash 就别加)。[1m] 只给真有
1M 窗口的模型用(如 glm-5.3-flash[1m]),否则 contextWindow 会虚标导致 auto-compact 错乱。glm-5.3-flash 或 glm-4.7-flash(免费文本),
比视觉 Flash 更能扛多步任务。适用:模型只在 OpenAI 兼容网关上提供,没有 Anthropic 原生端点。 典型是 Space Bunny(2026-09 匿名免费预览,1M 上下文,强制推理、五档 reasoning effort,$0)。
⚠️ 2026-09-29 重大更正:
claude-code-router的流式转换是坏的,不要再拿它做方案。 实测 v2.1.1(npm 上只有 2.1.0/2.1.1,没有修复版)输出的 Anthropic SSE: 每个事件都写两遍(两个不同的message_start)、从不发message_stop、message_delta.delta是空{}。Claude Code 因此报API Error: The response stream was malformed. The response above may be incomplete.定位手法(可复用):把它指向一个标准得不能再标准的假上游——自己起个 Python HTTP 服务 返回教科书式 OpenAI SSE(
data: {...}\n\n+finish_reason+data: [DONE])—— 照样畸形,说明是路由器自身 bug,与上游无关;再查假上游的请求日志确认上游只被请求 1 次, 即重复输出是它自己写了两遍。替代方案:自建
ccproxy.py(见第七部分),零依赖、事件序列严格合规。 下面的路由器配置留作历史参考,新搭建请直接走第七部分。
同一个 stealth 模型往往有多个上游,优先选免 key 的那个:
| 上游 | 端点 | 模型名 | 认证 |
|---|---|---|---|
| OpenCode Zen | https://opencode.ai/zen/v1/chat/completions | space-bunny-free | 免 key(匿名)可用 |
| OpenRouter | https://openrouter.ai/api/v1/chat/completions | stealth/space-bunny-alpha | 需 key(注意:整段 :free 后缀会 404) |
| AI/ML API | https://api.aimlapi.com/v1/chat/completions | stealth/space-bunny-alpha | 需 key |
Authorization 头直接 POST
space-bunny-free 即返回真实结果、cost:"0"。账号被封也不用怕,直接换 Zen。
(限时免费,约 ~2026-09-30 到期;@namzu/zen 也称 keyless 走 space-bunny-free,
但「网关准入随时可能变」。)-free 后缀,OpenRouter 不带 :free 后缀。Claude Code 只发 Anthropic Messages 协议,这类模型只吃 OpenAI Chat Completions。
协议不同,ANTHROPIC_BASE_URL 直接指过去必炸。用 claude-code-router 在本地
做协议转换(含流式 + 工具调用 + 推理参数翻译)。
架构:Claude Code → http://127.0.0.1:3456 → claude-code-router → OpenRouter
config-router.json,不是 config.json这是「配置明明写了却不生效」的头号原因,静默失败,极难查:
DEFAULT_CONFIG_PATH = path.join(homedir(), ".claude-code-router", "config-router.json")
config.json —— 错的。DEFAULT_CONFIG,
里面塞着 codewhisperer-primary(AWS)和 shuaihong-openai 两个陌生 provider。ccr health,若出现你不认识的 provider 名,就是没读到你的配置。网关内部用 axios,而 axios 会读取 http_proxy/https_proxy/HTTP_PROXY/HTTPS_PROXY
环境变量。本机(或任何开了代理的机器)若设了这些变量,网关会把所有上游请求都交给代理转发,
症状极具迷惑性:
| 现象 | 说明 |
|---|---|
打上游得 400,但用 curl 直连同一上游是 200/401 | 代理层引入的假错误 |
打本地 http://127.0.0.1:xxxx 得 502 | 代理无法回环(连 localhost 也被转发) |
| 不同上游表现不一致、模型名明明正确却失败 | 别怀疑模型名,先查代理 |
定位法(决定性):架一个本地回显 HTTP 服务器(Python stdlib),把网关 endpoint 临时
指到 http://127.0.0.1:3999/v1/chat/completions,再经网关发一次请求:
修复:按 HTTP 客户端通用的 no_proxy 约定,把本地回环地址与直连可达的上游域名列入
no_proxy,即声明这些目标不走代理(其余流量仍按系统代理设置走):
# bash:只为回环地址声明直连,其余保持系统代理设置
NO_PROXY='127.0.0.1,localhost' no_proxy='127.0.0.1,localhost' <node.exe> <cli.js> start
REM Windows 启动器里
set NO_PROXY=127.0.0.1,localhost
set no_proxy=127.0.0.1,localhost
<node.exe> <cli.js> start
若确实需要「这个进程完全不使用任何代理」(例如上游必须直连、而系统代理不通), 再显式覆盖这几个变量——先确认该上游直连可达(不带代理参数
curl能返回 200), 且只对单个进程生效,不要写成全局永久环境变量。
注意:这一条对所有「本地网关转上游」的场景都适用(不只 claude-code-router)。
Claude Code 自己连 127.0.0.1:3456 时同样可能被代理接管 → 在 no_proxy 里加上 127.0.0.1。
npm install -g)mkdir -p "C:/Users/<u>/.workbuddy/binaries/node/workspace"
cd "C:/Users/<u>/.workbuddy/binaries/node/workspace"
"<node.exe>" "<版本根目录>/node_modules/npm/bin/npm-cli.js" install claude-code-router
npm-cli.js 在版本根目录
.../node/versions/22.22.2-3/node_modules/npm/bin/npm-cli.js, 不在 workspace 里(踩过)。
~/.claude-code-router/config-router.json{
"server": { "port": 3456, "host": "127.0.0.1" },
"routing": {
"rules": {
"default": { "provider": "opencode-zen", "model": "space-bunny-free" },
"background": { "provider": "opencode-zen", "model": "space-bunny-free" },
"thinking": { "provider": "opencode-zen", "model": "space-bunny-free" },
"longcontext": { "provider": "opencode-zen", "model": "space-bunny-free" },
"search": { "provider": "opencode-zen", "model": "space-bunny-free" }
},
"defaultProvider": "opencode-zen",
"providers": {
"opencode-zen": {
"type": "openai",
"endpoint": "https://opencode.ai/zen/v1/chat/completions",
"authentication": { "type": "bearer", "credentials": { "apiKey": "" } },
"settings": {
"categoryMappings": { "default": true, "background": true, "thinking": true,
"longcontext": true, "search": true },
"models": ["space-bunny-free"],
"defaultModel": "space-bunny-free"
}
}
}
},
"debug": { "enabled": true, "logLevel": "info", "traceRequests": false,
"saveRequests": false, "logDir": "C:/Users/<u>/.claude-code-router/logs" }
}
要点:
endpoint 要写完整 /v1/chat/completions 路径(不是 base URL)。model。所以 settings.json 里写 space-bunny 也无所谓。ccr health 长期显示
System degraded + codewhisperer-primary ❌ —— 无害,别去修。
(opencode-zen ❌ 也可能出现,因为 health 探活方式与实际 messages 调用不同;
只要经 /v1/messages 能拿到真实回包,就是通的,别被 health 误导。)"apiKey": "" 即可,路由器会省略 Authorization 头 → Zen 视为匿名。
填任何非空假 key 反而会被 Zen 拒(AuthError: Invalid API key.)。max_tokens 要给足,否则推理 token 吃光预算、
返回 content: [](表现为"空回复",不是坏了)。"env": {
"ANTHROPIC_AUTH_TOKEN": "router-local",
"ANTHROPIC_BASE_URL": "http://127.0.0.1:3456",
"ANTHROPIC_MODEL": "space-bunny[1m]",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "space-bunny[1m]",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "space-bunny[1m]",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "space-bunny[1m]",
"ANTHROPIC_DEFAULT_FABLE_MODEL": "space-bunny[1m]",
"CLAUDE_CODE_SUBAGENT_MODEL": "space-bunny[1m]",
"CLAUDE_CODE_AUTO_COMPACT_WINDOW": "1000000",
"API_TIMEOUT_MS": "600000"
}
ANTHROPIC_AUTH_TOKEN 填本地哑值即可(如 router-local),真 key 在网关 config 里。
好处:真 key 只存在一处,不随 settings.json 扩散。[1m] 后缀 + AUTO_COMPACT_WINDOW=1000000。网关会把上游错误包装掉,只留一句 openrouter request failed: Request failed with status code 400,看不出是 auth 还是 body 格式问题。拿真实 body 的方法:
"traceRequests": true(配合 saveRequests),重启网关;config.data(日志里该字段会被截断,别直接肉眼看):node -e "
const fs=require('fs');
fs.readFileSync('<logDir>/ccr-2026-09-29.log','utf8').split('\n').filter(Boolean).forEach(l=>{
const o=JSON.parse(l);
if(o.level==='error'&&o.data?.config) console.log(o.data.config.data, JSON.stringify(o.data.config.headers));
});"
A/B 判据:同一个无效 key,直连 OpenRouter 返回 401 Missing Authentication header,
而经网关返回 400 —— 两者都是 auth 被拒,只是 OpenRouter 对网关来源的错误码不同。
拿到合法 OpenAI 格式 body 且模型名正确 = 链路已通,剩下只是换真 key。
(判断 body 是否合法:形状应是
{"model":"stealth/space-bunny-alpha","messages":[...],"max_tokens":N,"stream":false})
网关必须常驻,且不能用 nohup ... &(会随命令进程组被杀,见 Step 8)。
用工具自带的后台运行能力起,或给用户建启动器(本机已固化,见第六 / 七部分):
# 前台起网关(窗口可见,保持开着)
python scripts/ccproxy.py --port 3457 --verbose
# 或让切换器代劳(推荐):它判定是否真需要网关、避免重复启动
python scripts/ccswitch.py gateway
脚本随本技能提供,位于
scripts/目录下 ——ccproxy.py(网关)+ccswitch.py(切换器)。 纯 Python 标准库、零第三方依赖,拷到任意位置都能跑。 Windows 用户想双击即用,可自建.cmd启动器(内部先chcp 65001防中文乱码), 内容就是上面那两行命令。 注意 CC 2.1.278 起bin指向原生claude.exe(不再有cli.js);claude.cmd只是批处理包装。
CLAUDE_CODE_EFFORT_LEVEL 经网关未必能传下去(同 Step 9,
很可能是空操作),需要实测确认。目标:用户说一句「切 deepseek」/「切 space bunny」就完成切换,不再手工改配置。
python scripts/ccswitch.py deepseek # 切到 DeepSeek 原生端点
python scripts/ccswitch.py spacebunny # 切到 Space Bunny(自动拉起网关)
python scripts/ccswitch.py status # 当前 profile + 网关状态
python scripts/ccswitch.py list # 列出所有 profile
python scripts/ccswitch.py gateway # 只确保网关在跑
python scripts/ccswitch.py <p> --no-gateway # 只改 settings,不动网关
别名:ds→deepseek,bunny/sb/zen→spacebunny。
~/.claude/profiles/<name>.json 存一份完整的 Claude Code
settings.json;切换 = 复制覆盖 ~/.claude/settings.json。~/.claude/backups/settings.json.bak-<时间戳>;
与目标内容相同则跳过(不刷屏)。ANTHROPIC_BASE_URL 含 127.0.0.1:3457 → 需网关(切过去自动拉起、切走自动停);
其它(如 api.deepseek.com/anthropic)→ 原生端点,不需要网关。ccproxy.py(第七部分),不是 claude-code-router;
停网关:netstat -ano 找 :3457 LISTENING → taskkill /F /PID。list 里。scripts/ 下,跨平台:Windows 走 netstat + taskkill,
macOS / Linux 走 pidfile + SIGTERM。.cmd 启动器即可(内部 chcp 65001 防中文乱码),
内容就是 python <技能目录>\scripts\ccswitch.py <profile>。| 现象 | 真相 |
|---|---|
Popen 起的网关活不过本次调用 | 以后台方式启动(Bash 的 run_in_background),或让用户用 .cmd 启动器双击运行 |
/tmp/sb.json 写得出、Python 打不开 | Git Bash 的 /tmp 是虚拟路径,原生 Python 看不见。落盘要写 Windows 真实路径,或直接走管道 |
→ 结论:验证链路优先「直连端点」,别依赖拉起完整 CC;网关的常驻方式见第七部分。
为什么自建:见第五部分的更正。claude-code-router 的 SSE 转换是坏的,
且它只有 2 个版本、无新版可升,2MB 压缩产物 + 超长行无法可靠打补丁 —— 与其修,不如替换。
| 路径 | 作用 |
|---|---|
scripts/ccproxy.py | 网关本体(Python 3 stdlib,零第三方依赖,约 640 行) |
scripts/ccswitch.py | profile 切换器 + 网关启停协调(跨平台,约 430 行) |
~/.claude-code-proxy.json | 配置:host / port / upstream{url,model,api_key} / timeout / verbose |
examples/profiles/*.json | 开箱可用的 settings.json 模板,拷到 ~/.claude/profiles/ |
python ccproxy.py --port 3457 --verbose # 前台带日志
python ccproxy.py --upstream <url> --model <name> --key <k> # 临时换上游
curl -s http://127.0.0.1:3457/health # {"ok":true,"upstream":...}
CLI 参数优先于配置文件。当前上游:https://opencode.ai/zen/v1/chat/completions +
space-bunny-free + 空 api_key(Zen 免 key;填任何占位值都会被 AuthError: Invalid API key 拒)。
http.client 直连,不用 requests/axios → 不读取 HTTP_PROXY/HTTPS_PROXY
这类代理环境变量,从根上避免第五部分那类「请求被系统代理接管」的问题。message_start(恰 1 次) → content_block_start → content_block_delta* → content_block_stop
→ (下个块同理)→ message_delta(带真实 stop_reason)→ message_stop(恰 1 次)。message_delta + message_stop(必要时补一个空 text block),绝不裸断 ——
这是 stream was malformed 的根治手段。arguments 增量会交错到达,因此
不要在某个 tool 块出现时就关闭前一个块;让所有块保持 open,结束前按 index 顺序统一 close。
arguments 片段直接映射为 input_json_delta.partial_json(CC 自己拼接)。finish_reason → stop_reason:stop→end_turn、length→max_tokens、
tool_calls/function_call→tool_use。reasoning_content 直接丢弃:Anthropic 的 thinking block 需要 signature,伪造易炸。
(想要思考过程再加开关。)字符数 / 3.5 估算 input_tokens/output_tokens,
够 CC 显示成本与 auto-compact 用。system→role=system;user 里的 tool_result 块要拆成独立的
{"role":"tool","tool_call_id":...};assistant 的 tool_use → OpenAI tool_calls;
tools[].input_schema → function.parameters;tool_choice:any→required、
tool→{type:function,function:{name}};thinking.budget_tokens → reasoning_effort。curl -sN http://127.0.0.1:3457/v1/messages \
-H 'content-type: application/json' -H 'anthropic-version: 2023-06-01' \
-d '{"model":"x","max_tokens":300,"stream":true,"messages":[{"role":"user","content":"回答两个字:你好"}]}' > out.txt
逐块解析 event:/data: 后检查(实测通过的样子):
| 检查项 | 期望 |
|---|---|
message_start / message_delta / message_stop 计数 | 各 1 |
content_block_stop 计数 | = content_block_start 计数 |
| 首 / 末事件 | message_start / message_stop |
| text 拼接 | 等于完整回答(实测 '你好') |
| 带 tools 时 | 出现 content_block_start(type=tool_use) + input_json_delta,args 能 json.loads 成功(实测 {"city": "北京"}),stop_reason=tool_use |
| 真实 CC | claude.exe -p "7*8=?" --output-format json → result:"56"、stop_reason:"end_turn"、is_error:false |
claude.cmd 这层批处理包装更稳):
~/AppData/Roaming/npm/node_modules/@anthropic-ai/claude-code/bin/claude.exe
(CC 2.1.278:package.json 的 bin = bin/claude.exe,不再有 cli.js,别再按老路径找)。ls 会略慢,正常。