Skip to content

OpenCode 请求流程与请求 Header/Body 分析:GitHub Copilot(2026-07-14) #3

Description

@nickhou1983

本报告分析 opencode 向 GitHub Copilot 发起请求的完整流程,重点覆盖用户关注的三点:GitHub 身份验证与 Token 交换流程、客户端构造的 Request Header、客户端构造的 Request Body。分析基于当前 dev(已与上游 anomalyco/opencode 完成同步)的实际代码,未做任何代码修改。

0. 本次同步与变更提示

本次运行检测到上游 anomalyco/opencode 有 2 个新提交,已合并同步到本 fork 的 dev(合并提交 ed86694d6,同步后落后上游 0 个提交):

SHA 提交
15046658b feat(app): update tabs intro content (anomalyco#36701)
cb8be9ba1 chore: generate(生成物)

对 Copilot 请求流程的影响:这两个提交仅涉及 packages/app 的帮助按钮 UI(help-button.tsx)、若干帮助资源(home.png / tabs.png / introducing-tabs.mp4)与生成物,完全未触及 GitHub Copilot 的鉴权 / Header / Body 构造逻辑(packages/opencode/src/plugin/github-copilot/、packages/core/src/github-copilot/、packages/opencode/src/session/llm/)。因此本报告结论在同步前后一致。


1. 请求流程(时序)

GitHub Copilot 在 opencode 中以 插件(plugin) 形式实现,核心分两块:

  • 鉴权 + Header 注入:packages/opencode/src/plugin/github-copilot/copilot.ts
  • 协议 / Body 构造:packages/core/src/github-copilot/(chat/ 与 responses/ 两套语言模型实现,外加 Anthropic Messages shim)

会话层 packages/opencode/src/session/llm/request.ts 组织通用请求参数并触发 chat.params、chat.headers 两个钩子;随后由 AI SDK 语言模型(vendored 的 OpenAI 兼容实现)构造 JSON Body;最终由鉴权 loader 返回的自定义 fetch 完成 Header 收尾与 Token 注入并发出 HTTP 请求。

@ai-sdk/github-copilot 这个 npm 标识实际被映射到 仓库内 vendored 实现(不是外部包):packages/opencode/src/provider/provider.ts:131-132 → @opencode-ai/core/github-copilot/copilot-provider 的 createOpenaiCompatible。

sequenceDiagram
    participant S as Session / Prompt
    participant R as session/llm/request.ts
    participant P as copilot.ts (plugin hooks)
    participant LM as core/github-copilot LanguageModel
    participant F as Auth loader 自定义 fetch
    participant GH as GitHub Copilot API

    S->>R: 发起一次模型调用
    R->>R: 组装基础 Header (X-Session-Id / User-Agent ...)
    R->>P: trigger("chat.params")
    P-->>R: 调整 body 参数 (gpt→去 maxOutputTokens; anthropic→toolStreaming=false)
    R->>P: trigger("chat.headers")
    P-->>R: 追加 X-GitHub-Api-Version / X-Interaction-Type / anthropic-beta / x-initiator
    R->>LM: 交给对应 endpoint 的 LanguageModel
    LM->>LM: getArgs() 构造 JSON Body
    LM->>F: postJsonToApi(url, combineHeaders(...), body)
    F->>F: 解析 body 判定 isAgent / isVision
    F->>F: 注入 Authorization: Bearer <token> / Openai-Intent / Copilot-Vision-Request
    F->>F: 删除 x-api-key / 小写 authorization
    F->>GH: POST /chat/completions | /responses | /v1/messages
    GH-->>F: SSE / JSON 响应
    F-->>S: 流式回传
Loading

关键环节与代码位置

  1. Provider/Model 解析与 endpoint 选择
    • packages/opencode/src/plugin/github-copilot/models.ts:82-202(build):依据 /models 返回的 supported_endpoints 判定 messages / responses / chat,并设置 api.npm(@ai-sdk/anthropic 或 @ai-sdk/github-copilot)、api.url(Messages 走 {base}/v1)。
    • packages/core/src/github-copilot/chat/openai-compatible-chat-options.ts:19-25(shouldUseResponsesApi):gpt-N(N≥5 且非 gpt-5-mini)走 Responses,否则 Chat Completions。
    • packages/core/src/plugin/provider/github-copilot.ts:20-50:把 @ai-sdk/github-copilot 绑定到 vendored createOpenaiCompatible,并按 endpoint/模型 id 选择 sdk.responses 或 sdk.chat。
  2. 鉴权凭据获取:copilot.ts:96-181(auth.loader)。
  3. 请求参数 / Header 钩子:request.ts:114-146(触发 chat.params / chat.headers)+ copilot.ts:340-412。
  4. Body 构造:.../chat/openai-compatible-chat-language-model.ts:87-190(getArgs)、.../responses/openai-responses-language-model.ts:253-396。
  5. 传输发送 + Token 注入:copilot.ts:102-178(自定义 fetch);provider 层把 loader 产出的 {apiKey, fetch} 注入模型 options(provider.ts:1541-1560)。

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

实现位置:packages/opencode/src/plugin/github-copilot/copilot.ts。采用 GitHub OAuth 2.0 Device Authorization Grant(设备码流程,RFC 8628)。

关键常量与基址

  • CLIENT_ID = "Ov23li8tweQw6odWQebz"(copilot.ts:9)
  • API_VERSION = "2026-06-01"(copilot.ts:10)
  • 轮询安全余量 OAUTH_POLLING_SAFETY_MARGIN_MS = 3000(copilot.ts:14)
  • API Base(copilot.ts:26-28):企业版 https://copilot-api.{domain},公有云 https://api.githubcopilot.com。
  • 鉴权域名:公有云 github.com;企业版取用户输入的 enterpriseUrl(copilot.ts:222-232)。

流程步骤

  1. 请求设备码(copilot.ts:234-256)
    POST https://{domain}/login/device/code
    Header:Accept: application/json、Content-Type: application/json、User-Agent: opencode/{version}
    Body:{ "client_id": "Ov23li8tweQw6odWQebz", "scope": "read:user" }
    返回 verification_uri、user_code、device_code、interval。用户在浏览器输入 user_code 完成授权。

  2. 轮询交换 Access Token(copilot.ts:262-333)
    POST https://{domain}/login/oauth/access_token
    Body:{ "client_id": "...", "device_code": "...", "grant_type": "urn:ietf:params:oauth:grant-type:device_code" }

    • error === "authorization_pending" → 按 interval + 安全余量 等待后重试;
    • error === "slow_down" → 依 RFC 8628 将间隔 +5s(或采用服务端返回的新间隔)后重试;
    • 拿到 access_token → 返回成功。
  3. 持久化(copilot.ts:286-306 + provider/auth.ts:211-220)

    { type: "oauth", refresh: access_token, access: access_token, expires: 0 }
    

    企业版额外记录 enterpriseUrl。

关键结论(Token 交换):opencode 没有把 GitHub OAuth Token 再交换成短时效的「Copilot Token」,也没有任何 copilot_internal/v2/token 调用(全仓库源码中不存在该端点)。它把 GitHub OAuth Access Token 直接当作 Authorization: Bearer <token> 发往 api.githubcopilot.com;expires: 0 按「永不过期」处理。

佐证:

  • vendored provider packages/core/src/github-copilot/copilot-provider.ts:60-64 仅在 options.apiKey 存在时才写默认 Authorization;而 auth loader 返回 apiKey: ""(copilot.ts:101)。
  • 因此真正的 Authorization 由 loader 的自定义 fetch 注入(copilot.ts:164),Token 取自 getAuth() 的 access 字段。
  • 与之对照:plugin/openai/codex.ts:361、plugin/xai.ts:492、plugin/snowflake-cortex.ts:300 的 loader 都带 expires/refresh 续期逻辑,而 Copilot loader 完全没有续期/交换步骤——直接用 access。
  1. 凭据注入到每个请求:auth loader(copilot.ts:96-181)返回一个自定义 fetch,在每次真正发出请求前用最新的 getAuth() 结果注入 Authorization,并做 Header 收尾(见第 3 节)。这个 fetch 经由 provider.ts:1551-1556 挂到 github-copilot provider 的 options 上,被 vendored 语言模型的 postJsonToApi({ fetch }) 使用。

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

Header 分 多层叠加,后写覆盖先写;最终收尾在 auth loader 的自定义 fetch(copilot.ts:160-174)。SDK 侧还会追加一个 User-Agent 后缀(copilot-provider.ts:66 的 withUserAgentSuffix)与 Content-Type: application/json(postJsonToApi),但 User-Agent 最终会被自定义 fetch 覆盖。

3.1 基础层 —— session/llm/request.ts:187-204(非 opencode provider 分支)

Header 取值来源 作用 / 说明
x-session-affinity input.sessionID 会话亲和(路由/粘性)
X-Session-Id input.sessionID 会话标识
x-parent-session-id input.parentSessionID(存在时) 子会话标识父会话
User-Agent opencode/${InstallationVersion}(request.ts:18) 客户端标识(后续会被覆盖为同值)
...input.model.headers 模型级 Header 每模型可附带

3.2 chat.headers 钩子层 —— copilot.ts:360-412

Header 取值来源 作用 / 说明
X-GitHub-Api-Version API_VERSION(2026-06-01) 指定 Copilot API 版本
X-Interaction-Type agent-session-name-generation(当 agent === "title") 标记标题生成类交互
anthropic-beta interleaved-thinking-2025-05-14(当 api.npm === "@ai-sdk/anthropic") Messages shim 的交错思考 beta
x-initiator agent(压缩续写 compaction / 子会话时) 标记本次由 agent 发起

3.3 传输收尾层(最终值)—— auth loader 自定义 fetch copilot.ts:160-174

Header 取值来源 作用 / 说明
Authorization Bearer <GitHub OAuth access_token>(copilot.ts:164) 鉴权:GitHub OAuth Token 直接作为 Bearer
User-Agent opencode/${InstallationVersion}(copilot.ts:163) 覆盖为客户端标识
Openai-Intent 固定 conversation-edits(copilot.ts:165) Copilot 侧意图标记
x-initiator agent / user(copilot.ts:161) 依据 body 判定:末条非 user 消息或合成附件消息 → agent,否则 user(copilot.ts:107-158)
Copilot-Vision-Request true(含图片时,copilot.ts:168-170) 视觉请求标记
(删除)x-api-key copilot.ts:172 移除,避免与 Bearer 冲突
(删除)authorization(小写) copilot.ts:173 移除小写重复项

注:init.headers(含 3.1/3.2 及 Content-Type)会先被展开,再被本层的 User-Agent/Authorization/Openai-Intent 覆盖;x-initiator 因 init.headers 在默认值之后展开,故钩子层给出的值(若有)优先。

3.4 /models 列表接口的 Header —— copilot.ts:70-79(配合 models.ts:218)

Header 取值来源 作用
Authorization Bearer <GitHub OAuth access_token> 拉取可用模型列表鉴权
User-Agent opencode/${InstallationVersion} 客户端标识
X-GitHub-Api-Version API_VERSION API 版本

isAgent / isVision 的判定对三种 Body 格式分别处理(Completions / Responses / Messages),见 copilot.ts:107-158。


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

Copilot 模型按 endpoint 分三套 Body。chat.params 钩子(copilot.ts:340-353)先做统一调整:gpt 系列 去掉 maxOutputTokens;Anthropic(Messages shim) 设 toolStreaming = false(规避 eager_input_streaming 字段被 /v1/messages shim 拒绝)。

4.1 Chat Completions —— POST {base}/chat/completions

构造于 .../chat/openai-compatible-chat-language-model.ts:139-189(getArgs)、:305-314(doStream 追加 stream):

字段 说明
model 模型 id(api.id)
messages convertToOpenAICompatibleChatMessages(prompt) 产出:system / user(content 支持 text 与 image_url,见 convert-to-openai-compatible-chat-messages.ts:38-51)/ assistant(含 tool_calls,及 Copilot 专有 reasoning_text、reasoning_opaque,见 :117-121)/ tool(:153)
max_tokens maxOutputTokens(gpt 系列被钩子置空)
temperature / top_p / frequency_penalty / presence_penalty / stop / seed 标准采样参数
response_format json_schema / json_object(结构化输出时)
reasoning_effort 推理强度
verbosity 文本冗长度
tools / tool_choice 工具定义与选择
thinking_budget 思考预算
user 端用户标识(可选)
stream / stream_options 流式时 stream: true;严格兼容模式附 stream_options.include_usage(:308-314)

补充:当 Copilot 在回放历史工具调用但当前无启用工具时,request.ts:159-175 会注入一个 _noop 占位工具以满足 API 对 tools 字段的要求。

4.2 Responses API —— POST {base}/responses

构造于 .../responses/openai-responses-language-model.ts:253-396(baseArgs + :788 的 stream: true):

字段 说明
model / input 模型 id 与 Responses 输入项数组
temperature / top_p / max_output_tokens 采样与长度(推理模型会移除 temperature/top_p)
text 含 format(json_schema/json_object)与 verbosity
reasoning { effort, summary }(推理模型,:297-302)
include 如 reasoning.encrypted_content、code_interpreter_call.outputs 等
store 默认 true(:206;baseArgs 中透传 openaiOptions?.store)
instructions / user / metadata 指令 / 用户 / 元数据
parallel_tool_calls / max_tool_calls / previous_response_id 工具并行 / 上限 / 续接上一响应
service_tier / prompt_cache_key / safety_identifier / top_logprobs 服务档位 / 缓存键 / 安全标识 / logprobs
truncation auto(:307)
tools / tool_choice 工具定义与选择(:373-387)
stream true(:788)

4.3 Anthropic Messages Shim —— POST {base}/v1/messages

当模型 supported_endpoints 含 /v1/messages 时(models.ts:92-110),api.npm 置为 @ai-sdk/anthropic、api.url 为 {base}/v1,由外部 @ai-sdk/anthropic 走标准 Anthropic Messages Body;配合 anthropic-beta Header 与 toolStreaming=false(copilot.ts:351-353)。鉴权仍复用同一个自定义 fetch,故 Header 收尾与 2/3 节一致。


5. 关键代码位置索引

  • packages/opencode/src/plugin/github-copilot/copilot.ts
    • :9-14 常量(CLIENT_ID / API_VERSION / 安全余量)
    • :26-28 API Base
    • :96-181 auth loader(自定义 fetch + Token 注入 + Header 收尾)
    • :107-158 isAgent/isVision 判定
    • :160-174 最终 Header 构造
    • :222-336 OAuth 设备码授权与轮询交换
    • :340-353 chat.params;:360-412 chat.headers
  • packages/opencode/src/plugin/github-copilot/models.ts:82-256 模型/endpoint 派生与 /models 拉取
  • packages/opencode/src/provider/provider.ts:131-132 @ai-sdk/github-copilot → vendored provider 映射;:1541-1560 loader 产出的 {apiKey, fetch} 注入
  • packages/opencode/src/provider/auth.ts:211-220 OAuth 凭据持久化
  • packages/core/src/github-copilot/copilot-provider.ts:52-97 vendored provider(仅在 apiKey 存在时写默认 Authorization + User-Agent 后缀)
  • packages/core/src/github-copilot/chat/openai-compatible-chat-language-model.ts:87-190,305-329 Chat Body/请求
  • packages/core/src/github-copilot/chat/convert-to-openai-compatible-chat-messages.ts:15-164 消息转换
  • packages/core/src/github-copilot/responses/openai-responses-language-model.ts:253-396,782-790 Responses Body/请求
  • packages/core/src/github-copilot/chat/openai-compatible-chat-options.ts:19-25 endpoint 选择
  • packages/core/src/plugin/provider/github-copilot.ts:20-50 SDK/语言模型绑定
  • packages/opencode/src/session/llm/request.ts:18,114-205 通用参数/Header 组装与钩子触发

只读分析,未修改任何代码。生成于 2026-07-14,基于同步后的 dev(ed86694d6)。

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