GitHub Copilot SDK 的 BYOK 模式:使用自有 API Key 接入 OpenAI、Azure、Anthropic 与本地模型
【免费下载链接】copilot-sdkMulti-platform SDK for integrating GitHub Copilot Agent into apps and services项目地址: https://gitcode.com/GitHub_Trending/co/copilot-sdk
导读
BYOK(Bring Your Own Key,自带密钥)是 GitHub Copilot SDK 提供的一种认证模式:开发者跳过 GitHub Copilot 认证链路,直接使用自己从 OpenAI、Microsoft Foundry(Azure OpenAI)、Anthropic、Ollama 等模型提供商获取的 API Key 或 Bearer Token 来驱动会话。它特别适合企业私有化部署、自定义模型托管以及希望与模型提供商直接结算的场景。读完本文,你将掌握 BYOK 的完整配置模型(ProviderConfig)、五种语言的接入代码、wireApi传输协议选择、Bearer Token 动态获取回调,以及自定义模型列表与常见故障排查方法。
什么是 BYOK?
在默认的 GitHub Copilot 认证流程中,SDK 会使用 GitHub 账号凭证(如 Copilot CLI 存储的凭据、环境变量中的 GitHub Token 或显式传入的 SDK Token)访问 Copilot API。而 BYOK 模式完全绕开这条链路:应用在创建会话时显式传入一个provider配置,SDK 运行时直接向你所指定的模型提供商端点发起请求。
这意味着:
- 计费归属:请求费用直接计入你的模型提供商账户,而非 GitHub Copilot 配额;
- 模型自主权:可用模型完全由你的提供商决定,不受 GitHub Copilot 模型列表限制;
- 部署灵活性:既可以对接云端 API,也可以对接本机运行的 Ollama、Microsoft Foundry Local 等本地推理服务。
从源码结构看,BYOK 的核心载体是各语言 SDK 中的ProviderConfig类型(如 Go 的 ProviderConfig、Node.js 的 ProviderConfig),它承载了端点地址、凭据、传输协议等一系列字段,是连接 SDK 与外部模型服务的"接线端子"。
支持的提供商一览
| Provider | Type Value | 说明 |
|---|---|---|
| OpenAI | "openai" | OpenAI 官方 API 及任何 OpenAI 兼容端点 |
| Microsoft Foundry / Azure OpenAI | "openai"或"azure" | 走/openai/v1/兼容路径用"openai";走 Azure 原生端点用"azure" |
| Anthropic | "anthropic" | Claude 系列模型 |
| Ollama | "openai" | 通过 OpenAI 兼容 API 访问本地模型 |
| Microsoft Foundry Local | "openai" | 在本机设备上通过 OpenAI 兼容 API 运行 AI 模型 |
| 其他 OpenAI 兼容服务 | "openai" | vLLM、LiteLLM 等 |
可以看出"openai"是覆盖面最广的类型:只要端点对外暴露的是 OpenAI 兼容协议,都可以用它接入。
ProviderConfig 配置参考
创建 BYOK 会话时,provider参数接受以下字段:
| 字段 | 类型 | 说明 |
|---|---|---|
type | "openai"|"azure"|"anthropic" | 提供商类型(默认"openai") |
baseUrl/base_url | string | 必填。API 端点 URL |
apiKey/api_key | string | API Key(本地提供商如 Ollama 可省略) |
bearerToken/bearer_token | string | Bearer Token 认证(优先级高于apiKey) |
bearerTokenProvider/bearer_token_provider | callback | 按需返回 Bearer Token 的回调(优先级高于apiKey和bearerToken) |
wireApi/wire_api | "completions"|"responses" | 选择 Chat Completions API 以获得广泛模型兼容性;或选择 Responses API 以获得多轮状态管理、工具命名空间与推理支持。Anthropic 模型不受此字段影响,始终走 Messages API |
azure.apiVersion/azure.api_version | string | Azure API 版本。设置后运行时使用带版本的部署路由;省略时使用 GA 的v1无版本路由 |
在 SDK 源码中,这一配置模型还有若干未写入文档的扩展字段,理解它们有助于深度调优:
transport("http"/"websockets"):仅对 OpenAI 兼容提供商且wireApi: "responses"生效。设为"websockets"后,Responses API 请求通过持久 WebSocket 连接传输,适合长时间运行、工具调用密集且依赖previous_response_id增量续接的会话(见 Node.js 类型定义 与 Go 类型定义),默认值为"http";headers:附加到所有出站提供商请求的自定义 HTTP 头;modelId/wire_model:modelId是运行时用于查找 Agent 配置(工具、提示词、推理行为)与默认 token 上限的"已知模型名";wireModel是实际发送给提供商进行推理的模型名,适合提供商模型名(如 Azure 部署名、自定义微调名)与标准模型名不一致的场景,缺省时依次回退到modelId、SessionConfig.model;maxPromptTokens/maxOutputTokens:覆盖模型默认的提示词/输出 token 上限,前者触发会话压缩的阈值,后者决定生成被截断的边界;transport之外,SDK 各语言会对字段名做 snake_case / camelCase 的自动映射,例如 Python 的base_url、wire_api会转换成线上的baseUrl、wireApi(见 Python 客户端实现)。
wireApi:选择 Chat Completions 还是 Responses?
wireApi决定 SDK 以何种 OpenAI 协议格式与提供商通信:
"completions"(默认):使用 Chat Completions API(/chat/completions),兼容面最广,几乎任何 OpenAI 兼容服务都支持;"responses":使用 Responses API,提供多轮状态管理、工具命名空间和推理支持,适合 GPT-5 系列等较新模型。
Anthropic 模型则不受此设置影响:只要type: "anthropic",SDK 始终使用 Anthropic Messages API。
快速开始:接入 Microsoft Foundry
Microsoft Foundry 是企业 BYOK 部署的常见目标。下面给出完整示例(以gpt-5.2-codex为部署名,FOUNDRY_API_KEY从环境变量读取):
Python
import asyncio import os from copilot import CopilotClient from copilot.session import PermissionHandler FOUNDRY_MODEL_URL = "https://<resource-name>.openai.azure.com/openai/v1/" # 设置 FOUNDRY_API_KEY 环境变量 async def main(): client = CopilotClient() await client.start() session = await client.create_session(on_permission_request=PermissionHandler.approve_all, model="gpt-5.2-codex", provider={ "type": "openai", "base_url": FOUNDRY_MODEL_URL, "wire_api": "responses", # 旧模型使用 "completions" "api_key": os.environ["FOUNDRY_API_KEY"], }) done = asyncio.Event() def on_event(event): if event.type.value == "assistant.message": print(event.data.content) elif event.type.value == "session.idle": done.set() session.on(on_event) await session.send("What is 2+2?") await done.wait() await session.disconnect() await client.stop() asyncio.run(main())Node.js / TypeScript
import { CopilotClient } from "@github/copilot-sdk"; const FOUNDRY_MODEL_URL = "https://<resource-name>.openai.azure.com/openai/v1/"; const client = new CopilotClient(); const session = await client.createSession({ model: "gpt-5.2-codex", // 你的部署名 provider: { type: "openai", baseUrl: FOUNDRY_MODEL_URL, wireApi: "responses", // 旧模型使用 "completions" apiKey: process.env.FOUNDRY_API_KEY, }, }); session.on("assistant.message", (event) => { console.log(event.data.content); }); await session.sendAndWait({ prompt: "What is 2+2?" }); await client.stop();Go
package main import ( "context" "fmt" "os" copilot "github.com/github/copilot-sdk/go" ) func main() { ctx := context.Background() client := copilot.NewClient(nil) if err := client.Start(ctx); err != nil { panic(err) } defer client.Stop() session, err := client.CreateSession(ctx, &copilot.SessionConfig{ Model: "gpt-5.2-codex", // 你的部署名 Provider: &copilot.ProviderConfig{ Type: "openai", BaseURL: "https://<resource-name>.openai.azure.com/openai/v1/", WireAPI: "responses", // 旧模型使用 "completions" APIKey: os.Getenv("FOUNDRY_API_KEY"), }, }) if err != nil { panic(err) } response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "What is 2+2?", }) if err != nil { panic(err) } if d, ok := response.Data.(*copilot.AssistantMessageData); ok { fmt.Println(d.Content) } }.NET
using GitHub.Copilot; await using var client = new CopilotClient(); await using var session = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5.2-codex", // 你的部署名 Provider = new ProviderConfig { Type = "openai", BaseUrl = "https://<resource-name>.openai.azure.com/openai/v1/", WireApi = "responses", // 旧模型使用 "completions" ApiKey = Environment.GetEnvironmentVariable("FOUNDRY_API_KEY"), }, }); var response = await session.SendAndWaitAsync(new MessageOptions { Prompt = "What is 2+2?", }); Console.WriteLine(response?.Data.Content);Java
import com.github.copilot.CopilotClient; import com.github.copilot.rpc.*; var client = new CopilotClient(); client.start().get(); var session = client.createSession(new SessionConfig() .setModel("gpt-5.2-codex") // 你的部署名 .setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setProvider(new ProviderConfig() .setType("openai") .setBaseUrl("https://<resource-name>.openai.azure.com/openai/v1/") .setWireApi("responses") // 旧模型使用 "completions" .setApiKey(System.getenv("FOUNDRY_API_KEY"))) ).get(); var response = session.sendAndWait(new MessageOptions() .setPrompt("What is 2+2?")).get(); System.out.println(response.getData().content()); client.stop().get();五种语言的调用骨架高度一致:启动客户端 → 以provider配置创建会话 → 发送消息并消费事件/响应 → 停止客户端。唯一差异在于事件模型的表达方式:Python 与 TypeScript 使用事件回调(assistant.message、session.idle),Go、.NET、Java 使用同步的SendAndWait风格调用。
分类型配置示例
OpenAI 直连
provider: { type: "openai", baseUrl: "https://api.openai.com/v1", apiKey: process.env.OPENAI_API_KEY, }注意baseUrl需要包含完整路径(含/v1)。
Azure OpenAI(Azure 原生端点)
对*.openai.azure.com端点使用type: "azure":
provider: { type: "azure", baseUrl: "https://my-resource.openai.azure.com", // 仅主机名 apiKey: process.env.AZURE_OPENAI_KEY, azure: { apiVersion: "2024-10-21", }, }关键区别:baseUrl只写主机名,不要包含/openai/v1路径——SDK 会根据apiVersion自动构造请求路由(设置azure.apiVersion时走带版本的部署路由,省略时走 GA 的v1无版本路由)。
Microsoft Foundry(OpenAI 兼容端点)
如果 Foundry 部署暴露的是/openai/v1/兼容路径,则改用type: "openai":
provider: { type: "openai", baseUrl: "https://<resource-name>.openai.azure.com/openai/v1/", apiKey: process.env.FOUNDRY_API_KEY, wireApi: "responses", // GPT-5 系列模型 }Ollama(本地)
provider: { type: "openai", baseUrl: "http://localhost:11434/v1", // 本地 Ollama 无需 apiKey }Microsoft Foundry Local
Foundry Local 让你在自己设备上本地运行 AI 模型,同样暴露 OpenAI 兼容 API。通过 Foundry Local CLI 安装后,把 SDK 指向本地端点:
provider: { type: "openai", baseUrl: "http://localhost:<PORT>/v1", // 本地 Foundry Local 无需 apiKey }[!NOTE] Foundry Local 启动在动态端口上——端口号不固定。使用
foundry service status确认服务当前监听的端口,再将其填入baseUrl。
快速上手 Foundry Local:
# Windows:安装 Foundry Local CLI(需要 winget) winget install Microsoft.FoundryLocal # macOS / Linux:参见 foundrylocal.ai 的安装说明 # 列出可用模型 foundry model list # 运行模型(会自动启动本地服务) foundry model run phi-4-mini # 查看服务运行端口 foundry service statusAnthropic
provider: { type: "anthropic", baseUrl: "https://api.anthropic.com", apiKey: process.env.ANTHROPIC_API_KEY, }Anthropic 模型始终使用 Claude 专属的 Messages API 格式,不受wireApi影响。
认证方式:静态 API Key 与 Bearer Token
部分提供商要求 Bearer Token 认证而非 API Key。SDK 提供两种方式:
静态 Bearer Token(bearerToken)
适用于应用已经持有 token 的场景:
provider: { type: "openai", baseUrl: "https://<resource-name>.openai.azure.com/openai/v1/", bearerToken: process.env.MY_BEARER_TOKEN, // 设置 Authorization 头 }[!NOTE]
bearerToken只接受静态 token 字符串。SDK 不会自动刷新该 token。如果 token 过期,请求会失败,你需要用新 token 重新创建会话。
动态 Bearer Token 回调(bearerTokenProvider)
对于需要按需获取、自动续期的场景(典型如 Microsoft Entra ID / Azure Managed Identity),使用回调:
provider: { type: "openai", baseUrl: "https://my-custom-endpoint.example.com/v1", bearerTokenProvider: async () => { return await acquireBearerToken(); }, }从源码看,这是一个"回调留在客户端、运行时按需回拨"的设计:SDK 不会把回调本身序列化进 RPC 配置,而是发送hasBearerTokenProvider: true标志;运行时在每次出站模型请求前,通过会话级的providerToken.getTokenRPC 回调到客户端获取 token,并将其作为Authorization: Bearer头应用(见 Go 实现 与 Node.js 类型定义)。回调还接收providerName与sessionId上下文参数,便于按提供商或按会话区分 token 的作用域与缓存。SDK 自身不做 token 缓存,缓存与刷新逻辑由回调或其所包裹的身份库(如DefaultAzureCredential)负责。
关于使用 Microsoft Entra Bearer Token 获取与刷新的详细流程,参见 Azure Managed Identity with BYOK。
自定义模型列表(onListModels)
使用 BYOK 时,CLI 服务端并不知道你的提供商支持哪些模型。此时可以在客户端级别提供onListModels处理器,让client.listModels()返回你的提供商模型(标准ModelInfo格式),下游消费者无需查询 CLI 即可发现可用模型。
Node.js / TypeScript
import { CopilotClient } from "@github/copilot-sdk"; import type { ModelInfo } from "@github/copilot-sdk"; const client = new CopilotClient({ onListModels: () => [ { id: "my-custom-model", name: "My Custom Model", capabilities: { supports: { vision: false, reasoningEffort: false }, limits: { max_context_window_tokens: 128000 }, }, }, ], });Python
from copilot import CopilotClient from copilot.client import ModelInfo, ModelCapabilities, ModelSupports, ModelLimits client = CopilotClient( on_list_models=lambda: [ ModelInfo( id="my-custom-model", name="My Custom Model", capabilities=ModelCapabilities( supports=ModelSupports(vision=False, reasoning_effort=False), limits=ModelLimits(max_context_window_tokens=128000), ), ) ], )Go
package main import ( "context" copilot "github.com/github/copilot-sdk/go" ) func main() { client := copilot.NewClient(&copilot.ClientOptions{ OnListModels: func(ctx context.Context) ([]copilot.ModelInfo, error) { return []copilot.ModelInfo{ { ID: "my-custom-model", Name: "My Custom Model", Capabilities: copilot.ModelCapabilities{ Supports: copilot.ModelSupports{Vision: false, ReasoningEffort: false}, Limits: copilot.ModelLimits{MaxContextWindowTokens: copilot.Int(128000)}, }, }, }, nil }, }) _ = client }.NET
using GitHub.Copilot; var client = new CopilotClient(new CopilotClientOptions { OnListModels = (ct) => Task.FromResult<IList<ModelInfo>>(new List<ModelInfo> { new() { Id = "my-custom-model", Name = "My Custom Model", Capabilities = new ModelCapabilities { Supports = new ModelSupports { Vision = false, ReasoningEffort = false }, Limits = new ModelLimits { MaxContextWindowTokens = 128000 } } } }) });Java
import com.github.copilot.CopilotClient; import com.github.copilot.rpc.*; import java.util.List; import java.util.concurrent.CompletableFuture; var client = new CopilotClient(new CopilotClientOptions() .setOnListModels(() -> CompletableFuture.completedFuture(List.of( new ModelInfo() .setId("my-custom-model") .setName("My Custom Model") .setCapabilities(new ModelCapabilities() .setSupports(new ModelSupports().setVision(false).setReasoningEffort(false)) .setLimits(new ModelLimits().setMaxContextWindowTokens(128000))) ))) );需要注意两点行为语义:
- 结果缓存:首次调用后结果即被缓存,与默认行为一致;
- 完全接管:该处理器会完整替换 CLI 的
models.listRPC——不会回退到服务端。从 Node.js 客户端实现 可以看到,listModels()检测到自定义处理器存在时,直接调用它并返回结果。
限制与注意事项
功能层面的差异
- 模型可用性:只有你的提供商支持的模型可用;
- 限流策略:受你的提供商限流约束,而非 GitHub Copilot 的限流;
- 用量统计:用量由你的提供商统计,而非 GitHub Copilot;
- Premium 请求:BYOK 请求不计入 Copilot premium 请求配额。
各提供商特定限制
| Provider | 限制 |
|---|---|
| Microsoft Foundry Local | 仅限本地;模型可用性取决于设备硬件;无需 API Key |
| Ollama | 无 API Key;仅限本地;模型支持程度不一 |
| OpenAI | 受 OpenAI 限流与配额约束 |
故障排查
"Model not specified" 错误
使用 BYOK 时,model参数是必填的:
// ❌ 错误:使用自定义 provider 时必须指定模型 const session = await client.createSession({ provider: { type: "openai", baseUrl: "..." }, }); // ✅ 正确:指定模型 const session = await client.createSession({ model: "gpt-4", // 必填! provider: { type: "openai", baseUrl: "..." }, });Azure 端点类型混淆
对 Azure OpenAI 端点(*.openai.azure.com)要使用正确的类型:
// ❌ 错误:Azure 原生端点使用 "openai" 类型 provider: { type: "openai", // 无法正常工作 baseUrl: "https://my-resource.openai.azure.com", } // ✅ 正确:使用 "azure" 类型 provider: { type: "azure", baseUrl: "https://my-resource.openai.azure.com", }但如果你的 Microsoft Foundry 部署提供 OpenAI 兼容端点路径(例如/openai/v1/),则应使用type: "openai":
// ✅ 正确:OpenAI 兼容的 Microsoft Foundry 端点 provider: { type: "openai", baseUrl: "https://your-resource.openai.azure.com/openai/v1/", }连接被拒绝(Ollama)
确认 Ollama 正在运行且可访问:
# 检查 Ollama 是否运行 curl http://localhost:11434/v1/models # 未运行时启动 ollama serve连接被拒绝(Foundry Local)
Foundry Local 使用动态端口,重启后可能变化。确认当前端口:
# 查看服务状态与端口 foundry service status根据输出更新baseUrl中的端口。如果服务未运行,启动一个模型即可拉起服务:
foundry model run phi-4-mini认证失败
- 确认 API Key 正确且未过期;
- 检查
baseUrl是否符合你的提供商预期格式(尤其注意 Azure 原生端点不要带/openai/v1); - 对于 Bearer Token,确保提供完整的 token(而非仅前缀)。
进一步探索
BYOK 只是 GitHub Copilot SDK 的认证方式之一,SDK 还支持显式 Token、环境变量 GitHub Token、Copilot CLI 凭据等认证路径,完整对比见 认证总览 与 Authenticate Copilot SDK;若你的场景是在 GitHub Actions 或 GitHub App 中以组织身份运行自动化,可参考 服务端到服务端认证。入门构建第一个 Copilot 应用,参见 Getting Started 指南。
【免费下载链接】copilot-sdkMulti-platform SDK for integrating GitHub Copilot Agent into apps and services项目地址: https://gitcode.com/GitHub_Trending/co/copilot-sdk
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考