Skip to content

[opencode 请求流程分析] GitHub Copilot 认证/Token、请求 Header 与 Body 专项分析(2026-08-06) #25

Description

@nickhou1983

本次变更提示

本次从上游 anomalyco/opencode:dev 同步了 62 个提交1882c33827..24470e52a5,已合并为 c44418cc86 并推送到 fork 的 dev)。改动主要集中在 packages/apppackages/uipackages/desktoppackages/web(前端、i18n、RTL、Markdown 渲染等),未触及 GitHub Copilot 的认证与请求构造代码。唯一与请求流程沾边的是 #40265(修复 Azure + gpt-5.5 的 reasoningEffort),与 Copilot 无关。故下文结论与上一版一致,且做了 Copilot 专项细化。

说明:以下内容以仓库实际代码为准,不臆测 GitHub 服务端行为。凡涉及 Authorization 头字面量处,源码被 secret scanner 掩码为 ******,本文按其结构描述为 Bearer <access_token>


1. 请求流程总览

GitHub Copilot 在 opencode 中是一个认证插件 + AI SDK Provider 的组合:

  • 认证与 Header 注入:packages/opencode/src/plugin/github-copilot/copilot.tsCopilotAuthPlugin
  • 模型清单与端点路由:packages/opencode/src/plugin/github-copilot/models.ts
  • 请求体构造(AI SDK 适配层,仓库内 vendored 副本):packages/core/src/github-copilot/**

一次对话请求的整体时序:

sequenceDiagram
    participant U as 用户/Agent
    participant S as opencode 会话层
    participant H as chat.headers/params 钩子<br/>(copilot.ts)
    participant M as AI SDK 语言模型<br/>(core/github-copilot)
    participant F as 自定义 fetch 包装器<br/>(copilot.ts loader)
    participant G as api.githubcopilot.com

    U->>S: 发起 Prompt
    S->>H: 触发 chat.params / chat.headers
    H-->>S: 注入 X-GitHub-Api-Version 等 headers<br/>+ 调整 params(gpt* 去 max_tokens / anthropic 关 toolStreaming)
    S->>M: 调用 doGenerate / doStream
    M->>M: getArgs() 构造 Body(JSON)
    M->>F: postJsonToApi(url, combineHeaders(...), body)
    F->>F: 注入 Authorization: Bearer <access><br/>+ User-Agent / Openai-Intent / x-initiator<br/>+ (视觉请求)Copilot-Vision-Request<br/>删除小写 authorization / x-api-key
    F->>G: HTTP POST /chat/completions | /responses | /v1/messages
    G-->>F: SSE / JSON 响应
    F-->>M: 透传响应流
    M-->>U: 解析为消息/工具调用
Loading

关键点:认证头不是由 AI SDK Provider 注入的。Provider 以 apiKey: "" 创建(见 copilot-provider.ts:60-64if (options.apiKey) 判定为 false),真正的 Authorization 由 loader 里的自定义 fetch 包装器在每次请求时统一注入。


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

采用 OAuth 2.0 设备码流程(Device Authorization Grant, RFC 8628),代码位于 copilot.ts

关键常量:

常量 位置
CLIENT_ID Ov23li8tweQw6odWQebz copilot.ts 顶部
API_VERSION 2026-06-01 copilot.ts 顶部
OAUTH_POLLING_SAFETY_MARGIN_MS 3000 copilot.ts 顶部
认证域名 默认 github.com(企业版用自定义域名) authorize/callback
Base API https://api.githubcopilot.com(企业版 https://copilot-api.{normalizedEnterpriseUrl} provider.models

流程:

sequenceDiagram
    participant C as opencode
    participant D as github.com<br/>(login/device/*)
    participant A as api.githubcopilot.com

    C->>D: POST /login/device/code<br/>body {client_id, scope:"read:user"}<br/>Accept/Content-Type: application/json
    D-->>C: device_code, user_code, verification_uri, interval, expires_in
    C->>C: 提示用户在浏览器输入 user_code 授权
    loop 轮询(间隔 interval + 安全余量)
        C->>D: POST /login/oauth/access_token<br/>body {client_id, device_code,<br/>grant_type: urn:ietf:params:oauth:grant-type:device_code}
        alt 待授权
            D-->>C: error=authorization_pending → 继续轮询
        else 频率过快
            D-->>C: error=slow_down → interval +5s 后继续
        else 成功
            D-->>C: access_token
        end
    end
    C->>C: 存储 refresh=access=access_token, expires:0<br/>(+ 可选 enterpriseUrl)
    C->>A: 后续请求直接用 access_token 作 Bearer
Loading

关键结论(与"常见 Copilot 流程"不同):

  • opencode 没有 copilot_internal/v2/token 这一步二次令牌交换。grep 全仓确认不存在该端点。
  • 设备码流程拿到的 GitHub OAuth access_token 被直接当作 Bearer 令牌发往 api.githubcopilot.com
  • 令牌存储中 refreshaccess 同值,expires 记为 0,即不做本地过期滚动刷新(以实际存储字段为准)。

设备码/令牌交换请求头(copilot.ts):

Header 取值 说明
Accept application/json 期望 JSON 响应
Content-Type application/json 请求体为 JSON

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

Header 有 两个来源,最终由 combineHeaders() 合并、并由自定义 fetch 包装器做最后覆盖。

3.1 自定义 fetch 包装器(作用于所有 Copilot API 调用)

位置:copilot.ts loader 的 fetch 包装(约 155-175 行)。isAgent/isVision 通过解析请求体判定(兼容 Completions / Responses / Anthropic Messages 三种体形)。

Header 取值来源 作用
Authorization Bearer <access_token>(源码被掩码 ****** 携带设备流 OAuth 令牌鉴权
User-Agent opencode/${InstallationVersion} 客户端标识
Openai-Intent conversation-edits 固定意图声明
x-initiator agent(isAgent)或 user 请求发起者类型(可被钩子覆盖,见 3.2)
Copilot-Vision-Request true(仅当检测到图片/视觉内容) 声明视觉请求
(删除)x-api-keyauthorization 小写键被删除 避免与注入的鉴权头冲突

3.2 chat.headers 钩子(按会话/模型条件附加,作为 options.headers

位置:copilot.ts 约 360-412 行。

Header 取值 触发条件
X-GitHub-Api-Version 2026-06-01 所有 github-copilot 请求
X-Interaction-Type agent-session-name-generation agent === "title"(标题生成)
anthropic-beta interleaved-thinking-2025-05-14 模型 npm 为 @ai-sdk/anthropic
x-initiator agent 压缩/自动压缩会话,或子代理(有 parentID)会话

3.3 /models 清单请求

位置:copilot.ts 约 70-78 行 / models.ts

Header 取值 作用
Authorization Bearer <access_token> 鉴权
User-Agent opencode/${InstallationVersion} 客户端标识
X-GitHub-Api-Version 2026-06-01 API 版本

3.4 合并/覆盖顺序

  1. AI SDK Provider getHeaders():仅追加 User-Agent 后缀 ai-sdk/openai-compatible/0.1.0(因 apiKey:"" 不注入 Authorization)。
  2. combineHeaders(config.headers(), options.headers):并入 3.2 钩子产出的 headers。
  3. 自定义 fetch 包装器:先置 x-initiator 默认值 → 展开传入 headers(钩子的 x-initiator 覆盖默认值)→ 最后强制 User-Agent / Authorization / Openai-Intent(这三者始终以包装器为准)。

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

Copilot 模型按 models.ts:92-111supported_endpoints 路由到三种协议,Body 结构各不相同:

端点 URL npm 适配器 Body 构造
chat https://api.githubcopilot.com/chat/completions @ai-sdk/github-copilot OpenAI Chat Completions
responses https://api.githubcopilot.com/responses @ai-sdk/github-copilot OpenAI Responses
messages https://api.githubcopilot.com/v1/messages @ai-sdk/anthropic Anthropic Messages

端点选择:core/src/plugin/provider/github-copilot.ts:27-50 — GPT-5+(非 gpt-5-mini)优先走 responses,否则 chat;提供 /v1/messages 的模型走 Anthropic 协议。注意 chat/responses 的 baseURL 不含 /v1,仅 messages 的 baseURL 为 ${url}/v1

4.1 chat/completions Body

位置:core/src/github-copilot/chat/openai-compatible-chat-language-model.tsgetArgs()(约 87-189 行)。

字段 来源 说明
model this.modelId 模型 ID
messages convertToOpenAICompatibleChatMessages(prompt) 消息数组(见 4.2)
max_tokens maxOutputTokens 输出上限(gpt* 模型被 chat.params 删除,见 4.4)
temperature / top_p 选项 采样参数
frequency_penalty / presence_penalty 选项 惩罚项
stop stopSequences 停止序列
seed 选项 复现用随机种子
response_format responseFormat json_schemajson_object
reasoning_effort compatibleOptions.reasoningEffort 推理力度
verbosity compatibleOptions.textVerbosity 冗长度
thinking_budget compatibleOptions.thinking_budget 思考 token 预算
tools / tool_choice prepareTools() 工具定义与选择策略
user compatibleOptions.user 用户标识
stream truedoStream 流式开关
stream_options {include_usage: true}(若 includeUsage) 流内返回用量统计

4.2 messages 转换(chat 协议)

位置:convert-to-openai-compatible-chat-messages.ts。将 LanguageModelV3Prompt 转为 OpenAI 消息数组:

  • system{role:"system", content}
  • user → 纯文本走 {role:"user", content:string} 捷径;含附件时 content 为分片数组:{type:"text"}{type:"image_url", image_url:{url}}(图片以 data:{mediaType};base64,... 内联)
  • assistant{content, tool_calls?, reasoning_text?, reasoning_opaque?}
  • tool{role:"tool", tool_call_id, content}

4.3 responses Body

位置:core/src/github-copilot/responses/openai-responses-language-model.ts(约 253-300 行 baseArgs)。

主要字段:modelinputtemperaturetop_pmax_output_tokenstext(含 format/verbosity)、max_tool_callsmetadataparallel_tool_callsprevious_response_idstore(默认 true)、userinstructionsservice_tierincludereasoning(含 effort/summary,仅推理模型)、toolstool_choicedoStream 追加 stream: true

4.4 messages(Anthropic)Body 与 chat.params 调整

  • /v1/messages 的 Body 由 @ai-sdk/anthropic 构造,为标准 Anthropic Messages 形(modelmessagessystemmax_tokenstemperaturetop_ptop_kstop_sequencestoolstool_choicestreamthinking 等)。
  • chat.params 钩子(copilot.ts 约 340-354 行):
    • gpt* 模型:删除 maxOutputTokens(即不发 max_tokens)。
    • @ai-sdk/anthropic:设 toolStreaming = false(Copilot 的 /v1/messages 兼容层会拒绝 eager_input_streaming)。

5. 关键 API 端点汇总

端点 方法 用途 鉴权
https://github.com/login/device/code POST 发起设备码流程 否(仅 client_id)
https://github.com/login/oauth/access_token POST 轮询换取 access_token 否(client_id + device_code)
https://api.githubcopilot.com/models GET 获取模型清单 Bearer
https://api.githubcopilot.com/chat/completions POST Chat Completions Bearer
https://api.githubcopilot.com/responses POST Responses(GPT‑5+ 类) Bearer
https://api.githubcopilot.com/v1/messages POST Anthropic Messages Bearer
https://copilot-api.{enterprise}/... GET/POST 企业版对应端点 Bearer

6. 代码位置索引

关注点 文件 关键位置
设备码认证 / 令牌 / fetch 头注入 / chat.params / chat.headers packages/opencode/src/plugin/github-copilot/copilot.ts 9-28 常量;70-78 /models 头;155-175 fetch 注入;222-336 设备流;340-354 chat.params;360-412 chat.headers
模型清单与端点路由 packages/opencode/src/plugin/github-copilot/models.ts 92-111 端点判定;213-256 get()
SDK Provider(url / headers,apiKey 空不注入鉴权) packages/core/src/github-copilot/copilot-provider.ts 52-97;60-64 Authorization 条件;72/81 url
chat Body 构造 / 发送 packages/core/src/github-copilot/chat/openai-compatible-chat-language-model.ts 87-189 getArgs;192+ doGenerate;305-329 doStream
chat 消息转换 packages/core/src/github-copilot/chat/convert-to-openai-compatible-chat-messages.ts 13-170
responses Body 构造 packages/core/src/github-copilot/responses/openai-responses-language-model.ts 253-300 baseArgs;396/782 path:"/responses"
SDK 注册与语言模型选择 packages/core/src/plugin/provider/github-copilot.ts 20-25 注册;27-50 端点→模型选择
插件注册 packages/opencode/src/plugin/index.ts 15 导入;73 CopilotAuthPlugin

本报告由定时任务在同步上游后自动生成;分析基于同步点 24470e52a5(2026-08-06)对应的仓库代码。

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions