Skip to content

[opencode 请求流程分析] OpenCode 请求流程与请求 Header/Body 分析:GitHub Copilot(2026-07-17) #7

Description

@nickhou1983

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

本报告基于 fork nickhou1983/opencodedev 分支代码,只读分析,未修改任何代码。

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

  • 上游 anomalyco/opencode@dev 检测到 1 个新提交3a1c6df9 fix(app): deduplicate diff summaries linearly (#37414)
  • 已通过 GitHub merge-upstream API 将本 fork 的 dev 快进同步(新的合并提交 2c7507f6)。
  • 该提交属于 packages/app 的 diff 摘要去重修复,与 GitHub Copilot 的请求流程 / Header / Body 无关,本次分析结论不受影响。

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

OpenCode 对 GitHub Copilot 采用 OAuth 2.0 设备授权码流程(Device Authorization Grant,RFC 8628),核心实现全部位于
packages/opencode/src/plugin/github-copilot/copilot.tsCopilotAuthPlugin

关键常量与端点

取值 代码位置
OAuth 客户端 ID Ov23li8tweQw6odWQebz copilot.ts:9
API 版本 2026-06-01 copilot.ts:10
设备码端点 https://{domain}/login/device/code copilot.ts:19-24 (getUrls)
Token 端点 https://{domain}/login/oauth/access_token copilot.ts:19-24
请求作用域 read:user copilot.ts:244
轮询安全余量 3000ms copilot.ts:14
Copilot API Base https://api.githubcopilot.com(公有)/ https://copilot-api.{企业域名}(企业版) copilot.ts:26-28 (base)

domain 默认为 github.com;选择 “GitHub Enterprise” 时由用户输入的企业 URL 经 normalizeDomain 归一化得到(copilot.ts:222-230)。

交换时序

sequenceDiagram
    participant U as 用户
    participant OC as OpenCode (CopilotAuthPlugin)
    participant GH as GitHub OAuth (github.com / GHE)
    participant CAPI as Copilot API (api.githubcopilot.com)

    U->>OC: 选择 "Login with GitHub Copilot" (device flow)
    OC->>GH: POST /login/device/code {client_id, scope:"read:user"}
    GH-->>OC: {verification_uri, user_code, device_code, interval}
    OC-->>U: 显示 verification_uri + user_code
    U->>GH: 浏览器打开 verification_uri,输入 user_code 授权
    loop 轮询直至授权完成 (interval + 3s)
        OC->>GH: POST /login/oauth/access_token {client_id, device_code, grant_type=device_code}
        GH-->>OC: authorization_pending / slow_down / {access_token}
    end
    OC->>OC: 持久化 {access=refresh=access_token, expires:0, enterpriseUrl?}
    Note over OC,CAPI: 之后每次请求由 auth loader 的 fetch 包装器注入凭据
    OC->>CAPI: GET /models (Authorization: Bearer <token>) 拉取模型清单
    OC->>CAPI: POST /chat/completions | /responses | /v1/messages (推理请求)
Loading

流程要点

  1. 发起设备码请求copilot.ts:234-256):POST /login/device/code,请求体 {client_id, scope:"read:user"},Header 含 Accept: application/jsonContent-Type: application/jsonUser-Agent: opencode/{版本}
  2. 展示待授权信息:返回 verification_uriuser_codemethod: "auto" 自动打开浏览器(copilot.ts:258-261)。
  3. 轮询换取 Tokencopilot.ts:262-334):POST /login/oauth/access_tokengrant_type=urn:ietf:params:oauth:grant-type:device_code
    • authorization_pending:按 interval + 3s 休眠后继续(:308-311)。
    • slow_down:按 RFC 8628 §3.5 将间隔 +5s(若服务端返回新 interval 则采用),再加安全余量(:313-327)。
  4. 持久化凭据copilot.ts:286-306):{ type:"success", refresh: access_token, access: access_token, expires: 0, enterpriseUrl? }

⚠️ 关键结论:无独立的 Copilot Token 交换

与部分 Copilot 集成不同,本仓库不调用 api.github.com/copilot_internal/v2/token 交换短期 Copilot Token(全仓库无 copilot_internal 匹配)。OpenCode 直接把设备流拿到的 GitHub OAuth access token 当作 Copilot API 的 Bearer 使用:

  • expires: 0 且 auth loader 未定义 refresh 回调(对比 xai.ts/codex.ts 有刷新逻辑);
  • loader 内的 fetch 包装器每次请求都重新 getAuth() 读取当前存储的 token 后直接注入(copilot.ts:102-104,160-166)。

凭据注入位置

  • 模型发现provider.models() 携带 Authorization: Bearer {auth.access} + User-Agent + X-GitHub-Api-VersionCopilotModels.get()copilot.ts:62-92),后者 GET {base}/modelsmodels.ts:213-256)。
  • 推理请求:由 auth.loader 返回的自定义 fetch 包装器统一注入(见第 2 节)。

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

请求 Header 分两个阶段组装,最终由 auth loader 的 fetch 包装器覆盖写入并发送

阶段 A — LLMRequestPrep.preparepackages/opencode/src/session/llm/request.ts

对非 opencode 自有 provider(含 Copilot)注入基础 Header,并触发 chat.headers 插件钩子合并:

Header 取值来源(代码位置) 作用 / 说明
x-session-affinity input.sessionIDrequest.ts:197 会话粘性路由
X-Session-Id input.sessionIDrequest.ts:198 会话标识
x-parent-session-id input.parentSessionID(子代理时,request.ts:199 父会话标识
User-Agent opencode/{InstallationVersion}request.ts:194/200 客户端标识
X-GitHub-Api-Version 常量 2026-06-01copilot.ts:363chat.headers 钩子) 指定 Copilot API 版本
X-Interaction-Type agent-session-name-generation(仅 title 代理,copilot.ts:364-366 会话标题生成场景标记
anthropic-beta interleaved-thinking-2025-05-14(模型 npm 为 @ai-sdk/anthropic 时,copilot.ts:368-370 开启交错思考 beta
x-initiator agent(压缩续写 / 子代理会话,copilot.ts:385-411 标记请求由 agent 发起

随后 model.headers 与钩子 Header 合并进最终 headersrequest.ts:187-204)。

阶段 B — auth loader 的 fetch 包装器(copilot.ts:102-179,最终生效层)

包装器在真正发起 fetch 前,检查请求体判断 isVision/isAgent,并组装最终 Header:

Header 取值来源(代码位置) 作用 / 说明
Authorization Bearer {info.access}copilot.ts:164 鉴权:直接使用 GitHub OAuth access token
User-Agent opencode/{InstallationVersion}copilot.ts:163 客户端标识(覆盖上游)
Openai-Intent 常量 conversation-editscopilot.ts:165 Copilot 侧意图标记
x-initiator agent / user(由请求体推断,copilot.ts:160-161 计费 / 配额区分;若上游钩子已设 agent 则以其为准(init.headers 在其后展开覆盖)
Copilot-Vision-Request true(请求含图片时,copilot.ts:168-170 声明多模态视觉请求
x-api-key deletecopilot.ts:172 移除,避免与 Bearer 冲突
authorization(小写) deletecopilot.ts:173 移除小写重复项

isAgent 判定copilot.ts:107-158):解析请求体,兼容三种 API 形态——
Completions(body.messages 且 URL 含 completions)、Responses(body.input)、Messages(body.messages);
末条消息非 user、或为附带图片的合成用户消息(imgMsg)时判为 agent
isVision:任一消息含 image_url / input_image / image(含嵌套于 tool_result 内的图片)。

Header 组装与覆盖顺序

flowchart LR
    A[request.ts 基础 Header<br/>x-session-affinity / X-Session-Id / User-Agent] --> M[合并]
    B[chat.headers 钩子<br/>X-GitHub-Api-Version / X-Interaction-Type<br/>anthropic-beta / x-initiator] --> M
    C[model.headers] --> M
    M --> D[AI SDK doStream:<br/>combineHeaders provider.headers + 请求 Header]
    D --> E[auth loader fetch 包装器<br/>覆盖 Authorization / User-Agent / Openai-Intent<br/>x-initiator / Copilot-Vision-Request<br/>删除 x-api-key、authorization]
    E --> F[(发往 api.githubcopilot.com)]
Loading

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

请求体由 AI SDK 语言模型构造。Copilot 依据 /models 返回的 supported_endpoints 为每个模型选择端点
models.ts:92-99):/v1/messages → Anthropic 协议、/responses → Responses、/chat/completions → Chat。
请求参数源头统一在 request.tsparamstemperature/topP/topK/maxOutputTokens/options,来自 ProviderTransform),
系统提示注入为 system 消息(Responses 端点则注入 instructions)。

3.1 Chat Completions(POST {base}/chat/completions,npm @ai-sdk/github-copilot

Body 由 getArgs 构造(packages/core/src/github-copilot/chat/openai-compatible-chat-language-model.ts:139-189):

字段 说明 / 来源
model 模型 API id(:142
messages convertToOpenAICompatibleChatMessages(prompt):179
max_tokens maxOutputTokens:148;gpt 模型经 chat.params 置空以省略)
temperature / top_p :149-150
frequency_penalty / presence_penalty :151-152
response_format JSON / json_schema(结构化输出时,:153-165
stop / seed :167-168
reasoning_effort / verbosity 推理模型(:175-176
thinking_budget :186
tools / tool_choice prepareTools 结果(:182-183
stream 流式时 truedoStream:310

3.2 Responses(POST {base}/responses,GPT‑5 类模型,非 gpt-5-mini

Body 由 packages/core/src/github-copilot/responses/openai-responses-language-model.ts:253-300 构造:
modelinputinstructions(= 系统提示)、max_output_tokenstemperaturetop_p
store(默认 true)、toolsincludereasoningeffort/summary)、text.verbosity
parallel_tool_callsprevious_response_idstream: true:786-788)等。

3.3 Messages / Anthropic(POST {base}/v1/messages,npm @ai-sdk/anthropic

supported_endpoints/v1/messages 的模型,api.url 置为 {base}/v1npm 置为 @ai-sdk/anthropic
models.ts:92,109-110)。Body 为标准 Anthropic Messages 格式(modelsystemmessagesmax_tokenstools 等)。

3.4 Copilot 专属 Body 调整(chat.params 钩子,copilot.ts:340-354

  • GPT 模型maxOutputTokens = undefined → 省略 max_tokens,对齐官方 Copilot CLI(:344-346)。
  • Anthropic 模型toolStreaming = false,规避 Copilot /v1/messages shim 拒绝 eager_input_streaming 字段(:348-353)。
  • 工具占位:回放历史工具调用但当前无启用工具时,注入 _noop 工具以满足 Copilot API 对 tools 字段的要求(request.ts:159-175)。

4. 关键代码位置索引

关注点 文件 : 行
设备流 / 凭据 / fetch 包装器 / 钩子 packages/opencode/src/plugin/github-copilot/copilot.ts:9-14, 26-28, 56-92, 102-179, 222-337, 340-354, 360-412
模型发现与端点映射 packages/opencode/src/plugin/github-copilot/models.ts:82-159, 213-256
请求 Header / Body 参数组装 packages/opencode/src/session/llm/request.ts:56-206
Chat Completions Body / 发送 packages/core/src/github-copilot/chat/openai-compatible-chat-language-model.ts:87-210, 306-323
Responses Body / 发送 packages/core/src/github-copilot/responses/openai-responses-language-model.ts:253-300, 782-788
核心插件端点选择 packages/core/src/plugin/provider/github-copilot.ts:20-50
新 llm 栈 provider 定义 packages/llm/src/providers/github-copilot.ts:19-69
AI SDK 事件桥接 / Copilot 计费 packages/opencode/src/session/llm/ai-sdk.ts:30-42

5. 结论摘要

  • 鉴权:OAuth 设备授权码流程(client_id Ov23li8tweQw6odWQebz,scope read:user),直接使用 GitHub OAuth token 作为 Bearer,无 copilot_internal 二次交换
  • Header:最终由 auth loader 的 fetch 包装器统一注入 Authorization: Bearer <token>User-Agent: opencode/{ver}Openai-Intent: conversation-editsx-initiator、按需 Copilot-Vision-Request;上游另加 X-GitHub-Api-Version: 2026-06-01、会话相关及场景性 Header。
  • Body:按模型 supported_endpoints 分流到 /chat/completions/responses/v1/messages 三种协议,并针对 Copilot 做 max_tokens 省略、toolStreaming 关闭、_noop 工具占位等适配。

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions