Skip to content

[opencode 请求流程分析] OpenCode 向 GitHub Copilot 发送请求的流程与请求头分析(2026-07-19) #9

Description

@nickhou1983

OpenCode 向 GitHub Copilot 发送请求的流程与请求头分析(2026-07-19)

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

0. 本次上游同步说明

本次运行检测到上游 anomalyco/opencodedev 领先本 fork 8 个提交,已合并同步到 origin/dev(合并提交 317044017e,无冲突)。同步进来的提交如下:

SHA 提交
b8142c7aa8 chore: update nix node_modules hashes
ba3f966ed0 build: bump opentui to 0.4.5 (anomalyco#37616)
b95fe7b2d7 fix(nix): restore desktop integration (anomalyco#37197)
9da9ed9de6 fix(nix): relax Bun version check for desktop build (anomalyco#36767)
fab2133129 chore: configure trust center domain (anomalyco#36910)
901c9e7329 fix(app): disable unavailable file navigation (anomalyco#37595)
3476e6baa5 chore: update nix node_modules hashes
86e04d4174 revert(tui): downgrade opentui to 0.4.3 (anomalyco#37582)

对请求流程/Header 的影响:无。 这些提交集中在 nix 打包、opentui/tui 版本、desktop、app 导航与信任中心域名配置,均未触及 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/session/llm/request.ts 组装 system/messages/tools/params,触发 hooks,生成基础 Header
Provider 装配 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 构造与请求发送
消息转换 .../chat/convert-to-openai-compatible-chat-messages.ts.../responses/convert-to-openai-responses-input.ts LanguageModelV3 prompt → 端点 body 的 messages/input

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-48)。


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.tsxai.ts 都有 refresh)。
  • 实际用作 Bearer 的字段是 refresh(长期 GitHub token),而非 access
    • 模型清单请求:Authorization: \Bearer ${auth.refresh}`copilot.ts:74`)
    • 聊天/推理请求:Authorization: \Bearer ${info.refresh}`copilot.ts:164`)

2.2 客户端标识

  • OAuth App client_id = "Ov23li8tweQw6odWQebz"copilot.ts:9
  • API 版本 X-GitHub-Api-Version = "2026-06-01"copilot.ts:10
  • 域名:默认 github.com;企业版 normalizeDomain(enterpriseUrl),API base 变为 https://copilot-api.<domain>copilot.ts:26-28

2.3 设备码登录(authorize,copilot.ts:222-336

  1. 发起设备码POST https://<domain>/login/device/codecopilot.ts:234-245
    { "client_id": "Ov23li8tweQw6odWQebz", "scope": "read:user" }
    Header:Accept: application/jsonContent-Type: application/jsonUser-Agent: opencode/<version>
    返回 verification_uriuser_codedevice_codeinterval
  2. 提示用户:显示 verification_uriEnter code: <user_code>copilot.ts:258-261)。
  3. 轮询换取 tokenPOST https://<domain>/login/oauth/access_tokencopilot.ts:264-276
    {
      "client_id": "Ov23li8tweQw6odWQebz",
      "device_code": "<device_code>",
      "grant_type": "urn:ietf:params:oauth:grant-type:device_code"
    }
    • authorization_pending → 按 interval 轮询(含 3s 安全余量 OAUTH_POLLING_SAFETY_MARGIN_MScopilot.ts:308-311
    • slow_down → 依 RFC 8628 §3.5,间隔 +5s(或用服务端返回的 interval)后重试(copilot.ts:313-327
    • 其它 error → 返回 failedcopilot.ts:329
  4. 持久化凭据copilot.ts:286-306):
    { type: "success", refresh: access_token, access: access_token, expires: 0, enterpriseUrl? }
    accessrefresh 都被赋成同一个 GitHub token;expires: 0 表示不做基于过期时间的缓存/刷新,请求时始终取长期的 refresh 字段作为 Bearer。

2.4 凭据如何注入到每次请求

auth.loader(getAuth) 返回一个自定义 fetchcopilot.ts:96-181)。AI SDK 兼容层最终用这个 fetch 发请求(copilot-provider.ts:73fetch: options.fetch),因此凭据/Header 在这里统一注入。loader 同时返回 apiKey: ""copilot.ts:101),避免 SDK 用默认 Authorization/x-api-key(随后也会被显式删除,见 §3)。

2.5 鉴权时序图

sequenceDiagram
    participant U as 用户
    participant OC as OpenCode CLI
    participant GH as github.com (OAuth)
    participant CAPI as api.githubcopilot.com

    U->>OC: opencode 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 轮询直到授权
        OC->>GH: POST /login/oauth/access_token {client_id, device_code, grant_type}
        GH-->>OC: authorization_pending / slow_down / access_token
    end
    GH-->>OC: access_token (GitHub OAuth token)
    Note over OC: 存储 {refresh=access=token, expires:0}<br/>无二次 token 交换
    OC->>CAPI: GET /models (Authorization: Bearer <token>)
    CAPI-->>OC: 可用模型清单
    Note over OC,CAPI: 后续每次对话请求都用<br/>同一个 GitHub token 作 Bearer
Loading

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

Header 由 三处叠加而成(后者覆盖前者):

  1. request.ts 基础 Headerprepare(),非 opencode provider 分支,request.ts:196-204
  2. chat.headers hook(Copilot 插件,copilot.ts:360-412
  3. 自定义 fetch 强制层(Copilot 插件,copilot.ts:160-173)——最终决定权在此,强制写入鉴权类 Header 并删除 SDK 默认鉴权头。

3.1 最终发往 api.githubcopilot.com 的 Header 明细

Header 取值 来源(代码位置) 作用 / 说明
Authorization Bearer <GitHub OAuth token> copilot.ts:164(取 info.refresh 强制。鉴权凭据,直接用 GitHub token
User-Agent opencode/<InstallationVersion> copilot.ts:163(覆盖 SDK 追加的 UA 后缀) 强制。客户端标识
Openai-Intent conversation-edits copilot.ts:165 强制。声明请求意图,Copilot 后端据此计费/路由
x-initiator agentuser copilot.ts:161(按 body 启发式)/ copilot.ts:394,411(hook 对 compaction、子代理强制 agent 标识本轮由 agent 还是用户发起
X-GitHub-Api-Version 2026-06-01 copilot.ts:363chat.headers 固定 API 版本
Copilot-Vision-Request true copilot.ts:168-170 仅当请求含图片时。开启视觉请求
X-Interaction-Type agent-session-name-generation copilot.ts:364-366 仅当 title 代理(生成会话标题)时
anthropic-beta interleaved-thinking-2025-05-14 copilot.ts:368-370 仅当模型 npm 为 @ai-sdk/anthropic(即 /v1/messages 端点)时
x-session-affinity <sessionID> request.ts:197 会话亲和/路由
X-Session-Id <sessionID> request.ts:198 会话标识
x-parent-session-id <parentSessionID> request.ts:199 仅子代理会话存在时
Content-Type application/json AI SDK postJsonToApi 默认 JSON 请求体
x-api-key (删除) copilot.ts:172 移除 SDK 默认头,避免与 Authorization 冲突
authorization(小写) (删除) copilot.ts:173 同上,去重

chat.params hook 还会影响 body 而非 Header:GPT 模型省略 maxOutputTokenscopilot.ts:344-346);@ai-sdk/anthropic 模型关闭 toolStreaming,避免 /v1/messages shim 拒绝 eager_input_streaming 字段(copilot.ts:351-353)。

3.2 模型清单请求(GET /models)的 Header

copilot.ts:70-79Authorization: Bearer <auth.refresh>User-Agent: opencode/<version>X-GitHub-Api-Version: 2026-06-01,外加 provider.options.headers

3.3 x-initiator 判定逻辑(body 启发式)

自定义 fetch 解析 init.body,按端点判断「最后一条消息是否由用户直接发出」(copilot.ts:107-158):

  • Completionsmessages 最后一条 role !== "user" 或为合成附件消息 → agent
  • Responsesinput 最后一项同理
  • Messages(Anthropic):最后一条非「纯 user 且含非 tool_result 内容」→ agent
  • 任一端点消息里含图片(image_url / input_image / image)→ isVision = true → 加 Copilot-Vision-Request

此外 chat.headers hook 会在自动压缩续写(compaction)或子代理会话时把 x-initiator 强制为 agentcopilot.ts:385-411)。


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

Body 由 AI SDK 语言模型的 getArgs() 构造,再 JSON.stringify 发送。三种端点结构不同。

4.1 Chat Completions(POST /chat/completions

Body 组装见 openai-compatible-chat-language-model.ts:139-190doStream 额外加流式字段(:306-320):

字段 来源 说明
model this.modelId 模型 API id
messages convertToOpenAICompatibleChatMessages(prompt) 见 §4.4
max_tokens maxOutputTokens GPT 模型会被 chat.params 置空而省略
temperature / top_p params 采样参数
frequency_penalty / presence_penalty / stop / seed 调用选项 可选
tools / tool_choice prepareTools(...) 工具定义与选择策略
reasoning_effort / verbosity / thinking_budget provider options(copilot 推理相关
response_format responseFormat json_schema / json_object(仅结构化输出模型)
stream truedoStream:310 流式
stream_options includeUsage ? {include_usage:true} : undefined:313 Copilot 未设 includeUsage,故 省略

请求发送:postJsonToApi({ url: <base>/chat/completions, headers: combineHeaders(config.headers(), options.headers), body: args, fetch: config.fetch }):201-212)。

4.2 Responses(POST /responses

Body 组装见 openai-responses-language-model.ts:253-309doStreamstream: true:786-793):

字段 来源 说明
model this.modelId
input convertToOpenAIResponsesInput(...) Responses 输入项数组(见 §4.4)
temperature / top_p params 推理模型会被置空并告警(:311-330
max_output_tokens maxOutputTokens
reasoning {effort, summary} 仅推理模型且设置了 effort/summary 时
include 动态 reasoning.encrypted_content(由模型 variants 决定)、web_search sources、logprobs 等
text {format, verbosity} JSON schema / 详尽度
store openaiOptions.store ?? true(转换时默认 true:206 是否服务端存储
metadata / parallel_tool_calls / previous_response_id / max_tool_calls / service_tier / truncation provider options 可选
stream truedoStream:788 流式

请求发送:url: config.url({ path: "/responses" })fetch: config.fetch:394-396:781-793)。

4.3 Messages(POST /v1/messages,Anthropic 家族)

由外部包 @ai-sdk/anthropic 构造 body(modelsystemmessagestoolsmax_tokensthinkingstream 等)。OpenCode 侧的定制:

  • chat.params 关闭 toolStreaming(不注入 eager_input_streamingcopilot.ts:351-353);
  • chat.headersanthropic-beta: interleaved-thinking-2025-05-14copilot.ts:368-370);
  • models.ts:109-110 将该类模型 api.url 设为 <base>/v1,故最终路径为 /v1/messages

4.4 消息转换(body.messages / body.input 的形状)

Chatconvert-to-openai-compatible-chat-messages.ts):

  • system{role:"system", content}:18-25
  • user → 纯文本折叠为字符串;多模态为数组,图片转 {type:"image_url", image_url:{url}}data: base64 或 URL,:45-58
  • assistantcontent + 可选 tool_calls:[{id,type:"function",function:{name,arguments:JSON.stringify(input)}}] + reasoning_text/reasoning_opaque:73-123
  • tool{role:"tool", tool_call_id, content},其中 content 依 output 类型序列化(:128-158

Responsesconvert-to-openai-responses-input.ts):文本 → input_text;图片 → input_imageimage_url);文件 → input_filefile_url)(:83-95)。

4.5 Copilot 专属 body 兜底

当 Copilot 请求「重放历史工具调用」但当前无启用工具时,request.ts:159-175 会注入一个 _noop 占位工具(tools._noop),因为 Copilot 在存在历史 tool_call 时要求 body 必须带 tools 字段。


5. 完整请求时序(会话 → HTTP)

sequenceDiagram
    participant S as Session/Prompt
    participant P as LLMRequestPrep.prepare (request.ts)
    participant H as Copilot 插件 hooks (copilot.ts)
    participant SDK as AI SDK 语言模型 (core/github-copilot)
    participant F as 自定义 fetch (copilot.ts loader)
    participant C as api.githubcopilot.com

    S->>P: model/agent/messages/tools
    P->>P: 组 system+messages, resolveTools, (Copilot 注入 _noop)
    P->>H: trigger chat.params (GPT 省略 maxTokens; anthropic 关 toolStreaming)
    P->>H: trigger chat.headers (X-GitHub-Api-Version, x-initiator, ...)
    P-->>SDK: {messages, tools, params, headers}
    SDK->>SDK: getArgs() 构造 body (/chat/completions 或 /responses)
    SDK->>F: postJsonToApi(url, combineHeaders, body, fetch=config.fetch)
    F->>F: 解析 body 判定 isAgent/isVision
    F->>F: 强制 Authorization/User-Agent/Openai-Intent, 删除 x-api-key/authorization
    F->>C: POST body (stream:true)
    C-->>F: SSE 流
    F-->>SDK: Response
    SDK-->>S: 归一化为 LLMEvent (ai-sdk.ts)
Loading

6. 关键代码位置索引

  • 认证插件:packages/opencode/src/plugin/github-copilot/copilot.ts
    • 常量::9(CLIENT_ID)、:10(API_VERSION)、:26-28(base URL)
    • 设备码流程::222-336(authorize / 轮询 / 持久化)
    • 自定义 fetch 注入 Header::96-181:160-173 强制层)
    • provider.models 拉清单 Header::70-79
    • chat.params:340-354chat.headers:360-412
  • 模型/端点:packages/opencode/src/plugin/github-copilot/models.ts:92-111,218
  • 请求编排:packages/opencode/src/session/llm/request.ts:56-206(Header :187-204,_noop :159-175
  • Provider 装配:packages/core/src/plugin/provider/github-copilot.ts:20-52
  • 兼容 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-212,306-320
  • Responses body:packages/core/src/github-copilot/responses/openai-responses-language-model.ts:152-309,394-396,778-793
  • 消息转换:.../chat/convert-to-openai-compatible-chat-messages.ts.../responses/convert-to-openai-responses-input.ts:83-95
  • 事件归一化:packages/opencode/src/session/llm/ai-sdk.ts

本报告由每日「上游同步 + 请求流程分析」定时任务自动生成。仅只读分析,未修改任何业务代码。

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions