OpenCode 发送给 GitHub Copilot 的请求分析(2026-07-22)
只读分析,未修改任何代码。本次已将 fork 的 dev 与上游 anomalyco/opencode 同步(快进 10 个提交,最新 4438f69 zen: add security check )。
本次变更提示 :同步进来的 10 个提交(zen 模型新增、chore: generate、fix(app) 斜杠命令补全、fix(ui) 进度环、feat(app) 可拖拽项目行、Mistral tool-call-id 归一化等)均未触及 GitHub Copilot 的请求流程 / Header / Body ,因此下述结论相对上一版无实质变化。
分析对象为 opencode 运行时(packages/opencode)中的 GitHub Copilot 集成。核心实现集中在 packages/opencode/src/plugin/github-copilot/copilot.ts (鉴权 + 自定义 fetch)与 models.ts (模型发现 + 端点路由),请求组装在 session/llm/request.ts 与 session/llm.ts ,Body 变换在 provider/transform.ts 。
一、GitHub 身份验证与 Token 交换流程
1.1 结论先行
opencode 采用 GitHub OAuth Device Flow(设备码流程) 获取一个 GitHub OAuth access_token,随后直接 把该 token 作为 Authorization: Bearer <token> 发给 Copilot API。
关键点:opencode 不做 “GitHub token → 短时效 Copilot token” 的二次交换 (即不请求 api.github.com/copilot_internal/v2/token)。对比同仓库的 codex.ts、xai.ts、snowflake-cortex.ts 都实现了 refreshAccessToken(...),而 github-copilot 插件没有任何刷新/交换逻辑 ——设备码换来的 GitHub OAuth token 被当作长期凭据直接使用(存储时 expires: 0,copilot.ts:298)。
1.2 设备码换取流程(copilot.ts:222-336)
客户端标识 CLIENT_ID = "Ov23li8tweQw6odWQebz"(copilot.ts:9),API 版本 API_VERSION = "2026-06-01"(copilot.ts:10)。
支持两种部署(copilot.ts:186-221):GitHub.com(domain = github.com)与 GitHub Enterprise(用户填入企业域名,normalizeDomain 归一化,copilot.ts:15-17,227-230)。
端点由域名拼接(copilot.ts:19-24):
设备码:https://<domain>/login/device/code
换 token:https://<domain>/login/oauth/access_token
步骤:
请求设备码 (copilot.ts:234-256):POST /login/device/code,body { client_id, scope: "read:user" },Header Accept: application/json、Content-Type: application/json、User-Agent: opencode/<version>。返回 verification_uri、user_code、device_code、interval。
提示用户 在浏览器打开 verification_uri 并输入 user_code(copilot.ts:258-261)。
轮询换取 token (copilot.ts:262-334):循环 POST /login/oauth/access_token,body { client_id, device_code, grant_type: "urn:ietf:params:oauth:grant-type:device_code" }。
拿到 access_token 即成功,存储为 { type:"success", refresh: access_token, access: access_token, expires: 0, enterpriseUrl? }(copilot.ts:286-306)——注意 refresh 与 access 是同一个 GitHub OAuth token。
处理 authorization_pending(按 interval 等待重试)、slow_down(按 RFC 8628 增加 5s,copilot.ts:313-327),并统一加了 OAUTH_POLLING_SAFETY_MARGIN_MS = 3000 的时钟漂移安全余量(copilot.ts:14)。
1.3 凭据注入(自定义 fetch,copilot.ts:96-181)
auth.loader 返回一个覆盖版 fetch;仅当 auth.type === "oauth" 时生效(否则透传原生 fetch,copilot.ts:103-104)。它对每个 发往 Copilot 的请求:
解析 body,判定 isVision / isAgent(兼容 3 种 API 形态:Completions / Responses / Messages,copilot.ts:107-158)。
组装 Header(copilot.ts:160-166),设置 Authorization: Bearer <access>(copilot.ts:164,access 即 OAuth token,见 auth-override.test.ts:60 断言 loader 返回 { access: 'test-token' })。
删除可能冲突的 x-api-key、authorization(小写)(copilot.ts:172-173)。
1.4 模型发现也走同一凭据(copilot.ts:62-92 → models.ts:213-256)
登录后 provider.models(...) 会 GET <base>/models(models.ts:218),Header 带 Authorization: Bearer <access>、User-Agent: opencode/<version>、X-GitHub-Api-Version: 2026-06-01(copilot.ts:73-77)。用返回的 capabilities 动态构建模型、价格、reasoning variants,并据 model_picker_enabled 过滤选择器可见模型(models.ts:254)。请求失败或未登录时回退到静态模型表(copilot.ts:86-91)。
Base URL (copilot.ts:26-28):公有云 https://api.githubcopilot.com;企业版 https://copilot-api.<企业域名>。
sequenceDiagram
participant U as 用户
participant OC as opencode (copilot.ts)
participant GH as github.com (OAuth)
participant API as api.githubcopilot.com
U->>OC: auth login (github-copilot)
OC->>GH: POST /login/device/code {client_id, scope:read:user}
GH-->>OC: device_code, user_code, verification_uri, interval
OC-->>U: 打开 verification_uri, 输入 user_code
loop 轮询 (interval + 3s)
OC->>GH: POST /login/oauth/access_token {device_code, grant_type}
GH-->>OC: access_token (或 authorization_pending / slow_down)
end
Note over OC: 存储 access=refresh=access_token, expires=0 (无二次交换)
OC->>API: GET /models (Authorization: Bearer <token>)
API-->>OC: 模型 capabilities
U->>OC: 发送 prompt
OC->>API: POST /chat/completions | /responses | /v1/messages
Note right of API: 自定义 fetch 注入 Bearer + Copilot 专属 Header
API-->>OC: 流式响应 (SSE)
Loading
二、客户端发送给 GitHub Copilot 的 Request Header
最终 Header 由两处叠加而成:
session/llm/request.ts:187-204 组装基础 Header 并传给 streamText({ headers })(llm.ts:322)。
copilot.ts 的自定义 fetch(160-173) 在真正发出 HTTP 前追加/覆盖 Copilot 专属 Header。
期间 chat.headers 钩子(copilot.ts:360-412,经 request.ts:134-146 触发) 注入版本/场景类 Header。
叠加优先级:fetch 里先放默认 x-initiator,再 ...init.headers(即 chat.headers 的结果覆盖之),最后强制写死 User-Agent、Authorization、Openai-Intent(copilot.ts:160-166)。
Header
取值
来源(代码位置)
作用 / 说明
Authorization
Bearer <GitHub OAuth token>
copilot.ts:164(发现模型时 copilot.ts:74)
鉴权;直接用 OAuth access token,无二次交换
User-Agent
opencode/<InstallationVersion>
copilot.ts:163、request.ts:18,194,200
客户端标识(强制覆盖)
Openai-Intent
conversation-edits
copilot.ts:165
声明调用意图(对齐 Copilot CLI 行为)
X-GitHub-Api-Version
2026-06-01
copilot.ts:363(发现模型时 copilot.ts:76)
Copilot API 版本;由 chat.headers 注入
x-initiator
agent 或 user
copilot.ts:161(body 推断)、copilot.ts:394,411(compaction/子代理强制 agent)
标识请求由 agent 还是用户发起
Copilot-Vision-Request
true
copilot.ts:168-170
仅当消息含图片时添加(视觉请求)
X-Interaction-Type
agent-session-name-generation
copilot.ts:364-366
仅标题生成 agent(incoming.agent === "title")
anthropic-beta
interleaved-thinking-2025-05-14
copilot.ts:368-370
仅当模型走 @ai-sdk/anthropic(Claude 经 /v1/messages)
x-session-affinity / X-Session-Id
<sessionID>
request.ts:197-198
会话亲和/标识(非 opencode 网关分支)
x-parent-session-id
<parentSessionID>
request.ts:199
子代理会话才有
Content-Type / Accept
application/json / SSE
AI SDK(@ai-sdk/github-copilot、@ai-sdk/anthropic)自动设置
由传输层补齐
值得注意 :opencode 不发送 VS Code 版 Copilot 常见的 Copilot-Integration-Id、Editor-Version、Editor-Plugin-Version 等 Header(全仓库 grep 无匹配),而是用 User-Agent: opencode/... + Openai-Intent + X-GitHub-Api-Version 来标识自身。
三、客户端发送给 GitHub Copilot 的 Request Body
3.1 端点路由(决定 Body 形态,models.ts:92-111)
opencode 根据 Copilot /models 返回的 supported_endpoints 为每个模型选定端点与 AI SDK 适配包:
Copilot 端点
opencode endpoint
api.npm
api.url
Body 形态
/v1/messages
messages
@ai-sdk/anthropic
<base>/v1(models.ts:109)
Anthropic Messages
/responses
responses
@ai-sdk/github-copilot
<base>
OpenAI Responses
/chat/completions
chat
@ai-sdk/github-copilot
<base>
OpenAI Chat Completions
GPT-5 及以上(非 gpt-5-mini)默认走 Responses,其余走 Chat(llm/providers/github-copilot.ts:19-25 的 shouldUseResponsesApi)。Body 主体由对应 AI SDK 适配包序列化,opencode 再叠加下述变换。
3.2 Body 主要字段(按端点)
Chat Completions :{ model, messages, tools?, tool_choice?, temperature?, top_p?, max_tokens?/max_completion_tokens?, stream: true, stream_options, reasoning_effort? }
Responses :{ model, input, tools?, tool_choice?, temperature?, max_output_tokens?, reasoning:{ effort, summary:"auto" }, include:["reasoning.encrypted_content"], store: false, stream: true }
Messages(Anthropic) :{ model, messages, system?, tools?, max_tokens, thinking?, stream: true }
(opencode 用 streamText 发起,故均为流式;并对 Copilot 打开 includeRawChunks,以从 provider 专属字段读取真实计费额度,llm.ts:295。)
3.3 opencode 对 Body 的关键变换
变换
说明
代码位置
GPT 模型省略 maxOutputTokens
对齐 Copilot CLI,gpt* 模型不发 max_tokens
copilot.ts:343-346(chat.params 钩子)
Anthropic 关闭 toolStreaming
Copilot 的 /v1/messages shim 拒绝 eager_input_streaming,故关闭
copilot.ts:348-353
store = false
Copilot / OpenAI 系一律不在服务端存储(Responses)
transform.ts:1122-1130(含 @ai-sdk/github-copilot,行 1125)
剥离 Responses item ID
store !== true 时删除 itemId,保持已签名请求体不可变
transform.ts:474-486
Reasoning / thinking 选项
Responses:reasoningEffort + reasoningSummary:"auto" + include: encrypted;Claude:仅 reasoningEffort(过滤掉 max/xhigh,transform.ts:940-946);Gemini:Copilot 目前只回 thinking(不设)
transform.ts:847-871,1693-1697
提示缓存
providerOptions.copilot.copilot_cache_control = { type: "ephemeral" }
transform.ts:352-354(sdkKey 映射见 44-45)
注入 _noop 工具
重放历史 tool 调用但当前无启用工具时,Copilot 仍要求 tools 字段非空
request.ts:159-167
Body 组装的调用点见 session/llm.ts:280-324:streamText({ providerOptions: ProviderTransform.providerOptions(...), tools, toolChoice, maxOutputTokens, messages, headers, model })。
四、关键代码位置索引
packages/opencode/src/plugin/github-copilot/copilot.ts
CLIENT_ID/API_VERSION/base():9-10、26-28
模型发现 + 鉴权头:62-92
自定义 fetch(Header 注入 / 视觉 / 清理):96-181
设备码 OAuth 流程:222-336
chat.params(Body)钩子:340-354
chat.headers(Header)钩子:360-412
packages/opencode/src/plugin/github-copilot/models.ts:端点路由 92-111、GET /models 213-256
packages/opencode/src/session/llm/request.ts:Header 组装 187-204、钩子触发 114/134-146、_noop 工具 159-167
packages/opencode/src/session/llm.ts:streamText 280、includeRawChunks 295、providerOptions/headers 316/322
packages/opencode/src/provider/transform.ts:sdkKey 44-45、缓存 352-354、剥离 itemId 474-486、store=false 1122-1130、reasoning 847-871/940-946/1693-1697
packages/opencode/test/plugin/auth-override.test.ts:60:验证 loader 使用 access 字段
packages/llm/src/providers/github-copilot.ts:19-25:shouldUseResponsesApi
本报告由 opencode 请求流程分析任务自动生成,基于同步后的 dev(HEAD 对齐 upstream 4438f69)实际代码,未臆测。
OpenCode 发送给 GitHub Copilot 的请求分析(2026-07-22)
分析对象为 opencode 运行时(
packages/opencode)中的 GitHub Copilot 集成。核心实现集中在packages/opencode/src/plugin/github-copilot/copilot.ts(鉴权 + 自定义fetch)与models.ts(模型发现 + 端点路由),请求组装在session/llm/request.ts与session/llm.ts,Body 变换在provider/transform.ts。一、GitHub 身份验证与 Token 交换流程
1.1 结论先行
opencode 采用 GitHub OAuth Device Flow(设备码流程) 获取一个 GitHub OAuth
access_token,随后直接把该 token 作为Authorization: Bearer <token>发给 Copilot API。关键点:opencode 不做 “GitHub token → 短时效 Copilot token” 的二次交换(即不请求
api.github.com/copilot_internal/v2/token)。对比同仓库的codex.ts、xai.ts、snowflake-cortex.ts都实现了refreshAccessToken(...),而github-copilot插件没有任何刷新/交换逻辑——设备码换来的 GitHub OAuth token 被当作长期凭据直接使用(存储时expires: 0,copilot.ts:298)。1.2 设备码换取流程(
copilot.ts:222-336)CLIENT_ID = "Ov23li8tweQw6odWQebz"(copilot.ts:9),API 版本API_VERSION = "2026-06-01"(copilot.ts:10)。copilot.ts:186-221):GitHub.com(domain = github.com)与GitHub Enterprise(用户填入企业域名,normalizeDomain归一化,copilot.ts:15-17,227-230)。copilot.ts:19-24):https://<domain>/login/device/codehttps://<domain>/login/oauth/access_token步骤:
copilot.ts:234-256):POST /login/device/code,body{ client_id, scope: "read:user" },HeaderAccept: application/json、Content-Type: application/json、User-Agent: opencode/<version>。返回verification_uri、user_code、device_code、interval。verification_uri并输入user_code(copilot.ts:258-261)。copilot.ts:262-334):循环POST /login/oauth/access_token,body{ client_id, device_code, grant_type: "urn:ietf:params:oauth:grant-type:device_code" }。access_token即成功,存储为{ type:"success", refresh: access_token, access: access_token, expires: 0, enterpriseUrl? }(copilot.ts:286-306)——注意refresh与access是同一个 GitHub OAuth token。authorization_pending(按interval等待重试)、slow_down(按 RFC 8628 增加 5s,copilot.ts:313-327),并统一加了OAUTH_POLLING_SAFETY_MARGIN_MS = 3000的时钟漂移安全余量(copilot.ts:14)。1.3 凭据注入(自定义
fetch,copilot.ts:96-181)auth.loader返回一个覆盖版fetch;仅当auth.type === "oauth"时生效(否则透传原生fetch,copilot.ts:103-104)。它对每个发往 Copilot 的请求:isVision/isAgent(兼容 3 种 API 形态:Completions / Responses / Messages,copilot.ts:107-158)。copilot.ts:160-166),设置Authorization: Bearer <access>(copilot.ts:164,access即 OAuth token,见auth-override.test.ts:60断言 loader 返回{ access: 'test-token' })。x-api-key、authorization(小写)(copilot.ts:172-173)。1.4 模型发现也走同一凭据(
copilot.ts:62-92→models.ts:213-256)登录后
provider.models(...)会GET <base>/models(models.ts:218),Header 带Authorization: Bearer <access>、User-Agent: opencode/<version>、X-GitHub-Api-Version: 2026-06-01(copilot.ts:73-77)。用返回的 capabilities 动态构建模型、价格、reasoning variants,并据model_picker_enabled过滤选择器可见模型(models.ts:254)。请求失败或未登录时回退到静态模型表(copilot.ts:86-91)。Base URL(
copilot.ts:26-28):公有云https://api.githubcopilot.com;企业版https://copilot-api.<企业域名>。sequenceDiagram participant U as 用户 participant OC as opencode (copilot.ts) participant GH as github.com (OAuth) participant API as api.githubcopilot.com U->>OC: auth login (github-copilot) OC->>GH: POST /login/device/code {client_id, scope:read:user} GH-->>OC: device_code, user_code, verification_uri, interval OC-->>U: 打开 verification_uri, 输入 user_code loop 轮询 (interval + 3s) OC->>GH: POST /login/oauth/access_token {device_code, grant_type} GH-->>OC: access_token (或 authorization_pending / slow_down) end Note over OC: 存储 access=refresh=access_token, expires=0 (无二次交换) OC->>API: GET /models (Authorization: Bearer <token>) API-->>OC: 模型 capabilities U->>OC: 发送 prompt OC->>API: POST /chat/completions | /responses | /v1/messages Note right of API: 自定义 fetch 注入 Bearer + Copilot 专属 Header API-->>OC: 流式响应 (SSE)二、客户端发送给 GitHub Copilot 的 Request Header
最终 Header 由两处叠加而成:
session/llm/request.ts:187-204组装基础 Header 并传给streamText({ headers })(llm.ts:322)。copilot.ts的自定义fetch(160-173) 在真正发出 HTTP 前追加/覆盖 Copilot 专属 Header。chat.headers钩子(copilot.ts:360-412,经request.ts:134-146触发) 注入版本/场景类 Header。叠加优先级:
fetch里先放默认x-initiator,再...init.headers(即chat.headers的结果覆盖之),最后强制写死User-Agent、Authorization、Openai-Intent(copilot.ts:160-166)。AuthorizationBearer <GitHub OAuth token>copilot.ts:164(发现模型时copilot.ts:74)accesstoken,无二次交换User-Agentopencode/<InstallationVersion>copilot.ts:163、request.ts:18,194,200Openai-Intentconversation-editscopilot.ts:165X-GitHub-Api-Version2026-06-01copilot.ts:363(发现模型时copilot.ts:76)chat.headers注入x-initiatoragent或usercopilot.ts:161(body 推断)、copilot.ts:394,411(compaction/子代理强制agent)Copilot-Vision-Requesttruecopilot.ts:168-170X-Interaction-Typeagent-session-name-generationcopilot.ts:364-366incoming.agent === "title")anthropic-betainterleaved-thinking-2025-05-14copilot.ts:368-370@ai-sdk/anthropic(Claude 经/v1/messages)x-session-affinity/X-Session-Id<sessionID>request.ts:197-198x-parent-session-id<parentSessionID>request.ts:199Content-Type/Acceptapplication/json/ SSE@ai-sdk/github-copilot、@ai-sdk/anthropic)自动设置值得注意:opencode 不发送 VS Code 版 Copilot 常见的
Copilot-Integration-Id、Editor-Version、Editor-Plugin-Version等 Header(全仓库grep无匹配),而是用User-Agent: opencode/...+Openai-Intent+X-GitHub-Api-Version来标识自身。三、客户端发送给 GitHub Copilot 的 Request Body
3.1 端点路由(决定 Body 形态,
models.ts:92-111)opencode 根据 Copilot
/models返回的supported_endpoints为每个模型选定端点与 AI SDK 适配包:endpointapi.npmapi.url/v1/messagesmessages@ai-sdk/anthropic<base>/v1(models.ts:109)/responsesresponses@ai-sdk/github-copilot<base>/chat/completionschat@ai-sdk/github-copilot<base>GPT-5 及以上(非
gpt-5-mini)默认走 Responses,其余走 Chat(llm/providers/github-copilot.ts:19-25的shouldUseResponsesApi)。Body 主体由对应 AI SDK 适配包序列化,opencode 再叠加下述变换。3.2 Body 主要字段(按端点)
{ model, messages, tools?, tool_choice?, temperature?, top_p?, max_tokens?/max_completion_tokens?, stream: true, stream_options, reasoning_effort? }{ model, input, tools?, tool_choice?, temperature?, max_output_tokens?, reasoning:{ effort, summary:"auto" }, include:["reasoning.encrypted_content"], store: false, stream: true }{ model, messages, system?, tools?, max_tokens, thinking?, stream: true }(opencode 用
streamText发起,故均为流式;并对 Copilot 打开includeRawChunks,以从 provider 专属字段读取真实计费额度,llm.ts:295。)3.3 opencode 对 Body 的关键变换
maxOutputTokensgpt*模型不发max_tokenscopilot.ts:343-346(chat.params钩子)toolStreaming/v1/messagesshim 拒绝eager_input_streaming,故关闭copilot.ts:348-353store = falsetransform.ts:1122-1130(含@ai-sdk/github-copilot,行 1125)store !== true时删除itemId,保持已签名请求体不可变transform.ts:474-486reasoningEffort+reasoningSummary:"auto"+include: encrypted;Claude:仅reasoningEffort(过滤掉max/xhigh,transform.ts:940-946);Gemini:Copilot 目前只回 thinking(不设)transform.ts:847-871,1693-1697providerOptions.copilot.copilot_cache_control = { type: "ephemeral" }transform.ts:352-354(sdkKey映射见 44-45)_noop工具tools字段非空request.ts:159-167Body 组装的调用点见
session/llm.ts:280-324:streamText({ providerOptions: ProviderTransform.providerOptions(...), tools, toolChoice, maxOutputTokens, messages, headers, model })。四、关键代码位置索引
packages/opencode/src/plugin/github-copilot/copilot.tsCLIENT_ID/API_VERSION/base():9-10、26-28fetch(Header 注入 / 视觉 / 清理):96-181chat.params(Body)钩子:340-354chat.headers(Header)钩子:360-412packages/opencode/src/plugin/github-copilot/models.ts:端点路由 92-111、GET /models213-256packages/opencode/src/session/llm/request.ts:Header 组装 187-204、钩子触发 114/134-146、_noop工具 159-167packages/opencode/src/session/llm.ts:streamText280、includeRawChunks295、providerOptions/headers316/322packages/opencode/src/provider/transform.ts:sdkKey44-45、缓存 352-354、剥离 itemId 474-486、store=false1122-1130、reasoning 847-871/940-946/1693-1697packages/opencode/test/plugin/auth-override.test.ts:60:验证 loader 使用access字段packages/llm/src/providers/github-copilot.ts:19-25:shouldUseResponsesApi本报告由 opencode 请求流程分析任务自动生成,基于同步后的
dev(HEAD 对齐 upstream4438f69)实际代码,未臆测。