Skip to content

[opencode 请求流程分析] OpenCode → GitHub Copilot 鉴权/Header/Body 分析(2026-08-14) #35

Description

@nickhou1983

OpenCode → GitHub Copilot 请求流程与请求 Header/Body 分析(2026-08-14)

本报告分析 opencode 向 GitHub Copilotapi.githubcopilot.com)发起请求的完整链路,聚焦三部分:① GitHub 身份验证与 Token 交换流程、② 客户端发送的 Request Header、③ 客户端发送的 Request Body。所有结论以仓库实际代码为准,均给出 path:line 索引。

0. 本次上游同步提示

本次从上游 anomalyco/opencodedev 分支快进同步 12 个提交cc4b456129..d8bf79225f):

提交 说明 是否影响 Copilot 请求流程
d8bf79225f fix(opencode): preserve v1 database compatibility (anomalyco#42444)
2449581543 / f06e9491e1 Gemini 3.7 Flash 增删(go 侧)
d0c2b41adf / c7af47f9ed Grok 4.5 改用 responses API / 更新 grok endpoint 否(xAI,非 Copilot)
6c035e1fd7 fix(core): preserve unicode in grep previews
62387f39d4 / 864889ab9f skills 文档、移除 Ling 3.0 Tiny

结论:本次同步的提交均未改动 GitHub Copilot 的鉴权 / Header / Body 构造逻辑,下述分析对当前 dev 依旧成立。


1. 身份验证与 Token 交换流程

GitHub Copilot 采用 GitHub OAuth Device Flow(设备码流程,RFC 8628),核心实现集中在 packages/opencode/src/plugin/github-copilot/copilot.ts

1.1 关键常量与端点

位置
OAuth App Client ID Ov23li8tweQw6odWQebz copilot.ts:9
API 版本 2026-06-01 copilot.ts:10
设备码端点 https://{domain}/login/device/code copilot.ts:21
令牌端点 https://{domain}/login/oauth/access_token copilot.ts:22
推理基址(公有云) https://api.githubcopilot.com copilot.ts:27
推理基址(企业版) https://copilot-api.{enterprise-domain} copilot.ts:26-27

domain 默认为 github.com;选择 “GitHub Enterprise” 时取用户输入的企业域名(copilot.ts:222-230)。

1.2 登录与授权(authorizecopilot.ts:222-336

  1. 请求设备码POST {domain}/login/device/code,body 为 { client_id, scope: "read:user" },Header 含 Accept/Content-Type: application/jsonUser-Agent: opencode/${InstallationVersion}copilot.ts:234-245)。
  2. 返回 verification_uri + user_code,提示用户在浏览器输入验证码(copilot.ts:251-261)。
  3. 轮询换取令牌callbackcopilot.ts:262-334):循环 POST {domain}/login/oauth/access_token,body 为
    { client_id, device_code, grant_type: "urn:ietf:params:oauth:grant-type:device_code" }copilot.ts:271-275)。
    • authorization_pending → 按 interval + 3s 安全余量再轮询(copilot.ts:308-311OAUTH_POLLING_SAFETY_MARGIN_MS = 3000)。
    • slow_down → 按 RFC 8628 §3.5 将间隔 +5s(copilot.ts:313-327)。
  4. 落库:拿到 access_token 后返回 { type: "success", refresh: access_token, access: access_token, expires: 0 };企业版另存 enterpriseUrlcopilot.ts:286-305)。凭据经 ProviderAuth.callback 写入 auth 存储:auth.set(providerID, { type:"oauth", access, refresh, expires, ...extra })packages/opencode/src/provider/auth.ts:211-220)。

1.3 ⭐ Token “交换”的关键结论

本实现没有独立的 Copilot Token 交换步骤。全仓库检索 copilot_internal / v2/token 无任何命中(仅 base URL)——即 不存在 传统 VSCode Copilot 那种「GitHub OAuth token → api.github.com/copilot_internal/v2/token → 短时效 Copilot token」的二次交换。

  • 设备码换来的 GitHub OAuth user token 被直接当作 Bearer 使用(既用于拉取模型列表,也用于推理请求)。
  • expires: 0 表示不额外缓存 Copilot 短时令牌;github-copilot 的 auth hook 未定义 refresh 方法,每次请求由 loader 从 auth 存储实时读取 access 后直接注入。

1.4 凭据注入机制(loader / 自定义 fetch)

provider 装配阶段会调用插件的 auth.loader(getAuth, info)packages/opencode/src/provider/provider.ts:1556-1566),其中 getAuth = () => auth.get(providerID)。github-copilot 的 loader 返回 { apiKey: "", fetch },用一个 自定义 fetch 包装器copilot.ts:100-179)在每次请求时:实时 getAuth() → 取 info.access → 注入 Authorization: Bearer <access>

sequenceDiagram
    participant U as 用户
    participant CLI as opencode (copilot.ts)
    participant GH as github.com (OAuth)
    participant Auth as Auth 存储
    participant API as api.githubcopilot.com

    U->>CLI: auth login (github-copilot)
    CLI->>GH: POST /login/device/code {client_id, scope:read:user}
    GH-->>CLI: {device_code, user_code, verification_uri, interval}
    CLI-->>U: 打开 verification_uri, 输入 user_code
    loop 轮询 (interval + 3s)
        CLI->>GH: POST /login/oauth/access_token {client_id, device_code, grant_type}
        GH-->>CLI: authorization_pending / slow_down / access_token
    end
    CLI->>Auth: set oauth {access=refresh=token, expires:0}
    Note over CLI,API: 推理阶段(无二次 token 交换)
    CLI->>Auth: getAuth() → info.access
    CLI->>API: POST /chat|/responses|/v1/messages  Authorization: Bearer <GitHub OAuth token>
    API-->>CLI: SSE 流式响应
Loading

2. 客户端发送给 GitHub Copilot 的 Request Header

Header 由 三层 叠加而成,越靠后优先级越高(后者覆盖前者):

第 1 层 · 通用请求层(packages/opencode/src/session/llm/request.ts:187-204

对非 opencode 自有 provider(含 github-copilot)注入:

Header 取值来源 说明
x-session-affinity input.sessionID 会话亲和/路由
X-Session-Id input.sessionID 会话 ID
x-parent-session-id input.parentSessionID(仅子代理) 子会话标识
User-Agent opencode/${InstallationVersion}request.ts:18 后续会被 loader 再次强制覆盖
...input.model.headers 模型级 Header 通常为空
...headers 来自 chat.headers hook 输出 见第 2 层

第 2 层 · chat.headers 钩子(copilot.ts:360-412

Header 取值 条件 / 位置
X-GitHub-Api-Version 2026-06-01 恒定(copilot.ts:363
X-Interaction-Type agent-session-name-generation title 代理(copilot.ts:364-366
anthropic-beta interleaved-thinking-2025-05-14 模型 npm 为 @ai-sdk/anthropic 时(copilot.ts:368-370
x-initiator agent 命中 compaction / 子代理会话时(copilot.ts:394,411

第 3 层 · loader 自定义 fetch(最终权威,copilot.ts:160-174

请求真正出网前的最后一步,强制写入鉴权与身份类 Header:

Header 取值 说明
Authorization Bearer <info.access> GitHub OAuth token 直接作为 Bearer(copilot.ts:164
User-Agent opencode/${InstallationVersion} 强制覆盖(copilot.ts:163
Openai-Intent conversation-edits 恒定(copilot.ts:165
x-initiator agent | user 默认按 body 推断,可被第 2 层覆盖(copilot.ts:161
Copilot-Vision-Request true 请求含图片时(copilot.ts:168-170
x-api-key 删除 避免与 Bearer 冲突(copilot.ts:172
authorization(小写) 删除 去掉 ai-sdk 可能注入的重复项(copilot.ts:173

拉取模型列表 /models 时另用一组 Header:Authorization: BearerUser-AgentX-GitHub-Api-Versioncopilot.ts:72-77)。

2.1 x-initiator 的判定逻辑(copilot.ts:107-158

通过解析 init.body 的最后一条消息推断是「agent」还是「user」发起:

  • Completions / Responses APIisAgent = last.role !== "user" || imgMsg(last)
  • Messages APIisAgent = !(last.role === "user" && 含非 tool_result 内容) || imgMsg(last)
  • 另在 chat.headers 中:compaction 续写、子代理(有 parentID)一律标记为 agentcopilot.ts:385-411)。

2.2 ⚠️ 值得注意:缺省的经典 Header

本实现 未发送 VSCode Copilot / 旧版 opencode 常见的以下 Header(全仓库检索无命中):

  • Copilot-Integration-Id(经典值 vscode-chat
  • Editor-Version / Editor-Plugin-Version

即 opencode 以自有 User-Agent: opencode/<version> + X-GitHub-Api-Version 标识自身,而非伪装成编辑器插件。


3. 客户端发送给 GitHub Copilot 的 Request Body

3.1 端点与 Body 形态路由(packages/opencode/src/plugin/github-copilot/models.ts:95-114

依据 /models 返回的 supported_endpoints 决定端点、SDK 与 URL:

supported_endpoints endpoint npm SDK URL Body 形态
/v1/messages messages @ai-sdk/anthropic {base}/v1/v1/messages Anthropic Messages
/responses responses @ai-sdk/github-copilot {base}/responses OpenAI Responses
/chat/completions chat @ai-sdk/github-copilot {base}/chat/completions OpenAI Chat Completions

@ai-sdk/github-copilot 对应仓库内置的 OpenAI 兼容 provider packages/core/src/github-copilot/(README 明确标注「仅供 GitHub Copilot 使用」)。GPT-5 级模型(除 gpt-5-mini)走 Responses,其余走 Chat(packages/llm/src/providers/github-copilot.ts:19-25packages/core/src/plugin/provider/github-copilot.ts:42-48)。

3.2 Body 组装(packages/opencode/src/session/llm/request.ts:181-205

最终交给 ai-sdk 序列化的核心字段:

字段 内容 位置
messages / input system 提示(转 system 角色)+ 会话消息;Responses 家族会把 system 收敛进 instructionsrequest.ts:99 request.ts:101-112
tools 工具定义,按名称排序 request.ts:184
temperature / topP / topK 采样参数(经 ProviderTransform request.ts:124-128
maxOutputTokens 输出上限(GPT 模型会被置空,见下) request.ts:129
providerOptions.copilot Copilot 专属选项(sdkKey("@ai-sdk/github-copilot") = "copilot" transform.ts:44-45

3.3 chat.params 针对 Copilot 的 Body 调整(copilot.ts:340-354

  • GPT 模型maxOutputTokens = undefined(对齐 GitHub Copilot CLI,省略该字段,copilot.ts:344-346)。
  • Anthropic npm 模型toolStreaming = false —— Copilot 的 /v1/messages shim 会拒绝工具定义上的 eager_input_streaming 字段(“Extra inputs are not permitted”),故关闭(copilot.ts:348-353)。

3.4 _noop 工具兜底(request.ts:159-175

当 provider 为 github-copilot、当前无启用工具,但历史消息里存在工具调用(hasToolCalls)时,注入一个永不调用的 _noop 工具——因为 Copilot 在回放历史 tool_calls 时要求 body 必须带 tools 字段

3.5 transform.ts 中的 Copilot Body 变换

  • Prompt 缓存copilot_cache_control: { type: "ephemeral" }transform.ts:376-378)。
  • Responses APIstore: falsetransform.ts:1331-1337)、reasoningSummary: "auto"transform.ts:1295-1301)。
  • 推理力度 reasoningEffort:按模型族分派——gemini 仅返回 thinking(不设 effort)、claude 用 reasoningEffort、gpt 视 5.1-codex-max/5.2/5.3 追加 xhightransform.ts:897-9161751-1757)。
  • 推理变体附带 include: ["reasoning.encrypted_content"]models.ts:165-171)。

3.6 Body 结构示例

Chat CompletionsPOST /chat/completions):

{ "model": "<id>", "messages": [{ "role": "system", ... }, ...],
  "tools": [...], "stream": true,
  "copilot_cache_control": { "type": "ephemeral" }, "reasoning_effort": "..." }

ResponsesPOST /responses,GPT-5 级):

{ "model": "<id>", "instructions": "<system>", "input": [...],
  "tools": [...], "store": false, "stream": true,
  "reasoning": { "effort": "...", "summary": "auto" },
  "include": ["reasoning.encrypted_content"] }

MessagesPOST /v1/messages,Claude 系):

{ "model": "<id>", "system": "<system>", "messages": [...],
  "tools": [...], "stream": true }  // 工具定义已去除 eager_input_streaming

4. 关键代码位置索引

主题 文件 关键行
设备码 OAuth / 令牌轮询 / 落库 packages/opencode/src/plugin/github-copilot/copilot.ts 9-28, 222-336
凭据注入 / Header 强制层 / x-initiator packages/opencode/src/plugin/github-copilot/copilot.ts 100-179, 340-412
OAuth 回调落库 packages/opencode/src/provider/auth.ts 188-221
loader 装配 / getAuth packages/opencode/src/provider/provider.ts 1548-1566
通用请求层 Header/Body 组装 packages/opencode/src/session/llm/request.ts 18, 99-205
端点路由 / 模型能力解析 packages/opencode/src/plugin/github-copilot/models.ts 95-172
Body 变换(缓存/store/reasoning) packages/opencode/src/provider/transform.ts 44-45, 376-378, 897-916, 1295-1337, 1751-1757
Chat/Responses 路由选择 packages/llm/src/providers/github-copilot.ts 19-63
内置 OpenAI 兼容 provider packages/core/src/github-copilot/ copilot-provider.ts, chat/, responses/

本报告为只读分析,未修改任何业务代码;仅执行了「同步上游 dev」这一用户明确要求的操作。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions