Skip to content

[opencode 请求流程分析] OpenCode → GitHub Copilot 请求分析:身份验证/Token 交换、Request Header 与 Request Body(2026-08-10) #29

Description

@nickhou1983

OpenCode → GitHub Copilot 请求分析:身份验证/Token 交换、Request Header 与 Request Body(2026-08-10)

分析对象:nickhou1983/opencode(fork,分支 dev),已与上游 anomalyco/opencode 同步。
本报告聚焦 GitHub Copilot provider 的请求处理链路,以仓库实际代码为准。

0. 上游同步状态与本次变更提示

  • 同步检测(GitHub MCP):上游 anomalyco/opencode:dev 最新提交为 0bff28de09(fix(stats): fall back after full sync failure,2026-08-09)。fork 的 dev HEAD 为 4e61168ed1(每日合并工作流产生的 Merge branch 'anomalyco:dev' into dev),其父提交即 0bff28de09
  • 结论:无需同步git log dev..upstream/dev 为空,upstream/dev 已是本地 dev 的祖先——上游更新已被每日合并工作流并入,fork 处于最新状态。
  • 变更提示:最近同步进来的提交(至 0bff28de09)集中在 app / tui / i18n / stats / desktop 与消息排序修复,未触及 GitHub Copilot 的鉴权 / 请求头 / 请求体代码路径。因此本次 Copilot 请求流程与上一轮分析一致,无行为变化。

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

GitHub Copilot 的鉴权由插件 CopilotAuthPlugin 实现,采用 GitHub OAuth 设备码(Device Flow)

关键常量与端点(packages/opencode/src/plugin/github-copilot/copilot.ts)

  • CLIENT_ID = "Ov23li8tweQw6odWQebz"(opencode 自有 GitHub OAuth App) — L9
  • API_VERSION = "2026-06-01" — L10
  • 设备码端点 https://{domain}/login/device/code;换取 token 端点 https://{domain}/login/oauth/access_token — L19–24
  • API Base:公有云 https://api.githubcopilot.com;企业版 https://copilot-api.{enterpriseDomain}base() L26–28

流程步骤

  1. 选择部署类型:交互式提示 github.comGitHub Enterprise(企业版需填 enterpriseUrl) — L186–221。
  2. 发起设备授权:POST /login/device/code,body={ client_id, scope: "read:user" },得到 device_code / user_code / verification_uri / interval — L234–256。
  3. 展示 user_code,提示用户在浏览器完成授权 — L258–261。
  4. 轮询换 token:循环 POST /login/oauth/access_token,body={ client_id, device_code, grant_type: "urn:ietf:params:oauth:grant-type:device_code" };处理 authorization_pending / slow_down(遵循 RFC 8628,退避 + 3s 安全余量 OAUTH_POLLING_SAFETY_MARGIN_MS) — L262–334。
  5. 持久化凭据:成功后写入 auth.json,结构为 Oauth { type:"oauth", refresh, access, expires, enterpriseUrl? };此处 refreshaccess 都被设为同一个 GitHub OAuth access_token,expires: 0 — copilot.ts L286–305;schema 见 packages/opencode/src/auth/index.ts L14–21。

关键结论:没有独立的 Copilot Token 交换

  • 代码库中 不存在 api.github.com/copilot_internal/v2/token 这类“GitHub OAuth token → 短期 Copilot token”的二次交换(全库检索 copilot_internal / /v2/token 无命中)。
  • OpenCode 将设备码流程拿到的 GitHub OAuth token(gho_…)直接作为 Authorization: Bearer 打到 api.githubcopilot.com
  • expires: 0 且插件 未注册 OAuth refresh 处理器(不同于 xai / codex / snowflake 等在 loader 里做刷新);因 GitHub OAuth token 默认长期有效,每次请求由 loader.fetch 直接读取 access 使用。
  • 这与官方 Copilot 客户端(先换取 tid=… 短期 token)不同,是 OpenCode 的一个显著实现特征。

每次请求的凭据注入(auth.loader → 返回 { apiKey: "", fetch },copilot.ts L96–181)

  • apiKey 返回空串(配合 OAUTH_DUMMY_KEY,auth/index.ts:8),真正的凭据由自定义 fetch 注入。
  • 自定义 fetch 设置 Authorization: Bearer <access>,并 delete headers["authorization"] / delete headers["x-api-key"] 清掉 AI SDK 依据空 apiKey 写入的小写重复头 — L164、L172–173。
sequenceDiagram
    participant U as 用户
    participant OC as OpenCode(CopilotAuthPlugin)
    participant GH as github.com(OAuth)
    participant API 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 轮询(interval + 3s;处理 pending/slow_down)
        OC->>GH: POST /login/oauth/access_token {client_id, device_code, grant_type:device_code}
        GH-->>OC: access_token(或 authorization_pending)
    end
    OC->>OC: 写 auth.json {refresh=access=access_token, expires:0}
    Note over OC,API: 无 copilot_internal/v2/token 二次交换
    OC->>API: 请求携带 Authorization: Bearer <access_token>
    API-->>OC: 响应(SSE 流)
Loading

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

请求头由两处叠加构成:(a) chat.headers 钩子(copilot.ts L360–412),(b) auth.loader 的自定义 fetch(copilot.ts L160–170)。fetch 中的 User-Agent / Authorization / Openai-Intent 会覆盖前面 init.headers 的同名项。

2.1 聊天/推理请求头

Header 取值 来源(代码位置) 作用 / 说明
Authorization Bearer <GitHub OAuth access_token> copilot.ts L164 主鉴权头;直接用 OAuth token(无二次交换)
User-Agent opencode/<InstallationVersion> copilot.ts L163 客户端标识
Openai-Intent conversation-edits copilot.ts L165 Copilot 侧意图标记
x-initiator agentuser copilot.ts L161(基于 body)、L394/L411(compaction/子代理强制 agent) 区分“用户发起”/“代理发起”,用于遥测与计费口径
X-GitHub-Api-Version 2026-06-01 copilot.ts L363(API_VERSION L10) Copilot API 版本
Copilot-Vision-Request true(仅含图片时) copilot.ts L168–170 视觉/多模态请求标记
X-Interaction-Type agent-session-name-generation(仅 title 代理) copilot.ts L364–366 标题生成这类工具型交互标记
anthropic-beta interleaved-thinking-2025-05-14(仅 @ai-sdk/anthropic,即 Claude 走 /v1/messages) copilot.ts L368–370 开启 Claude 交错思考 beta
Content-Type application/json AI SDK(postJsonToApi) JSON 请求体

x-initiator 的取值:loader.fetch 解析 body,若最后一条消息不是“纯 user 文本”(如 assistant / tool_result / 合成图片消息)则判为 isAgent=trueagent,否则 user(copilot.ts L107–158)。chat.headers 钩子对含 compaction 部件的消息或存在 parentID 的子代理会话强制置为 agent(L385–411)。

2.2 模型发现(GET {base}/models)请求头

Header 取值 来源 说明
Authorization Bearer <access> copilot.ts L74 同一 OAuth token
User-Agent opencode/<InstallationVersion> copilot.ts L75
X-GitHub-Api-Version 2026-06-01 copilot.ts L76

拉取端点 GET {base}/models,5s 超时,解析出可选模型与 model_picker_enabled 集合(packages/opencode/src/plugin/github-copilot/models.ts L213–255)。

2.3 注意:未发送的官方头

OpenCode 未发送 官方 Copilot 客户端常见的 Copilot-Integration-IdEditor-VersionEditor-Plugin-VersionCopilot-Language-Server-Version 等头(全库检索无命中);它以 User-Agent: opencode/<version> + Openai-Intent + X-GitHub-Api-Version + x-initiator 作为标识。


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

Copilot 模型按 /models 返回的 supported_endpoints 被路由到 三种端点/协议,请求体格式各异(models.ts L92–110):

  • messagesPOST {base}/v1/messages,npm=@ai-sdk/anthropic(Claude 系,Anthropic Messages 格式)
  • responsesPOST {base}/responses,npm=@ai-sdk/github-copilot(OpenAI Responses 格式)
  • chatPOST {base}/chat/completions,npm=@ai-sdk/github-copilot(OpenAI Chat Completions 格式)

v2/core 侧路由:GPT‑5+(非 gpt-5-mini)默认走 Responses,其余走 Chat(packages/core/src/plugin/provider/github-copilot.ts L27–50)。

3.1 Chat Completions 请求体(/chat/completions)

字段构造见 packages/core/src/github-copilot/chat/openai-compatible-chat-language-model.ts getArgs L139–189:

  • model:模型 api.id
  • messages:convertToOpenAICompatibleChatMessages(prompt)(L179)
  • max_tokens:maxOutputTokens(L148)——注意:GPT 系被钩子置空(见 3.4)
  • temperature / top_p / frequency_penalty / presence_penalty / stop / seed(L149–168)
  • reasoning_effort(L175)、verbosity(L176)、thinking_budget(L186)
  • response_format:JSON/结构化输出时(L153–165)
  • tools / tool_choice:由 prepareTools 生成(L182–183)
  • user(L145)
  • 流式(doStream)追加 stream: true,可选 stream_options: { include_usage: true }(L306–313)

3.2 Responses 请求体(/responses)

packages/core/src/github-copilot/responses/openai-responses-language-model.ts getArgs L152–390:

  • modelinput(由 convert-to-openai-responses-input 生成)
  • max_output_tokens(L258)
  • store:默认 true(L206;顶层组装 L285)
  • instructions(L287)、include(L289,常含 reasoning.encrypted_content)
  • reasoning: { effort, summary }:含 reasoningEffort / reasoningSummary 时注入(L296–303)
  • 推理模型会移除不支持的 temperature / top_p(L312–344)
  • tools / tool_choice(L373–386)
  • 流式追加 stream: true(L786–788)

3.3 Messages 请求体(/v1/messages,Claude)

Claude 系模型走 @ai-sdk/anthropic,请求体为 Anthropic Messages 格式(model / messages / system / max_tokens / tools / thinking 等),并叠加 anthropic-beta: interleaved-thinking-2025-05-14 头。

3.4 opencode 对请求体的定制(钩子 + transform)

  • chat.params 钩子(copilot.ts L340–354):
    • GPT 系模型 maxOutputTokens = undefined(对齐 GitHub Copilot CLI,省略 max_tokens)— L344–346
    • @ai-sdk/anthropic(Claude via messages)options.toolStreaming = false(规避 Copilot /v1/messages shim 拒绝 eager_input_streaming)— L351–353
  • 推理变体注入 providerOptions(packages/opencode/src/provider/transform.ts):
    • sdkKey("@ai-sdk/github-copilot") = "copilot"(providerOptions 命名空间键)— L42–45
    • GPT 系:注入 { reasoningEffort, reasoningSummary:"auto", include: <encrypted reasoning> };gpt-5.1-codex-max/5.2/5.3 追加 xhigh — L889–913;单值映射 L1746–1750
    • Claude via Copilot:仅 { reasoningEffort },且限制为 low/medium/high,opus-4.7 固定 medium — L976–1000、L1746–1749
    • Gemini via Copilot:不注入 reasoning 变体 — L890–893、L1748

4. 关键代码位置索引

  • packages/opencode/src/plugin/github-copilot/copilot.ts
    • 常量/端点/Base:L9–28;设备码鉴权:L182–338;凭据持久化:L286–305
    • auth.loader + 自定义 fetch(注入 Header/Bearer、isAgent/isVision 判定):L96–181
    • chat.params(请求体定制):L340–354;chat.headers(请求头):L360–412
    • provider.models(模型发现头):L60–92
  • packages/opencode/src/auth/index.ts:OAUTH_DUMMY_KEY L8;Oauth schema L14–21
  • packages/opencode/src/plugin/github-copilot/models.ts:端点路由 L92–110;GET /models L213–255
  • packages/core/src/plugin/provider/github-copilot.ts:v2 端点(chat/responses)选择 L27–50
  • packages/core/src/github-copilot/chat/openai-compatible-chat-language-model.ts:Chat 请求体 L139–189、流式 L306–313
  • packages/core/src/github-copilot/responses/openai-responses-language-model.ts:Responses 请求体 L152–390、流式 L786–788
  • packages/opencode/src/provider/transform.ts:sdkKey L42–60;Copilot 推理变体 L889–913、L976–1000、L1746–1750

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions