Skip to content

[opencode 请求流程分析] OpenCode 向 GitHub Copilot 发送请求的流程、Header 与 Body 分析(2026-07-20) #10

Description

@nickhou1983

OpenCode 向 GitHub Copilot 发送请求的流程、Header 与 Body 分析(2026-07-20)

本报告聚焦 GitHub Copilot provider,逐一梳理用户关心的三部分:① GitHub 身份验证与 Token 交换流程② 客户端发送给 Copilot 的 Request Header③ 客户端发送给 Copilot 的 Request Body。所有结论均以仓库实际代码为准,并标注 path:line

0. 本次上游同步说明

使用 GitHub MCP(list_commits)检测到上游 anomalyco/opencodedev 领先本 fork 11 个提交(本地 HEAD/origin/dev = 317044017e,上游最新 = 7985c2066a)。由于本 fork 的 dev 因 fork 专属提交(b1e2d230de 新增每日同步工作流)已与上游分叉,无法快进,故以 merge 方式同步(合并提交 0e58547962,无冲突)。同步进来的提交:

SHA 提交
7985c2066a fix(app): show keybind tooltips on prompt input controls (anomalyco#37824)
67caf894e0 fix(opencode): increase OpenAI header timeout (anomalyco#37770)
78587c141b fix(app): restore question pager segments (anomalyco#37575)
f5573281ca chore: generate
4817228f96 docs(go): update Kimi K3 request estimates (anomalyco#37725)
5a5117b36c Merge branch 'dev'
6a288426b8 go: support openai route
573ac85983 feat(desktop): align new session project picker (anomalyco#37461)
1221bbbe14 Merge branch 'dev'
6bea0f4fa4 zen: grok 4.5 adjusted price
cc34084dfe feat(desktop): prevent overlapping composer borders (anomalyco#37490)

对 Copilot 请求流程 / Header 的影响:无。 唯一触及 provider.ts 的是 67caf894e0,它把 OPENAI_HEADER_TIMEOUT_DEFAULT10_000 改为 300_000provider.ts:35),但该超时只应用于 openai providerprovider.ts:208),github-copilotprovider.ts:225-239)并未使用,故对 Copilot 无影响。其余提交集中在 app/desktop/go/docs/zen,均未触及 Copilot 鉴权、Header 或 Body 构造代码。


1. 架构分层与关键文件

OpenCode 把「Copilot 特有逻辑」放进插件,把「HTTP 传输 / body 构造」交给 AI SDK 兼容层,二者通过 provider 的自定义 fetch 与 plugin hooks 衔接:

位置 职责
Copilot 认证插件 packages/opencode/src/plugin/github-copilot/copilot.ts OAuth 设备码登录、注入鉴权与 Header 的自定义 fetchchat.params/chat.headers hook
Copilot 模型清单 packages/opencode/src/plugin/github-copilot/models.ts 拉取 /models、决定端点(chat/responses/messages)与价格
凭据存储 packages/opencode/src/provider/auth.tspackages/opencode/src/auth/index.ts OAuth authorize/callback、auth.json 持久化
请求准备(编排) packages/opencode/src/session/llm/request.ts 组装 system/messages/tools/params,触发 hooks,生成基础 Header
Provider 装配(v2) packages/core/src/plugin/provider/github-copilot.ts 端点路由,注入 @ai-sdk/github-copilot SDK
OpenAI 兼容 Provider packages/core/src/github-copilot/copilot-provider.ts 组装 baseURL / headers / 自定义 fetch
Chat 语言模型 packages/core/src/github-copilot/chat/openai-compatible-chat-language-model.ts /chat/completions body 构造与发送
Responses 语言模型 packages/core/src/github-copilot/responses/openai-responses-language-model.ts /responses body 构造与发送

Copilot 三种端点(由 /models 返回的 supported_endpoints 决定,models.ts:92-111):

  • chatPOST https://api.githubcopilot.com/chat/completions(npm @ai-sdk/github-copilot
  • responsesPOST https://api.githubcopilot.com/responses(npm @ai-sdk/github-copilot
  • messagesPOST https://api.githubcopilot.com/v1/messages(npm @ai-sdk/anthropicapi.url 追加 /v1

GPT-5 及以上(除 gpt-5-mini)默认走 Responses,其余走 Chat(github-copilot.ts:42-48provider.ts:234-236)。

sequenceDiagram
  autonumber
  participant S as Session/Prompt
  participant R as request.ts (prepare)
  participant PL as copilot.ts hooks
  participant SDK as AI SDK 语言模型
  participant F as loader.fetch (copilot.ts)
  participant API as api.githubcopilot.com
  S->>R: system / messages / tools / params
  R->>PL: trigger chat.params & chat.headers
  PL-->>R: params(maxOutputTokens 等) + headers(X-GitHub-Api-Version 等)
  R->>SDK: streamText(model, messages, headers)
  SDK->>SDK: getArgs() 构造 body (JSON)
  SDK->>F: postJsonToApi(url, combineHeaders, body)
  F->>F: 注入 Authorization/Openai-Intent/x-initiator/UA;删除 x-api-key
  F->>API: POST /chat/completions | /responses | /v1/messages (SSE)
  API-->>SDK: text/event-stream 流式响应
Loading

2. GitHub 身份验证与 Token 交换流程

2.1 核心结论

OpenCode 使用 GitHub OAuth「设备授权码流程」(Device Authorization Grant,RFC 8628)拿到一个 GitHub OAuth token,然后把它 直接 作为 Authorization: Bearer 用于 api.githubcopilot.com。仓库中 不存在 传统的 POST /copilot_internal/v2/token 二次换取短期 Copilot token 的步骤,也 没有github-copilot 注册任何 refresh 刷新函数。

关键证据:

  • 全仓检索无 copilot_internal / /v2/token 端点调用;github-copilotauth.methods 里只有 type: "oauth"refresh 实现(对比 openai/codex.ts:361xai.ts:492snowflake-cortex.ts:443 都有 expires < Date.now() + grant_type: refresh_token 的刷新逻辑)。
  • 实际用作 Bearer 的是 refresh 字段(长期 GitHub token),而非 access
    • 模型清单请求:Authorization: \Bearer ${auth.refresh}`copilot.ts:74`)
    • 聊天/推理请求:Authorization: \Bearer ${info.refresh}`copilot.ts:164`)
  • 存储时 refreshaccess 都等于 data.access_tokenexpires: 0copilot.ts:296-298),且无人读取 expires 触发刷新,故 token 原样长期使用。

2.2 客户端标识与常量(copilot.ts:9-28

  • OAuth App client_id = "Ov23li8tweQw6odWQebz"
  • API 版本 API_VERSION = "2026-06-01"(用于 X-GitHub-Api-Version
  • 轮询安全余量 OAUTH_POLLING_SAFETY_MARGIN_MS = 3000
  • 域名:默认 github.com;企业版 normalizeDomain(enterpriseUrl),API base 变为 https://copilot-api.<domain>;公有云为 https://api.githubcopilot.com

2.3 设备码登录流程(authorizecopilot.ts:222-336

  1. 选择部署类型GitHub.comGitHub Enterprise(企业版需再输入 URL/域名,带校验)(copilot.ts:186-221)。
  2. 发起设备码POST https://<domain>/login/device/codecopilot.ts:234-245
    • Header:Accept: application/jsonContent-Type: application/jsonUser-Agent: opencode/<version>
    • Body:{ "client_id": "Ov23li8tweQw6odWQebz", "scope": "read:user" }
    • 返回:verification_uriuser_codedevice_codeinterval
  3. 提示用户:打开 verification_uri 并输入 Enter code: <user_code>copilot.ts:258-261)。
  4. 轮询换取 tokenPOST https://<domain>/login/oauth/access_tokencopilot.ts:264-276
    • Body:{ "client_id", "device_code", "grant_type": "urn:ietf:params:oauth:grant-type:device_code" }
    • authorization_pending → 按 interval + 3s 继续轮询(copilot.ts:308-311
    • slow_down → 依 RFC 8628 §3.5 间隔 +5s(或用服务端返回的 interval)后重试(copilot.ts:313-327
    • 其它 error → 返回 failedcopilot.ts:329
  5. 持久化凭据copilot.ts:286-306provider/auth.ts:211-220auth/index.ts:73-81):
    写入 ~/.local/share/opencode/auth.jsonGlobal.Path.data/auth.json,权限 0o600),结构:
    { "type": "oauth", "refresh": "<gho_...>", "access": "<gho_...>", "expires": 0, "enterpriseUrl": "<可选>" }

2.4 请求期的 Token 注入(loadercopilot.ts:96-181

  • loader(getAuth) 返回 { apiKey: "", fetch },即一个 provider 级自定义 fetch
  • getAuth = () => auth.get("github-copilot")provider.ts:1553-1558)——只读取存储的 token,不做刷新
  • 每次请求进入自定义 fetch 后:读取 info.refresh 作为 Bearer 注入(详见第 3 节)。apiKey: "" 使 AI SDK 基础层写入的空 Authorization 最终被自定义 fetch 覆盖为真实 token。
sequenceDiagram
  autonumber
  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(github.com / 企业版)
  OC->>GH: POST /login/device/code {client_id, scope: read:user}
  GH-->>OC: {verification_uri, user_code, device_code, interval}
  OC-->>U: 打开 verification_uri,输入 user_code
  loop 轮询 (interval + 3s)
    OC->>GH: POST /login/oauth/access_token {client_id, device_code, grant_type: device_code}
    GH-->>OC: authorization_pending / slow_down / {access_token}
  end
  OC->>OC: 写 auth.json {refresh = access = access_token, expires: 0}
  Note over OC,API: 不再二次换取 Copilot token,无刷新
  OC->>API: 后续请求 Authorization: Bearer <refresh>
Loading

3. 客户端发送给 Copilot 的 Request Header

最终 Header 由四层叠加而成,自定义 fetch(第 4 层)拥有最终决定权

3.1 模型清单请求 GET {base}/modelscopilot.ts:70-79 + models.ts:213-226

Header 取值来源 说明
Authorization Bearer ${auth.refresh}copilot.ts:74 GitHub OAuth token
User-Agent opencode/${InstallationVersion}copilot.ts:75 客户端标识
X-GitHub-Api-Version 2026-06-01copilot.ts:76 Copilot API 版本
...provider.options.headers 既有 provider 配置(copilot.ts:73 透传

该请求带 5s 超时(models.ts:220),失败时回退为「用内置模型 + 直连 base」。

3.2 聊天 / 推理请求(/chat/completions/responses/v1/messages

第 1 层 — request.ts 基础 Header(request.ts:187-204,非 opencode provider 分支)

Header 取值来源 说明
x-session-affinity input.sessionIDrequest.ts:197 会话粘性
X-Session-Id input.sessionIDrequest.ts:198 会话 ID
x-parent-session-id input.parentSessionIDrequest.ts:199,子代理时) 父会话 ID
User-Agent opencode/${InstallationVersion}request.ts:200USER_AGENT=request.ts:18 客户端标识
...input.model.headers 模型级 Header(request.ts:202 透传

第 2 层 — chat.headers hook(copilot.ts:360-412

Header 取值来源 说明
X-GitHub-Api-Version 2026-06-01copilot.ts:363 恒定注入
X-Interaction-Type agent-session-name-generationcopilot.ts:365 仅当 agent === "title"(生成会话标题)
anthropic-beta interleaved-thinking-2025-05-14copilot.ts:369 仅当模型走 @ai-sdk/anthropic(messages 端点)
x-initiator agentcopilot.ts:394/411 命中压缩(compaction)续写或子代理会话时置为 agent

第 3 层 — AI SDK 兼容 Provider 基础 Header(copilot-provider.ts:60-66

Header 取值来源 说明
Authorization Bearer ${apiKey}copilot-provider.ts:62 apiKey="",占位,随后被覆盖
User-Agent(后缀) withUserAgentSuffix(..., "ai-sdk/openai-compatible/0.1.0")copilot-provider.ts:66 AI SDK 标识后缀
Content-Type application/json(由 postJsonToApi 添加) JSON body
...options.headers 上面 1/2 层合并的 headers 透传(combineHeaderschat...:323responses...:406

第 4 层 — 自定义 fetch 最终覆盖(copilot.ts:160-173)——真正写到线上的鉴权/意图 Header

Header 取值来源 说明
Authorization Bearer ${info.refresh}copilot.ts:164 真正的 GitHub OAuth token
User-Agent opencode/${InstallationVersion}copilot.ts:163 覆盖 AI SDK 的 UA
Openai-Intent conversation-editscopilot.ts:165 Copilot 侧意图标识
x-initiator agent / usercopilot.ts:161 由请求体推断(见 3.3)
Copilot-Vision-Request truecopilot.ts:168-170 仅当请求体含图片
x-api-key(删除) delete headers["x-api-key"]copilot.ts:172 去除 Anthropic 风格 key
authorization(删除小写) delete headers["authorization"]copilot.ts:173 防止大小写重复

综合结果(发往 api.githubcopilot.com 的有效 Header):
Authorization: Bearer <gho token>User-Agent: opencode/<version>Openai-Intent: conversation-editsX-GitHub-Api-Version: 2026-06-01x-initiator: user|agentContent-Type: application/jsonX-Session-Id/x-session-affinity: <sessionID>,以及按需的 Copilot-Vision-Request: trueanthropic-betaX-Interaction-Typex-parent-session-id

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

自定义 fetch 会解析请求体,区分「用户主动」与「代理自动」:

  • Completions APIbody.messages 且 url 含 completions):末条消息 role !== "user" 或为合成图片消息 → agent
  • Responses APIbody.input):同理判断 input 末元素。
  • Messages APIbody.messages,Anthropic):末条非「纯 user 文本」→ agent
  • isVision:任一消息含 image_url / input_image / image(含嵌套在 tool_result 内的图片)→ 追加 Copilot-Vision-Request: true

4. 客户端发送给 Copilot 的 Request Body

请求体由 AI SDK 语言模型的 getArgs() 构造,params(温度/topP/最大输出等)来自 request.ts:123-131chat.params hook。

4.1 chat.params hook 的 Copilot 特化(copilot.ts:340-354

  • 模型 id 含 gptmaxOutputTokens = undefined(对齐官方 Copilot CLI,不传 max_tokens)。
  • @ai-sdk/anthropic(messages 端点)→ options.toolStreaming = false(Copilot 的 /v1/messages shim 拒绝 GA 的 eager_input_streaming 字段)。

4.2 Chat Completions body — POST {base}/chat/completionsopenai-compatible-chat-language-model.ts:139-190

{
  "model": "<api.id>",
  "user": "<可选>",
  "max_tokens": <maxOutputTokens  undefined(gpt)>,
  "temperature": <number>,
  "top_p": <number>,
  "frequency_penalty": <number>,
  "presence_penalty": <number>,
  "response_format": { "type": "json_schema|json_object" }, // 可选
  "stop": [...],            // 可选
  "seed": <number>,         // 可选
  "reasoning_effort": "<low|medium|high...>", // 可选
  "verbosity": "<...>",     // 可选
  "messages": [ /* convertToOpenAICompatibleChatMessages(prompt) */ ],
  "tools": [ /* function tools */ ],
  "tool_choice": <...>,
  "thinking_budget": <number> // 可选
}
  • 流式doStream...:308-314)追加:"stream": true,并在 includeUsage 时加 "stream_options": { "include_usage": true }
  • messages 为 OpenAI 兼容结构:system / user / assistant(含 tool_calls)/ tool(工具结果)等 role,content 可为字符串或 text/image_url 分块。
  • _noop 工具占位request.ts:159-175):当当前无启用工具但历史里存在工具调用时,Copilot 要求 body 里必须带 tools 字段,故注入一个「永不调用」的占位工具。

4.3 Responses body — POST {base}/responsesopenai-responses-language-model.ts

GPT-5+(除 mini)默认走此端点,body 关键字段:modelinput(由 convert-to-openai-responses-input.ts 生成)、instructions(系统提示,:287)、toolsstore(默认 true:206)、include(如 reasoning.encrypted_content:214)、reasoning: { effort, summary }:297)、以及 max_output_tokens/temperature/top_p 等标准项;流式同样 POST /responses:396:782)。

4.4 Messages body — POST {base}/v1/messages@ai-sdk/anthropic

supported_endpoints/v1/messages 的模型(models.ts:92),使用 Anthropic Messages 协议格式(systemmessagestoolsmax_tokensthinking 等),并按 4.1 关闭 toolStreaming


5. 鉴权机制小结

provider 凭据获取 注入位置 是否刷新
github-copilot GitHub OAuth 设备码流程scope: read:user),token 直接复用 自定义 fetch:Authorization: Bearer ${info.refresh}copilot.ts:164 (无 refresh 实现,expires:0 不触发换取)

对比参照(同仓其它 provider 均实现了刷新):openai/codex.ts:361xai.ts:492snowflake-cortex.ts:443。这反衬出 Copilot「直接复用 GitHub token」的设计是有意为之。


6. 关键代码位置索引

  • 认证/Token/Header/自定义 fetch:packages/opencode/src/plugin/github-copilot/copilot.ts:9,21-28,70-79,96-181,222-336,340-354,360-412
  • 模型清单/端点判定:packages/opencode/src/plugin/github-copilot/models.ts:92-111,213-226
  • OAuth 存储:packages/opencode/src/provider/auth.ts:188-221packages/opencode/src/auth/index.ts:14-21,73-81
  • Loader 装配/端点注册/超时:packages/opencode/src/provider/provider.ts:225-239,1543-1562(+ 35,208 的 OpenAI 超时对比)
  • 请求编排/基础 Header/params/_nooppackages/opencode/src/session/llm/request.ts:18,123-146,159-175,181-205
  • Provider 装配(v2):packages/core/src/plugin/provider/github-copilot.ts:20-49
  • OpenAI 兼容 Provider:packages/core/src/github-copilot/copilot-provider.ts:52-97
  • Chat body/发送:packages/core/src/github-copilot/chat/openai-compatible-chat-language-model.ts:87-205,305-329
  • Responses body/发送:packages/core/src/github-copilot/responses/openai-responses-language-model.ts:206-297,396,782

本报告为只读分析,未修改任何业务代码。分支:nickhou1983-copilot-auth-headers-body,分析基线合并提交 0e58547962

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions