下午打算让小龙虾帮忙干活,发现它不说话了。
检查了一下,CopilotX 的 Azure VM 没响应——我这个月微软员工 150 美刀的 Azure 订阅额度用超了,虚机直接停了。
好消息是:OpenClaw 自己就能登录 GitHub Copilot。坏消息是:登录成功不等于能用。
背景:CopilotX 与 OpenClaw 的关系
如果你是第一次来,先简单介绍一下这几个角色。不熟悉的读者可以看看前两篇:让小龙虾跑起来和 CopilotX v2.3.2。
- OpenClaw(小龙虾):开源个人 AI 助手,能同时接入 Telegram、Discord、WhatsApp 等消息渠道
- CopilotX:我写的 API 代理,部署在 Azure VM(
api.polly.wang),把 GitHub Copilot 订阅转成标准 Anthropic Messages API - 关系:OpenClaw 调用 CopilotX 获取 Claude 的能力
这套架构跑了半个多月,很爽——直到今天 Azure VM 停了。
CopilotX vs github-copilot:两种吃 Copilot 的方式
让 OpenClaw 用上 GitHub Copilot 的模型,其实有两条路:
| CopilotX(自建代理) | github-copilot(原生认证) | |
|---|---|---|
| 原理 | Azure VM 上跑 FastAPI,转发请求到 Copilot 内部 API | OpenClaw 内置 Device Flow OAuth,直接调 GitHub API |
| 部署 | 需要一台服务器(api.polly.wang) | 不需要,纯本地 |
| 认证 | CopilotX 自己登录 GitHub,客户端用 API Key | 在浏览器里授权一次,token 自动管理 |
| API 格式 | Anthropic Messages(/v1/messages) | OpenAI Completions(Copilot 原生格式) |
| 依赖 | Azure VM 必须在线 | 只要 GitHub 不挂就行 |
| 适合场景 | 多客户端共享、自定义路由 | 单机使用、快速切换 |
之前选 CopilotX 是因为 OpenClaw 初始版本不支持 Copilot,我只能通过代理桥接。但现在 OpenClaw 已经内置了原生的 github-copilot provider——不需要中间人了。
实操:三步完成切换
Step 1:更新 OpenClaw
我的本地版本是 v2026.2.14,但配置格式要求 v2026.2.17,直接报版本不匹配:
Error: Configuration version mismatch. Config: 2026.2.17, Installed: 2026.2.14
更新一下:
cd X-Workspace/openclaw
git stash && git pull && pnpm install && pnpm build
跳到了 v2026.2.26,问题解决。
Step 2:登录 GitHub Copilot
这一步我踩了个坑。直觉告诉我应该用:
# ❌ 这是错的
openclaw models auth login --provider github-copilot
报错 Unknown provider: github-copilot。试了半天 copilot-proxy 插件也是条死路(那是本地 VS Code 代理,需要 VS Code 扩展运行在 localhost:3000,完全不是我要的东西)。
正确的命令是一个独立子命令,不走 --provider 参数:
# ✅ 这才对
pnpm openclaw models auth login-github-copilot
跑起来后,熟悉的 GitHub Device Flow:
🔑 GitHub Copilot Authentication
Please visit: https://github.com/login/device
Enter code: 223C-2FDC
Waiting for authorization...
浏览器里输入设备码,点授权,终端里自动完成:
✅ GitHub Copilot authentication successful!
Auth profile saved: github-copilot:github (github-copilot/token)
Step 3:切换默认模型
登录成功后,OpenClaw 的配置中已经有了 github-copilot 这个 provider。但默认模型还指向不可用的 copilotx/claude-opus-4-6。
编辑 ~/.openclaw/openclaw.json,把默认模型改成:
{
"agents": {
"defaults": {
"model": {
"primary": "github-copilot/claude-opus-4-6"
}
}
}
}
看起来万事俱备。重启 gateway,准备跟小龙虾聊天——
技术原理:为什么一条命令就够了
好奇心驱使我翻了一下 OpenClaw 的源码,看看 login-github-copilot 背后到底做了什么。
Device Flow OAuth
这是 GitHub 为无浏览器设备设计的 OAuth 流程,整个过程不需要回调 URL:
OpenClaw GitHub 浏览器
│ │ │
│─── POST /login/device/code ──►│ │
│◄── device_code + user_code ───│ │
│ │ │
│ 显示 "请访问 github.com/login/device 输入 223C-2FDC" │
│ │ 用户输入设备码 ──►│
│─── POST /login/oauth/access_token(轮询)──►│ │
│◄── authorization_pending ─────│ │
│─── POST(再次轮询)──────────►│ │
│◄── access_token ──────────────│ │
OpenClaw 用的 Client ID 是 Iv1.b507a08c87ecfe98,scope 是 read:user——这和 VS Code 里 GitHub Copilot 扩展用的 ID 非常接近。
Token 交换:关键一步
拿到 GitHub access_token 后,还不能直接调 Copilot API。需要一步token 交换:
POST https://api.github.com/copilot_internal/v2/token
Authorization: token ghp_xxxxx
返回的是一个短期 Copilot token(通常几十分钟有效期),包含在一串分号分隔的字符串里。OpenClaw 会:
- 缓存到
~/.openclaw/credentials/github-copilot.token.json - 从 token 中提取
proxy-ep字段,自动发现 API 端点 - 过期前 5 分钟自动刷新
默认的 API 端点是 https://api.individual.githubcopilot.com,但 token 里的 proxy-ep 可能指向不同的区域代理。
这就是 CopilotX 做的事情的简化版——CopilotX 本质上也是做同样的 token 交换,只不过它部署在服务器上,给多个客户端共享。而 OpenClaw 的原生实现把这一切搬到了本地。
剧情反转:400 "model not supported"
以为万事大吉,启动 OpenClaw gateway——
Error: 400 - The requested model is not supported
Claude 模型全部 400。但 GPT-4o 能用。
排查过程
这是一场长达半天的系统性排查。我怀疑过所有的环节,逐一排除:
1. 端点问题?
Copilot token 里的 proxy-ep 指向 proxy.enterprise.githubcopilot.com,会被 OpenClaw 转成 api.enterprise.githubcopilot.com。企业端点可能没有 Claude。
于是我在 openclaw.json 里强制覆盖 baseUrl:
{
"models": {
"providers": {
"github-copilot": {
"baseUrl": "https://api.githubcopilot.com"
}
}
}
}
2. curl 测试?
用同一个 token,直接 curl api.githubcopilot.com/v1/messages:
curl -X POST https://api.githubcopilot.com/v1/messages \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"model":"claude-sonnet-4.5","max_tokens":100,"stream":true,
"messages":[{"role":"user","content":"Hi"}]}'
200。 完美返回。
加上 tools、system prompt、thinking、cache_control、anthropic-beta header——全部 200。
3. pi-ai SDK 内部测试?
写了个脚本直接测 pi-ai 的 ModelRegistry,确认 baseUrl 和 api 字段都正确解析了。没问题。
4. 最终定位
写了个脚本直接调 pi-ai 的 streamAnthropic() 函数——400,model not supported。
同样的 URL、同样的 token、同样的 payload,curl 返回 200,pi-ai 的 Anthropic SDK 返回 400。
未解之谜
问题出在 @anthropic-ai/sdk 这个 JavaScript SDK 内部。它在构造 HTTP 请求时,做了一些 curl 没做的事情——可能是额外的 header、不同的序列化方式、或者某种请求签名。而 Copilot 的后端对这些差异很敏感。
我花了半天时间,但无法继续深入 SDK 的内部实现(编译后的代码不好调试)。最终决定:放弃原生方案,回归 CopilotX。
回归 CopilotX:本地启动
既然 Azure VM 挂了,那就在本地跑 CopilotX:
cd X-Workspace/CopilotX
pip install -e .
copilotx auth login # 重新登录
copilotx serve # 启动本地代理
CopilotX 的输出让人安心:
🚀 CopilotX v2.3.2
✅ Copilot Token valid (29m remaining, auto-refresh)
🔗 Anthropic API: http://127.0.0.1:24680/v1/messages
📋 Models: claude-opus-4.6, claude-sonnet-4.6, claude-sonnet-4, ...
然后把 openclaw.json 里的 baseUrl 改回 CopilotX:
{
"models": {
"providers": {
"copilotx": {
"baseUrl": "http://localhost:24680",
"api": "anthropic-messages"
}
}
}
}
重启 gateway——小龙虾立刻活蹦乱跳。
为了以后方便切换本地/远程,我还写了个小脚本:
python scripts/switch_copilotx.py # 一键 toggle
python scripts/switch_copilotx.py local # 切本地
python scripts/switch_copilotx.py remote # 切远程 (api.polly.wang)
python scripts/switch_copilotx.py status # 看当前
插曲:切了配置但小龙虾还是连不上
Azure VM 恢复后,我用脚本切到远程——结果小龙虾还是报 Connection error。
查了半天才发现:OpenClaw 在 agent 目录下有一个模型缓存文件 ~/.openclaw/agents/main/agent/models.json,里面硬编码了 baseUrl。我的脚本只改了 openclaw.json,但 gateway 运行时读的是缓存——还在连 localhost:24680。
# 罪魁祸首:缓存了旧的 baseUrl
cat ~/.openclaw/agents/main/agent/models.json | grep baseUrl
# → "baseUrl": "http://localhost:24680" ← 早就该是远程了!
修复很简单:切换时顺手删掉缓存。我更新了 switch 脚本,每次切换自动清理这个文件。
教训:配置系统有缓存层时,改了源头不等于改了运行时。
为什么 CopilotX 能行而原生不行?
这是个有趣的对比:
| CopilotX | OpenClaw 原生 github-copilot | |
|---|---|---|
| 请求格式 | OpenAI Chat Completions(/chat/completions) | Anthropic Messages(/v1/messages)via pi-ai SDK |
| HTTP 库 | Python httpx(直接构造请求) | JS @anthropic-ai/sdk(SDK 抽象层) |
| 结果 | ✅ 200 | ❌ 400 |
CopilotX 用的是 OpenAI 格式 + Python httpx,请求完全可控;而 OpenClaw 的原生 provider 走 pi-ai 的 Anthropic SDK,SDK 内部的请求构造对 Copilot 后端来说有些"不对劲"。
本质区别:CopilotX 说的是 Copilot 听得懂的"方言"(OpenAI Chat Completions),而 Anthropic SDK 说的是 Copilot 不完全兼容的"方言"(Anthropic Messages + SDK 内部的额外 header 和格式)。
经验总结
自己写的代码最可控。CopilotX 不过几百行 Python,但因为每一行我都清楚在做什么,debug 和修改都是分钟级。而踩进第三方 SDK 的黑盒里,半天也出不来。
curl 通过 ≠ SDK 通过。这是我这次最大的教训。curl 成功只能证明 API 端点和认证没问题,但 SDK 封装可能引入你看不到的差异。
先给自己留后路。CopilotX 的本地模式是个意外惊喜——不需要 Azure VM,本地
copilotx serve就能跑。这让"Plan B"的切换成本几乎为零。调试到极限也要知道何时收手。半天的排查让我精确定位到了问题(Anthropic SDK 内部的请求构造),但继续深入的边际回报很低。有时候绕过问题比解决问题更务实。
改了配置不等于改了运行时。OpenClaw 有 agent-level 的
models.json缓存,改了openclaw.json不会自动同步。任何有缓存层的系统,都要想清楚"我的改动到底在哪一层生效"。
版本线
| 日期 | 事件 | 博文 |
|---|---|---|
| 2026-02-11 | OpenClaw + CopilotX 首次联通 | 让小龙虾跑起来 |
| 2026-02-18 | CopilotX v2.3.2 错误透传修复 | 当 400 变成了「你猜怎么了」 |
| 2026-02-26 | Azure VM 停服 → 尝试原生认证 → 发现 Anthropic SDK 兼容问题 → 回归本地 CopilotX | 本文 |
