🚀 CopilotX:一个晚上,把 GitHub Copilot 变成本地 API 服务器

🚀 CopilotX:一个晚上,把 GitHub Copilot 变成本地 API 服务器

凌晨一点,当终端里的并发测试输出 16/16 requests passed 时,我知道这个东西可以发布了。

从下午开始研究三个开源项目,到深夜在 PyPI 上发布 CopilotX,整个过程不到一天。这篇文章记录这个项目从灵感到上线的完整旅程。


起因:三个项目的启发

事情的起点是一个简单的好奇心——GitHub Copilot 订阅能不能当本地 API 用?

我在 GitHub 上发现了三个试图解决这个问题的项目:

项目语言核心思路
Router-MaestroPython独立 CLI 工具,OAuth 登录后起 FastAPI 代理
Agent-MaestroPython完整 Agent 框架,内置 MCP Server
GateX(我之前写的)TypeScriptVS Code 扩展,借 vscode.lm API 透传

花了一个下午仔细研究它们的代码,我得出几个关键发现:

Router-Maestro 的设计最优雅——它发现了 GitHub Copilot 的一个重要事实:Copilot 后端(api.githubcopilot.com)原生就是 OpenAI 格式。这意味着 OpenAI SDK 的请求只需要换个 Token 就能直接转发,根本不需要复杂的格式转换。而且它用 OAuth Device Flow 做认证,完全不依赖 VS Code。

Agent-Maestro 走了另一条路,想做成"一站式 Agent 平台"。功能丰富但过于庞大,光 MCP Server 就带了十几个工具,对于"我只想有个本地 API"的需求来说太重了。

GateX 是我之前写的 VS Code 扩展,通过 vscode.lm API 调用模型再转成 HTTP。优点是零配置(VS Code 已经处理了认证),缺点是必须在 VS Code 里运行,没法在纯终端环境用。

三个项目各有优缺,但没有一个完美击中我的需求:轻量、独立、双格式(OpenAI + Anthropic)、开箱即用。

那就自己写一个。


设计决策:Less is More

在动手之前,我花了一些时间做设计决策。这些选择后来证明每一个都是对的:

为什么是 Python 独立工具?

❌ VS Code 扩展 → 依赖编辑器,不够独立
❌ Node.js CLI  → AI/ML 生态大多在 Python
✅ Python CLI   → pip install 一行搞定,生态亲和

为什么同时支持 OpenAI 和 Anthropic 格式?

因为我的主要使用场景就是两个:

  • Claude Code → 说 Anthropic 协议(/v1/messages)
  • Codex / 其他工具 → 说 OpenAI 协议(/v1/chat/completions)

Copilot 后端原生是 OpenAI 格式,所以 OpenAI 请求直接透传;Anthropic 请求需要实时翻译。一个代理同时覆盖两大生态,这是 CopilotX 的核心差异化。

为什么用 OAuth Device Flow?

GitHub 的 Device Flow 认证非常优雅:

1. 终端显示一个 8 位码  →  AB12-CD34
2. 浏览器打开 github.com/login/device
3. 输入码,授权完成
4. 终端自动获取 Token

不需要在终端里粘贴密码,不需要手动创建 Personal Access Token。而且我用的 client_id 跟 VS Code Copilot Chat 扩展完全一样(Iv1.b507a08c87ecfe98),这样获得的 Token 权限也完全一致。


核心架构:两层 Token + 格式翻译

整个系统的架构可以画成这样:

你的工具 (Claude Code / Codex / Python 脚本)
    │
    │  OpenAI 或 Anthropic 格式
    ▼
┌────────────────────────────────────────┐
│  CopilotX (localhost:24680)            │
│                                        │
│  OpenAI 请求  → 直接转发(加 Token)    │
│  Anthropic 请求 → 翻译成 OpenAI → 转发  │
│                                        │
│  Token 管理:GitHub OAuth → Copilot JWT │
│  (JWT ~30分钟,到期前60秒自动刷新)    │
└──────────────────┬─────────────────────┘
                   │  OpenAI 格式 + Copilot JWT
                   ▼
    api.githubcopilot.com/chat/completions
    → GPT-4o, Claude, Gemini, o3-mini ...

两层 Token 机制

这是我从 Router-Maestro 学到的最重要的一点:GitHub Copilot 用的是两层 Token 架构。

  • 第一层:GitHub OAuth Token(长期有效)

    • 通过 Device Flow 获取
    • 存储在 ~/.copilotx/auth.json(chmod 600)
    • 只要用户不撤销授权就一直有效
  • 第二层:Copilot JWT(约 30 分钟有效)

    • 用 GitHub Token 向 api.github.com/copilot_internal/v2/token 换取
    • 实际请求 Copilot API 用的是这个 JWT
    • CopilotX 在 JWT 到期前 60 秒自动刷新,对用户完全透明
async def ensure_copilot_token(self) -> str:
    """确保 Copilot JWT 有效,到期前自动刷新"""
    if self._copilot_token and time.time() < self._expires_at - TOKEN_REFRESH_BUFFER:
        return self._copilot_token
    # 自动用 GitHub Token 换取新的 Copilot JWT
    self._copilot_token = await self._fetch_copilot_token()
    return self._copilot_token

Anthropic → OpenAI 格式翻译

Copilot 后端只说 OpenAI,但我需要让它也能接受 Anthropic 格式。翻译引擎需要处理的核心差异:

差异点OpenAIAnthropic
System 消息messages 数组里 role: "system"独立的 system 字段
内容格式content: "string"content: [{type: "text", text: "..."}]
停止原因finish_reason: "stop"stop_reason: "end_turn"
流式事件data: {choices: [{delta}]}event: content_block_delta + data: {delta}

其中流式翻译最复杂。Anthropic 的 SSE 有 6 种事件类型需要构造:

message_start → content_block_start → content_block_delta (N次)
→ content_block_stop → message_delta → message_stop

而 OpenAI 只有 data: {...} 和 data: [DONE]。我需要把 OpenAI 的简单流拆解成 Anthropic 的完整事件序列。


实现亮点

22 个模型,开箱即用

copilotx models 一下就能看到 Copilot 后端提供的所有模型:

📋 Available Models
┌──────────────────────────────────┬───────────────────────┬─────────┐
│ Model ID                         │ Name                  │ Vendor  │
├──────────────────────────────────┼───────────────────────┼─────────┤
│ gpt-4o                           │ GPT-4o                │ Azure   │
│ gpt-4o-mini                      │ GPT-4o Mini           │ Azure   │
│ o3-mini                          │ o3-mini               │ Azure   │
│ claude-sonnet-4                  │ Claude Sonnet 4       │ Azure   │
│ gemini-2.0-flash                 │ Gemini 2.0 Flash      │ Azure   │
│ ...                              │ ...                   │ ...     │
└──────────────────────────────────┴───────────────────────┴─────────┘

Total: 22 models

GPT-4o、Claude Sonnet 4、Gemini 2.0 Flash、o3-mini……这些模型全都可以通过一个统一的本地 API 调用。

智能端口管理

默认端口选了 24680(偶数递增,好记又冷门)。但真正的亮点在于端口策略的双模式设计:

# 自动模式:24680 被占就自动试 24681、24682…
copilotx serve

# 严格模式:端口冲突直接报错,尊重用户的选择
copilotx serve --port 9999

同时,启动时会写入 ~/.copilotx/server.json:

{
  "host": "127.0.0.1",
  "port": 24680,
  "pid": 12345,
  "base_url": "http://127.0.0.1:24680"
}

这样其他脚本可以程序化地发现端口,而不需要硬编码。服务器停止时自动清理这个文件。

并发测试:16/16

这是最让我满意的一个环节。我用 Python asyncio + httpx 写了一个并发测试套件,三组测试全部通过:

============================================================
  OpenAI /v1/chat/completions: 5 parallel requests
============================================================
  [OK]   #1 |    1000ms | Request 1 OK
  [OK]   #2 |     975ms | Request 2 OK
  [OK]   #3 |     958ms | Request 3 OK
  [OK]   #4 |     962ms | Request 4 OK
  [OK]   #5 |     966ms | Request 5 OK
  --- 5/5 succeeded, wall time: 1001ms ---

============================================================
  Anthropic /v1/messages: 5 parallel requests
============================================================
  [OK]   #1 |    1062ms | Request 1 OK
  [OK]   #2 |    1063ms | Request 2 OK
  [OK]   #3 |     998ms | Request 3 OK
  [OK]   #4 |    1189ms | Request 4 OK
  [OK]   #5 |    1032ms | Request 5 OK
  --- 5/5 succeeded, wall time: 1190ms ---

============================================================
  Mixed: 3 OpenAI + 3 Anthropic parallel
============================================================
  [OK]   #1 |     604ms | Hello! How can I assist you today?
  [OK]   #4 |    2135ms | Hi there! I'm Claude
  --- 6/6 succeeded, wall time: 2136ms ---

  FINAL: 16/16 requests passed ✅

注意看 wall time——5 个并发请求的总耗时约等于单个请求(~1s),证明 FastAPI async 架构真正实现了并行处理。


三行命令体验

说了这么多技术细节,实际用起来就三行:

pip install copilotx        # 安装
copilotx auth login          # 浏览器授权
copilotx serve               # 起服务

然后你就有了一个本地 API 服务器。搭配 Claude Code:

export ANTHROPIC_BASE_URL=http://localhost:24680
export ANTHROPIC_API_KEY=copilotx
claude

搭配 Codex:

export OPENAI_BASE_URL=http://localhost:24680/v1
export OPENAI_API_KEY=copilotx
codex

搭配任意 Python 脚本:

from openai import OpenAI
client = OpenAI(base_url="http://localhost:24680/v1", api_key="copilotx")

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello!"}],
    stream=True,
)
for chunk in response:
    print(chunk.choices[0].delta.content or "", end="")

api_key 可以填任意字符串——CopilotX 自己管理认证,不需要你提供 key。


技术栈一览

组件选择理由
HTTP 框架FastAPI + Uvicorn原生 async,SSE streaming 支持好
HTTP 客户端httpxasync 支持,streaming 友好
CLI 框架Typer + Rich漂亮的终端输出,类型安全
数据验证Pydantic v2FastAPI 原生支持
构建系统Hatchling现代 Python 打包标准

整个项目 21 个文件,1479 行代码。没有一行是多余的。

src/copilotx/
├── auth/          # OAuth + Token 生命周期
│   ├── oauth.py      # GitHub Device Flow
│   ├── token.py      # Copilot JWT 管理
│   └── storage.py    # 凭证持久化
├── proxy/         # 代理核心
│   ├── client.py     # Copilot API 客户端
│   ├── translator.py # Anthropic ↔ OpenAI 翻译
│   └── streaming.py  # SSE 流式响应
├── server/        # FastAPI 服务
│   ├── app.py        # 应用工厂 + lifespan
│   ├── routes_openai.py
│   ├── routes_anthropic.py
│   └── routes_models.py
├── cli.py         # Typer CLI
└── config.py      # 全局常量

收获与反思

从分析到产品,速度可以很快

这个项目从"我来看看别人怎么做的"到"PyPI 发布 + GitHub 开源",用了不到一天。关键不是写得快,而是先花足够时间研究,想清楚再动手。那个下午分析三个项目的时间,价值远超晚上的编码时间。

站在巨人的肩膀上

Router-Maestro 教会了我 OAuth Device Flow 和两层 Token 机制,Agent-Maestro 让我知道了 MCP 集成的可能性,GateX 则是我自己之前的实践积累。CopilotX 不是凭空创造的,是三个项目最佳实践的融合。

下一步计划

  • v0.2: 支持 Google Gemini API 格式(第三大 AI SDK)
  • v0.3: 增加 copilotx config 命令,支持自定义 Header 和模型别名
  • GateX v2: 把 CopilotX 的双格式翻译引擎移植回 VS Code 扩展

链接

如果你也有 GitHub Copilot 订阅,不妨试试:

pip install copilotx && copilotx auth login && copilotx serve

三行命令,解锁 22 个模型。🚀

Comments