自建 Copilot Cli 代理:让 GitHub Copilot 真正"Bring Your Own Key"
Github: https://github.com/wosledon/copilot-auto-byok
一个基于 .NET 10 的轻量级模型代理,解决 Copilot Cli 只支持单个固定 BYOK 的问题,附带完整的指标监控与动态路由能力。
三大核心设计
1. 双协议代理,不强行转换
很多代理项目的一个误区是"协议互转"------把 OpenAI 的请求格式转成 Anthropic 的,反之亦然。这在实际使用中极其脆弱:
- 字段映射永远滞后于官方 API 的更新
- 工具调用(Function Calling)、系统提示等高级特性很难对齐
- 流式响应的 SSE 格式差异巨大
我们的做法更简单也更可靠:分别暴露两套原生端点。
| 端点 | 协议 | 用途 |
|---|---|---|
POST /v1/chat/completions |
OpenAI | Copilot 客户端或任何 OpenAI SDK |
POST /v1/messages |
Anthropic | Claude Code、Claude Desktop 等 |
GET /v1/models |
OpenAI | 列出可用模型(含 AutoCopilot) |
请求到达后,代理只做三件事:
- 校验 API Key
- 根据模型名找到对应的 Provider 和 Key
- 用
HttpCompletionOption.ResponseHeadersRead直接透传流式响应
没有格式转换,没有中间缓冲,延迟几乎为零。
csharp
// ProxyService.cs 核心片段
var requestMessage = CreateProxyRequest(context, provider, actualModel);
var response = await _httpClient.SendAsync(
requestMessage,
HttpCompletionOption.ResponseHeadersRead, // 关键:不缓冲响应体
cancellationToken);
// 直接 pipe SSE 流
await response.Content.CopyToAsync(context.Response.Body);
2. AutoCopilot 影子模型
这是项目最具特色的设计。
传统代理需要在客户端指定确切的模型名,比如 gpt-4o 或 claude-3-5-sonnet。但 Copilot 客户端通常不会暴露模型选择,或者你希望在不修改客户端配置的情况下动态切换底层模型。
AutoCopilot 是一个虚拟模型名。你可以在管理后台随时把它绑定到任意一个已配置的模型:
json
{
"autoCopilot": {
"currentModel": "claude-3-5-sonnet",
"currentProvider": "anthropic"
}
}
切换即时生效,无需重启服务。所有请求 model: "AutoCopilot" 的调用都会被透明转发到当前绑定的真实模型。
这在实际工作中非常有用:
- 早上用
gpt-5.5写代码,随时切到deepseekv4-flash写文档 - 某个模型临时故障,秒级切换到备用模型
- A/B 测试不同模型在相同提示下的表现
3. 请求级指标监控
BYOK 的一个核心诉求是"成本可控"。如果只是转发请求,你根本不知道这个月花了多少钱、哪个模型最费 token、响应慢不慢。
我们在代理层拦截了每一次请求,记录了 14 个字段的详细指标:
| 指标 | 说明 |
|---|---|
timestamp |
请求时间 |
requested_model |
客户端请求的模型名 |
actual_model |
实际转发的模型名 |
provider |
openai / anthropic |
protocol |
chat.completions / messages |
is_streaming |
是否流式 |
prompt_tokens |
输入 token 数 |
completion_tokens |
输出 token 数 |
latency_ms |
首字节延迟(TTFT) |
total_duration_ms |
总耗时 |
tokens_per_second |
生成速度 |
is_cache_hit |
是否命中缓存 |
status_code |
HTTP 状态码 |
error |
错误信息 |
基于这些数据,管理后台提供了:
- 概览卡片:总请求数、成功率、总 token、预估成本
- 趋势图表:请求量、token 消耗、延迟分布、模型占比(基于 Chart.js)
- 请求日志:可筛选、可分页、可导出 CSV
成本估算是内置的------我们在代码中维护了一张各模型的价格表,根据 prompt_tokens 和 completion_tokens 自动计算。虽然不是 100% 精确(不含缓存折扣等),但足够做用量预警。
关键实现细节
流式响应的零拷贝转发
对于 SSE 流式响应,很多初学者会犯一个错误:把响应完整读进内存字符串,再逐行 WriteAsync。这在高并发下既耗内存又增加延迟。
正确的做法是让 HTTP 管道直接对接:
csharp
// 流式路径
if (isStreaming)
{
await response.Content.CopyToAsync(originalResponse.Body);
}
CopyToAsync 内部使用缓冲区循环读写,数据从 Provider 的 TCP 连接直接流向客户端,不经过完整的字符串解析。我们在旁边再开一个任务读取同样的流来解析 usage 数据,互不阻塞。
配置热重载
代理服务理论上要 7×24 运行,重启来加载新配置是不可接受的。
ConfigService 使用了一个简单的原子替换模式:
csharp
private AppConfiguration _config = new();
private readonly ReaderWriterLockSlim _lock = new();
public void UpdateConfiguration(AppConfiguration config)
{
_lock.EnterWriteLock();
try
{
_config = config; // 引用替换,瞬间完成
PersistToSqlite(config);
}
finally
{
_lock.ExitWriteLock();
}
}
读配置用读锁,更新配置用写锁,保证并发安全的同时切换是毫秒级的。
JSON 到 SQLite 的平滑迁移
项目早期使用 JSON 文件存储配置(Data/models.json)。随着功能增加,配置和指标需要更结构化的查询能力,我们迁移到了 SQLite。
但在 Program.cs 启动时,会检查是否存在旧的 JSON 文件:
csharp
if (File.Exists(configPath))
{
MigrateJsonToSqlite(configPath, configService);
File.Move(configPath, configPath + ".backup", overwrite: true);
}
适用场景与局限性
适合用
- 个人开发者:已有 OpenAI/Anthropic 额度,想把 Copilot 客户端接过来
- 小团队:统一管理模型访问和用量,避免每个人自己存 API Key
- 模型评测:快速切换不同模型,在相同代码库上对比效果
不适合用
- 需要多轮对话状态管理的场景(本项目是无状态代理)
- 需要请求内容审查/过滤的企业环境(本项目不做内容安全扫描)
- 超高并发生产环境(SQLite 单文件在极高写入下会瓶颈)
总结
copilot-auto-byok 的定位非常明确:不做太多,把 BYOK 代理这件事做到极致。
它没有复杂的协议转换,没有沉重的依赖,没有需要编译的前端框架。有的只是:
- 双协议原生代理
- 一个可以随时切换的影子模型
- 详尽的请求级指标
如果你也在寻找一种方式,让 Copilot 客户端用你自己的 API Key,同时还想看清每一分钱花在哪里------这个项目值得一试。
GitHub : copilot-auto-byok
技术栈: .NET 10 · ASP.NET Core · EF Core · SQLite · Chart.js