Skip to content

[opencode 请求流程分析] OpenCode → GitHub Copilot 请求流程、鉴权/Token 交换与请求 Header/Body 分析(2026-08-19) #40

Description

@nickhou1983

只读分析,基于当前 dev 分支实际代码(已与上游 anomalyco/opencode 同步)。本报告聚焦 GitHub Copilot provider:身份验证与 Token 交换流程、客户端发往 GitHub Copilot 的 Request Header 与 Request Body。

摘要

  • 上游同步检测:上游 anomalyco/opencode 的 dev 最新提交为 8b65fa2ef6(fix(opencode): remove Qwen sampling defaults #43310,2026‑08‑18)。该提交已经是本 fork dev 的祖先(今日 00:43 的自动同步合并 21c291d794 的第二父提交即为它)。git rev-list --left-right --count dev...upstream/dev = 43 0,即本 fork 领先 43 个合并提交、落后 0 个。结论:无新增上游内容需要同步,仓库已是最新。
  • 关键发现(鉴权):本版本 opencode 直接把 GitHub OAuth 设备流令牌当作 Authorization: Bearer 发给 api.githubcopilot.com,仓库内不存在 copilot_internal/v2/token 这类「GitHub token → 短期 Copilot token」的二次交换。
  • 关键发现(Header):opencode 不发送传统的 Copilot-Integration-Id / Editor-Version / Editor-Plugin-Version 等编辑器标识头;它通过 User-Agent: opencode/<版本> + Openai-Intent + x-initiator + X-GitHub-Api-Version 等标识自身。
  • 三种请求端点:依据 /models 返回的 supported_endpoints 路由到 /chat/completions、/responses(GPT‑5 类)或 /v1/messages(Claude,走 @ai-sdk/anthropic)。

本次变更提示(近期同步进来的相关提交)

git log --oneline -20 dev 中与「请求流程 / Header」直接相关的是:

  • 0033bb3559 fix(core): restore session request headers (#43188) —— 在 v2 core runner packages/core/src/session/runner/llm.ts 的 LLM.request({...}) 中恢复注入会话级 HTTP 头:x-session-affinity、X-Session-Id、以及子会话的 x-parent-session-id(用于代理缓存路由亲和)。对 Copilot 等所有走 v2 路径的 provider 均生效。
  • 8b65fa2ef6 fix(opencode): remove Qwen sampling defaults (#43310) —— 与 Copilot Header/Body 无直接关系(Qwen 采样默认值)。

其余为 console/app/provider(Vertex) 等非 Copilot 改动。


一、GitHub 身份验证与 Token 交换流程

实现位置:packages/opencode/src/plugin/github-copilot/copilot.ts

  • OAuth App CLIENT_ID = "Ov23li8tweQw6odWQebz"(copilot.ts:9),API_VERSION = "2026-06-01"(copilot.ts:10)。
  • 采用 OAuth 2.0 设备授权流(Device Flow, RFC 8628),authorize()(copilot.ts:222):
    1. 选择部署类型:GitHub.com 或 GitHub Enterprise(copilot.ts:186-221)。企业版会归一化域名(normalizeDomain, copilot.ts:15),端点改为 https://<domain>/...(getUrls, copilot.ts:19)。
    2. POST https://github.com/login/device/code,体 { client_id, scope: "read:user" }(copilot.ts:234-245)→ 得到 device_code、user_code、verification_uri、interval。
    3. 提示用户在 verification_uri 输入 user_code(copilot.ts:258-261)。
    4. callback() 轮询 POST https://github.com/login/oauth/access_token,体 { client_id, device_code, grant_type: "urn:ietf:params:oauth:grant-type:device_code" }(copilot.ts:264-276);处理 authorization_pending / slow_down(含 3s 安全余量 OAUTH_POLLING_SAFETY_MARGIN_MS,copilot.ts:308-327)。
    5. 成功后返回并持久化:{ type:"success", refresh: access_token, access: access_token, expires: 0 }(copilot.ts:286-305);企业版附带 enterpriseUrl。

Token 交换的关键点:refresh 与 access 都等于同一个 GitHub OAuth 令牌,expires: 0,且 methods 中没有 refresh 处理器。运行时的出站请求 fetch(copilot.ts:102)在 info.type === "oauth" 时,直接用 Authorization: Bearer ${info.access}(copilot.ts:164)访问 Copilot API。全仓库检索 copilot_internal / /v2/token 无命中——没有第二段 Copilot 短期 token 交换,与「经典」逆向实现不同。

sequenceDiagram
    participant U as 用户
    participant OC as opencode (CopilotAuthPlugin)
    participant GH as github.com (OAuth)
    participant CAPI as api.githubcopilot.com
    U->>OC: 选择 GitHub.com / Enterprise 登录
    OC->>GH: POST /login/device/code {client_id, scope:read:user}
    GH-->>OC: device_code, user_code, verification_uri, interval
    OC-->>U: 显示 user_code + 打开 verification_uri
    loop 轮询直至授权
        OC->>GH: POST /login/oauth/access_token {client_id, device_code, grant_type:device_code}
        GH-->>OC: authorization_pending / slow_down / access_token
    end
    Note over OC: 持久化 access=refresh=GitHub OAuth token, expires:0
    OC->>CAPI: 之后所有请求 Authorization: Bearer <GitHub OAuth token>
    Note over OC,CAPI: 无 copilot_internal/v2/token 二次交换
Loading

二、请求整体流程(会话 → provider → 传输)

涉及三层协作:会话请求组装(packages/opencode/src/session/llm/request.ts)+ v2 core 语言模型/传输(packages/core/src/github-copilot/*)+ auth 插件的出站 fetch 包装(copilot.ts loader)。

  • 端点路由:packages/core/src/plugin/provider/github-copilot.ts:27-50 依据模型 endpoint(来自 /models 的 supported_endpoints)选择 responses / chat;GPT‑5 类(非 gpt-5-mini)默认走 Responses。
  • Base URL:base()(copilot.ts:26-28)→ 公有云 https://api.githubcopilot.com;企业版 https://copilot-api.<domain>。模型列表 GET <base>/models(models.ts:221)。
  • Provider 实例:createOpenaiCompatible(copilot-provider.ts:52)构造 chat/responses 语言模型,url = <baseURL><path>(copilot-provider.ts:72,81),默认头追加 UA 后缀 ai-sdk/openai-compatible/0.1.0(copilot-provider.ts:66)。
  • Header 组装:request.ts:187-204 合并「基础头(github‑copilot 仅 User-Agent)」+ model.headers + chat.headers 钩子输出;随后语言模型 combineHeaders(this.config.headers(), options.headers)(chat 模型 openai-compatible-chat-language-model.ts:206,323)。
  • 出站发送:最终网络 fetch 是 auth loader 的自定义 fetch(copilot.ts:102-179,通过 options.fetch 传入 provider),它强制覆盖 Authorization、User-Agent,并追加 Openai-Intent / 视觉头、删除 x-api-key 与小写 authorization。
sequenceDiagram
    participant S as Session/Prompt
    participant RB as request.ts (组装 headers/params)
    participant LM as core 语言模型 (chat/responses)
    participant FE as loader.fetch (copilot.ts)
    participant API as api.githubcopilot.com
    S->>RB: chat.params / chat.headers 钩子
    RB->>LM: getArgs() 构造 body + options.headers
    LM->>FE: postJsonToApi(url=<base>/chat(或/responses), headers, body)
    Note over FE: 解析 body 判定 isAgent/isVision<br/>覆盖 Authorization:Bearer, UA, Openai-Intent<br/>删除 x-api-key / authorization
    FE->>API: HTTPS 请求(流式 SSE)
    API-->>FE: 流式响应
Loading

三、客户端发往 GitHub Copilot 的 Request Header 明细

以一次普通对话(/chat/completions 或 /responses)为例,出站到 api.githubcopilot.com 的最终有效 Header:

Header 名称 取值来源(代码位置) 作用 / 说明
Authorization Bearer ${info.access}(copilot.ts:164;/models 处 copilot.ts:74) 鉴权。直接使用 GitHub OAuth 设备流令牌,无二次 Copilot token 交换
User-Agent opencode/${InstallationVersion}(copilot.ts:163、request.ts:18/194) 客户端标识(loader 强制覆盖为该值)
Openai-Intent 常量 "conversation-edits"(copilot.ts:165) 声明调用意图
x-initiator "agent" / "user":按请求体最后一条消息角色/是否工具续写判定(copilot.ts:107-158,161);对子代理会话、压缩续写在 chat.headers 中强制 agent(copilot.ts:394,411) 标识发起方(人类 vs 代理)
X-GitHub-Api-Version 常量 "2026-06-01"(copilot.ts:10,注入于 chat.headers copilot.ts:363;/models 请求头 copilot.ts:76) Copilot API 版本
Copilot-Vision-Request "true",当消息含图片(image_url / input_image / image)时(copilot.ts:168-170) 声明视觉请求
X-Interaction-Type "agent-session-name-generation",仅 title agent(copilot.ts:364-366) 会话标题生成的交互类型
anthropic-beta "interleaved-thinking-2025-05-14",仅 Claude(@ai-sdk/anthropic → /v1/messages)(copilot.ts:368-370) 开启交错思考
X-Session-Id / x-session-affinity / x-parent-session-id v2 core runner(packages/core/src/session/runner/llm.ts,anomalyco#43188) 代理缓存路由亲和 / 会话与父会话标识
Content-Type application/json(AI SDK postJsonToApi) JSON 请求体
x-api-key / authorization(小写) 在 loader 中被删除(copilot.ts:172-173) 移除 AI SDK 默认注入的鉴权头,避免与 Bearer 冲突

说明:request.ts 的基础头分支中,只有 opencode 自家 provider 才注入 x-opencode-project / x-parent-session-id 等(request.ts:188-201);github‑copilot 走 else 分支只带 User-Agent,其余 Copilot 专属头由 chat.headers 钩子与 loader fetch 注入。


四、客户端发往 GitHub Copilot 的 Request Body 明细

Body 由 v2 core 的两套语言模型(OpenAI 兼容 chat / OpenAI Responses)或 Anthropic Messages 适配器构造。

4.1 /chat/completions(OpenAI 兼容,@ai-sdk/github-copilot 的 chat 模型)

构造于 getArgs()(openai-compatible-chat-language-model.ts:139-190),流式在 doStream 追加 stream:true 与可选 stream_options(:308-313),POST 到 /chat/completions(:203,320)。字段:

字段 来源 说明
model this.modelId 模型 id
messages convertToOpenAICompatibleChatMessages(prompt)(:179) 见下方消息结构
max_tokens maxOutputTokens 对 gpt 模型被 chat.params 置空(copilot.ts:344-346)
temperature/top_p/frequency_penalty/presence_penalty/stop/seed 采样参数 标准 OpenAI 字段
response_format JSON/JSON‑Schema(:153-165) 结构化输出
reasoning_effort / verbosity / thinking_budget provider options(:175,176,186) 推理与思考预算
tools / tool_choice prepareTools(:130-137,182-183) 函数工具定义
stream / stream_options doStream(:310-313) SSE 流式与 usage

消息结构(convert-to-openai-compatible-chat-messages.ts):

  • system:{ role:"system", content:string }(:18-21)
  • user:纯文本 { role:"user", content:string } 或多模态 content:[{type:"text",...},{type:"image_url",image_url:{...}}](:27-51)
  • assistant:{ role:"assistant", content:text|null, tool_calls:[{type:"function", function:{name,arguments}, id}] }(:73-119)
  • tool:{ role:"tool", tool_call_id, content }(:128-155)

4.2 /responses(GPT‑5 类,OpenAI Responses)

构造于 openai-responses-language-model.ts 的 baseArgs(:253-309),流式 stream:true(:788),POST 到 /responses(:396,782)。字段:model、input(convertToOpenAIResponsesInput,:202-208)、temperature、top_p、max_output_tokens、text.{format,verbosity}、store(默认 true,:206,285)、instructions、include、parallel_tool_calls、previous_response_id、max_tool_calls、metadata、reasoning.{effort,summary}(仅推理模型,:295-305)、truncation:"auto"(需要时)、tools。推理模型会移除 temperature 等不支持项(:311-320)。

4.3 /v1/messages(Claude,@ai-sdk/anthropic 适配器)

当 /models 的 supported_endpoints 含 /v1/messages 时,模型 api.npm = "@ai-sdk/anthropic"、api.url = <base>/v1(models.ts:95-113)。Body 为标准 Anthropic Messages 结构。相关适配:chat.params 中对 @ai-sdk/anthropic 关闭 toolStreaming(避免 Copilot /v1/messages shim 拒绝 eager_input_streaming,copilot.ts:348-353);Header 追加 anthropic-beta。


五、鉴权机制小结(GitHub Copilot)

  • 凭据类型:GitHub OAuth 设备流令牌(type:"oauth"),持久化于 opencode auth 存储;access = refresh、expires:0。
  • 注入方式:auth loader 返回 apiKey:"" + 自定义 fetch;每次请求以 Authorization: Bearer <GitHub OAuth token> 注入(copilot.ts:100-179),并删除 AI SDK 默认的 x-api-key/小写 authorization。
  • 无二次交换:不请求 github.com/ghapi/copilot_internal/v2/token;GitHub OAuth 令牌被直接用于 api.githubcopilot.com(公有云)或 copilot-api.<domain>(企业版)。
  • 企业版:域名来自登录时输入,端点与 base URL 相应改写(copilot.ts:26-28,205-230)。

六、关键代码位置索引

  • packages/opencode/src/plugin/github-copilot/copilot.ts —— OAuth 设备流、出站 fetch 头注入、chat.params/chat.headers/experimental.provider.small_model 钩子
    • 常量 :9-11;base() :26-28;出站 fetch :102-179(头 :160-166,视觉 :168-170,删除 :172-173);authorize :222-336;chat.params :340-354;chat.headers :360-412
  • packages/opencode/src/plugin/github-copilot/models.ts —— GET <base>/models、模型能力/端点/计费解析(get() :216-259,build() :82-205,api.url/npm/endpoint :110-114)
  • packages/core/src/plugin/provider/github-copilot.ts —— provider 装配与端点路由(:20-50)
  • packages/core/src/github-copilot/copilot-provider.ts —— createOpenaiCompatible、默认头/UA、url 组装(:52-97)
  • packages/core/src/github-copilot/chat/openai-compatible-chat-language-model.ts —— /chat/completions body(getArgs :87-190)、doGenerate/doStream(:192-323)
  • packages/core/src/github-copilot/chat/convert-to-openai-compatible-chat-messages.ts —— 消息/多模态/工具调用结构(:18-155)
  • packages/core/src/github-copilot/responses/openai-responses-language-model.ts —— /responses body(baseArgs :253-309,端点 :396,782)
  • packages/opencode/src/session/llm/request.ts —— 会话级 Header/参数组装(:114-146,187-204,USER_AGENT :18)
  • packages/core/src/session/runner/llm.ts —— v2 会话级 X-Session-Id/x-session-affinity/x-parent-session-id(fix(core): restore session request headers anomalyco/opencode#43188)

本报告由定时分析任务生成;仅做只读分析,未修改任何仓库代码。上游检测结论:已是最新,无需同步。

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