跳到主要内容

dsh-opencode-patch

已验证

dsh-opencode-patch · v1.1.1 · MIT · Web 界面

OpenCode on DeepSeek Harness — session affinity, Zen/Go gateway origin headers, free-tier tool fallback, and live Go quota display.

安装

dsh plugin add dsh-opencode-patch

用 dsh --profile default --dump-config 确认 layer 已生效 —— 参见安装指南。

源码

发布到 npm 但没有公开仓库。安装前请检查包内容。

标签

说明文档

English  ·  简体中文

OpenCode on DeepSeek Harness

dsh-opencode-patch

OpenCode on DeepSeek Harness
网关来源头 · 会话亲和 · 免费层工具回退 · 实时双模式额度计量

npm version npm downloads CI Release license node TypeScript DSH host plugin zero-config PRs welcome

快速开始 · 修复的问题 · 界面 · 配置 · 故障排查 · 更新日志 · 贡献指南


dsh-opencode-patch 是一个 DeepSeek Harness 宿主插件,让 OpenCode Zen 与 Go 模型在 DSH 内持续可用。无需遭遇网络拒绝、Cloudflare 挑战、权益不匹配或隐形限制,即可接入 claude-sonnet-4-5、gpt-5.4、gemini-3.8-flash、deepseek-v4.1-flash、muse-spark-1.3-contributor-free、qwen3.8-flash 以及 OpenCode 目录中的其余模型。

默认状态。 把 DSH 直接指向 OpenCode、不装任何插件,模型是完全不能用的——不是某一种形状不能用,而是全部:

  • 请求不会被识别为"编码 agent"发出的。 DSH 发的是自己的 User-Agent,不带 x-opencode-client、x-opencode-project,也不带 x-opencode-session;网关看到的就是一次匿名 SDK 调用:免费层模型返回 403 FreeTierError,流量还要受厂商对"不表明身份的客户端"所设的滥用规则约束。
  • 没有会话 id 的一轮会被拒、或失去关联。 每一轮都必须带一个稳定的 ses_… 形状的 id,而 DSH 在子代理、后台评估以及 Auto Review 这类模式下会完全省略 sessionId。
  • 免费层的 /responses 请求体缺少 read 与 bash 会被拒,而 DSH 是刻意不发这两个工具的。
  • 目录很单薄。 裸 /models 列表只有 id,几乎没有别的信息,所以选择器里是一堆没有名字、没有价格、没有上下文窗口的行。

这些能用之后剩下的是另一个问题,而且与账号无关:OpenCode 的模型分布在四种不同的 API 形状上,而 DSH 是按 provider 行选择传输协议的——一条路由只能说一种形状。所以走 Responses / Messages / Mistral 的模型在基础问题解决之后就会返回 500。这正是插件按模型而不是按 provider 路由的原因。

插件在网络层补齐所有缺失的协议要素——且仅针对 OpenCode 路由(opencode / opencode-go / opencode-responses / opencode-anthropic / opencode-mistral)。其余全部流量(DeepSeek、OpenAI、Anthropic、GitHub)原样通过。

三条主线。

  1. 最小侵入。 不做全局补丁:只拦截本插件声明的路由,其他厂商——DeepSeek、OpenAI、Anthropic、GitHub——的请求原样通过。计量器离"彻底消失"只差一个开关,而不是离"勉强忍受"隔着一个页面。
  2. 原生 DSH 插件。 一个 apply()、一张绑定自己命名空间的设置卡、用 ctx.inject 声明服务、按宿主自己的方式填充插槽,文案随宿主语言切换。没有私有钩子,不改宿主代码。
  3. OpenCode API 兼容。 网关真正检查的那些特征在网络层还原——与 CLI 同形状的 ses_… 会话 id、官方来源头、父会话血统,以及免费层的 read/bash 工具 schema 回退。

亮点

  • 🔑 确定性的 ses_<12hex><14base62> 会话哈希,跨轮次、子代理与分叉保持 KV 缓存亲和
  • 🌐 网关来源恢复——User-Agent、x-opencode-client、x-opencode-project、父会话谱系
  • 🧰 免费层 read + bash 工具 schema 回退,让 Zen 免费模型不再报 403 FreeTierError
  • 📇 基于 models.dev 的模型目录,带离线预置与后台 SWR 刷新——名称、上下文窗口、价格
  • ⭕ 实时双模式停靠栏计量——Go 额度环(5 小时 / 每周 / 每月)或 Zen 按量计费胶囊,外加会话消耗与模型费率
  • 🕵️ 严格凭据隔离——Zen 密钥(oc_sk_…)绝不查询 Go 额度端点

目录

  1. 快速开始
  2. 修复的问题
  3. 支持的模型与协议
  4. 界面
  5. 配置参考
  6. 故障排查
  7. 兼容性与验证
  8. 深入解析 — 协议内部实现,已移出本 README
  9. 署名与许可

🚀 快速开始

1. 安装到你的 DSH Web profile(Node 24+):

cd ~/.dsh/profiles/web
npm install dsh-opencode-patch

包名在所有用到它的地方都是 dsh-opencode-patch:你安装的依赖、profile 里的 bundle 条目,以及宿主解析的那一行。

2. 启用 bundle——把该包加入 profile 的 dsh.profile.bundles 数组:

// ~/.dsh/profiles/web/package.json
{
  "dependencies": {
    "dsh-opencode-patch": "^0.12.0",
  },
  "dsh": {
    "profile": {
      "bundles": ["dsh-opencode-patch"],
      "patchReload": "live",
    },
  },
}

3. 添加凭据,让额度计量能解析到密钥——在 DSH Credentials 或你的环境变量中存储 OPENCODE_GO_API_KEY(Go 订阅,sk-…)和/或 OPENCODE_API_KEY(Zen 按量计费,oc_sk_…)。

4. 重启 dsh web。 宿主 bundle 只在启动时导入(hmr root: []),因此要重启才会加载 lib/index.mjs;客户端 UI 变更(lib/client.js)只需刷新浏览器。

完成——OpenCode 补丁设置卡片会出现在 Settings → Plugins 下,一旦有 OpenCode 模型激活,计量表就会挂载到输入框停靠栏。


🔌 修复的问题

没有补丁时 使用 dsh-opencode-patch 后
Zen 免费模型报 403 FreeTierError 自动恢复网关来源头与工具回退
一条路由只能说一种形状——基础问题解决后,其余模型返回 500 逐模型路由——按目录里每个模型自己声明的 SDK 派发到对应端点
会话 ID 被拒并返回 400 MissingSessionID 确定性的 ses_… 会话哈希与跨轮次亲和
子代理丢失对话上下文 父会话跟踪(x-opencode-parent-session-id、x-parent-session-id)
Auto Review 调用报 TRANSPORT: Connection error 回退会话轮次捕获,在评估调用之间保留轮次状态
额度与余额不可见 实时双模式停靠栏计量,显示 Go 额度或 Zen 按量计费
Zen 密钥在 Go 用量上触发 403 EntitlementError 严格凭据隔离,让 Zen 密钥不接触 Go 端点
所有项目共用一个 "global" 遥测桶 动态工作区归属,由当前 session.header.cwd 解析
网关 /models 返回截断且无名称的列表 models.dev 补全:显示名、上下文窗口与价格

🧭 支持的模型与多协议路由

两种端点,两种凭据形状。 OpenCode 的目录在两个层级上定义端点:provider 默认端点——Zen 是 https://opencode.ai/zen/v1,Go 是 https://opencode.ai/zen/go/v1——以及逐模型的推理端点,目录里设置时会覆盖默认值(CLI 读 model.api.url,取不到就回落到 provider 默认)。插件两个都不钉死:它匹配的是 opencode.ai/zen 这个标记,所以走默认端点的模型和走自己端点的模型都会被补丁覆盖。

这些端点的认证有两种,因为它们说三种线格式:OpenAI 那两个平面发 Authorization: Bearer <key>,Anthropic 平面发 x-api-key: <key>。插件两种都读——先 Authorization,再 x-api-key / api-key——所以凭据无论由哪个平面带过来都能被捕获,Go 额度查询也仍然找得到密钥。

网关提供四种线路家族,而 OpenAI 自己就占了两种:Chat Completions 与 Responses 是两种不同的线路格式,不是同一个东西的两个名字。需要 Responses 的模型在 completions 端点上不工作,而网关不会回退——它直接返回 500。

DSH 能说其中三种,所以插件路由这三种,故意不提供第四种(Google Generative AI)。见下表。

1. OpenCode Zen (provider: opencode) — 按量计费与免费额度

  • Anthropic Messages (https://opencode.ai/zen/v1/messages):claude-sonnet-4-5、claude-opus-4-7、claude-haiku-4-5、qwen3.8-flash
  • OpenAI Responses (https://opencode.ai/zen/v1/responses):gpt-5.4、gpt-5.2、gpt-5.1-codex-max、muse-spark-1.3、space-bunny-free,以及免费层的 muse-spark-1.3-contributor-free
  • OpenAI Chat Completions (https://opencode.ai/zen/v1/chat/completions):deepseek-v4.1-flash、kimi-k2.5、kimi-k3、minimax-m2.5、glm-5.2,外加免费层的 nemotron-3-ultra-free、ling-3.0-flash-fin-free、mimo-v2.6-flash-free
  • Google Generative AI (https://opencode.ai/zen/v1/models/*:streamGenerateContent):gemini-3.8-flash、gemini-3.1-pro、gemini-3.5-flash-lite

2. OpenCode Go (provider: opencode-go) — 订阅额度

  • OpenAI Chat Completions(https://opencode.ai/zen/go/v1/chat/completions):deepseek-v4-flash, deepseek-v4-flash-vision-exp, deepseek-v4-pro, deepseek-v4.1-flash, glm-5.2, glm-5.3, glm-5.3-flash, hy3, hy4-preview, kimi-k2.6, kimi-k2.7-code, kimi-k3, longcat-2.0, longcat-2.5-preview-free, mimo-v2.5, mimo-v2.5-pro, mimo-v2.6-flash, mimo-v2.6-pro, qwen3.6-plus, qwen3.7-max, space-bunny, space-bunny-free, step-5-preview-free
  • OpenAI Responses(https://opencode.ai/zen/go/v1/responses):gpt-5.6-luna, gpt-6-luna, grok-4.5, grok-4.6, grok-4.7, muse-spark-1.2-contributor, muse-spark-1.3-contributor
  • Anthropic Messages(https://opencode.ai/zen/go/v1/messages):claude-haiku-5-5, minimax-m2.7, minimax-m3, qwen3.7-plus, qwen3.8-flash, qwen3.8-max
  • 由实时三窗口额度计量监控(5 小时滚动、每周、每月)。完整集合随内置目录发布——参见权威模型目录。

3. 模型的协议是怎么定的 —— 以及为什么你什么都不用配

Zen 的 provider 级 SDK 是 @ai-sdk/openai-compatible。models.dev 只在该模型需要不同 SDK 时才逐模型标注 provider.npm —— 所以这个字段的存在本身就是信号,它决定走哪条线路协议。没有任何手工匹配:

models.dev provider.npm 模型数 协议 服务自
(缺失) 28 OpenAI Chat Completions 你配置的路由
@ai-sdk/openai 30 OpenAI Responses opencode-responses
@ai-sdk/anthropic 18 Anthropic Messages opencode-anthropic
@ai-sdk/google 7 (DSH 无此协议) 不提供

数量为 models.dev 中 opencode 的 83 个在用模型,统计于 2026-10-05 —— 其余已废弃或下架,补丁只内置网关仍在服务的部分。同样这 83 个模型离线内置在 src/catalog-data.ts,因此在第一次 catalog 刷新之前它们每一个路由都是正确的;用 pnpm run catalog:shim 重新生成该文件。

插件把这两条内部路由同时挡在模型选择器和 Settings → Models 之外。三条性质今天就成立,还有一条尚未成立:

  • 你的挑选仍然有效。 选择器显示的就是你列出的模型,每个模型都会自动匹配到正确的协议。OpenCode 的模型分布在三种形状上——OpenAI Chat Completions(默认)、OpenAI Responses、Anthropic Messages——需要后两种的模型会被派发到对应路由。不需要手工声明第二条路由。
  • 永远不会提供跑不通的模型。 那 8 个 @ai-sdk/google 模型会从"获取可用模型"和 opencode 路由自己的报告中双双剔除 —— DSH 没有对应协议,选了只会失败,而且行里没有任何东西能告诉你原因。
  • 一份凭据。 所有路由模型共用你已配置的 opencode key,经凭据服务解析,不会重新问你要。

如果你更想自己声明路由:opencode-responses(以及你用上 Anthropic 平面模型后的 opencode-anthropic)可以写进 profile 的 llm-pi-ai providers 块里,并列出它服务的模型。声明是一个选择,不是要求:

opencode-responses:
  api: openai-responses
  baseURL: https://opencode.ai/zen/v1
  headers:
    authorization: Bearer unused # 由 fetch 补丁换成你的 key
  models:
    - id: muse-spark-1.3-contributor-free
      name: Muse Spark 1.3 Free
      contextWindow: 1048576
      maxTokens: 131072
      input: [text, image]

这条路由由插件替你注册,所以上面那段配置是可选的。responses-provider.ts 会在进程内挂载宿主自己的 llm-pi-ai:隔离 authorization 与 settings 两个接缝,并在一层只转发 adapter 注册的 llm 门面之后挂载——因此它既不会重复登记整个 catalog 目录、settings 命名空间,也不会多注册任何一条 sign-in flow。这一版是对着真实 harness 验证的,不是桩:路由注册成功,宿主自己的 41 条 catalog 与 41 条 sign-in flow 原封不动,卸载时该路由被撤回。

这些路由继承你的凭据:每一条都沿用你 opencode 路由已经声明的同一个 apiKeyEnv,所以自定义的 key 引用不需要你再说一遍。你在 profile 里已经声明过的路由会被尊重——插件不会覆盖你的模型列表。

若宿主没有已加载的 llm-pi-ai 条目,插件不会注册任何东西,只会在日志里说明;此时请在 profile 里显式声明该路由。

4. 覆盖的执行模式

模式 补丁做什么
交互式多轮对话 通过每会话稳定的 ses_… id 提供 KV 提示缓存亲和
子代理与分叉 子、父会话都参与哈希;谱系由父级请求头携带
Agent 团队 跨编排轮次保留共享工作区的归属信息
实验性 Auto Review 即使后台审计调用省略 sessionId,仍会得到确定性的轮次状态、请求头与工具回退

5. 目录:合并了什么,谁说了算

模型清单由四个来源拼成,每个来源回答的是不同的问题:

来源 回答
内置 shim 一次网络请求都还没有时能提供什么——冷启动、离线
models.dev 规范参数、价格与显示名
网关的 /models 这个账号实际能用什么
发现装饰 discoverModels 该回答什么

合并是增量式的:适配器给的行保留,规范行只在缺失时追加,只有当按 provider 记录的退役名单点名某个模型时才省略它——那是事实,不是猜测。显示名与价格取自 models.dev,因为网关列表只给 id,别的几乎没有。

有两点你在使用中会注意到。冷启动永远不为空——内置 shim 在第一次刷新前就能回答,选择器立刻有模型可选。而且models.dev 里没有条目的模型照样出现,费率位置显示 —:既不猜,也不会退回去显示上一个模型的名字。

两个生成文件都在 CI 里校验(catalog:shim、limits:shim);厂商数据变动时它们以非零码退出——这是唯一能发现 models.dev 在你背后变了的办法。

→ 协议路由、合并与界面的技术细节: docs/protocol-routing-and-merge.md · English

🖥 界面

两个界面,同一个读数。两者都是纯视图,数据来自宿主。

界面 位置 显示什么
计量表触发按钮 输入框停靠栏,模型选择器旁 Go:圆环 + 该窗口的百分比。Zen:本会话消费额,不画圆环
计量表面板 点击触发按钮 三个窗口、月度额度、本会话消耗、Zen 余额、操作链接
设置卡片 Settings → Plugins → OpenCode 补丁设置 八个控件,上方另有一份实时 Go 用量摘要

悬停触发按钮解释它自己的数字;点击打开面板。下面逐节展示每个界面,逐状态的行为见 docs/quota-meter.zh-CN.md。

设置卡片 — Settings → Plugins → OpenCode 补丁设置

三个分区共八个控件——都是用户真正会做出的决定。所有控件都基于平台自身的原语渲染(Switch、Tag、Button、宿主 token),每个配置项都带提示以及恢复默认的操作。

分区 控件 默认 作用
网关请求 恢复 User-Agent(默认开启) on 恢复官方 OpenCode CLI 的 User-Agent,使 Cloudflare WAF 校验通过
网关请求 注入客户端来源头(默认开启) on 在网关流量上注入 x-opencode-client(以及来源请求头集合)
网关请求 附带工作区项目标识(默认开启) on 用当前文件夹名标记 x-opencode-project;关闭则省略该请求头
模型与免费额度 使用 models.dev 补全模型列表(默认开启) on 将规范参数、显示名、价格与当前免费模型合并进列表以及原生 DSH 模型发现
模型与免费额度 自动补全核心工具(默认开启) on 补上免费层 /responses 请求体所需的 read + bash schema
配额计量 开启输入区用量计量(默认开启) on 在输入框停靠栏挂载实时计量表——Go 额度环,或 Zen 会话消耗
配额计量 显示会话消耗与模型费率(默认开启) on 追加显示本会话累计花费与当前模型每百万 Token 费率
配额计量 凭据来源 auto 多个密钥同时可用时以哪个为准:自动 · 优先实时请求 · 优先已声明密钥

覆盖类配置项(字面字符串、标记、路由列表)刻意只留在配置里,让默认值适用于所有有文档记载的配置——参见配置参考。

卡片在控件上方还带一份实时的 Go 用量摘要——和输入框计量表同一个读数,但用官方控制台的排版:每个窗口一行,剩余单独放在右侧。

Go 用量                                             状态正常
● 5 小时   0% 已用 · 4小时58分钟后重置              100% 剩余
● 每周    28% 已用 · 3天2小时后重置                  72% 剩余
● 每月    14% 已用 · 11月7日8点55分重置              86% 剩余
月度额度  mimo-v2.6-pro · Go $60 · Plus $120              ⓘ

「剩余」是百分比,不是美元数:/usage 每个窗口只给一个百分比、完全没有余额接口,而套餐档位也无法探测——任何美元余额都会是猜的。这一点由 ⓘ 说明。读不到配额时它明说读不到,并且不打印任何数字,而不是打一排会被当成真实读数的 0%。

输入框停靠栏计量表

计量表挂载在 conversation.composer.dock 中 DSH 原生 ContextMeter 旁。逐状态的行为说明(触发按钮的三种状态、两个面板、每一行的含义、三种「没有数字」的区别)见 docs/quota-meter.zh-CN.md。

┌─────────────────────────────────────────────────────────────────┐
│ Type a message...                                               │
│                                                                 │
│ [+] Attach                            [DeepSeek V4.1 Flash ⌄] [⬆]│
└─────────────────────────────────────────────────────────────────┘
   [ ⭕ 73% Context ]   [ ⭕ 42% Go Quota ]   ← when OpenCode Go is active
   [ ⭕ 73% Context ]   [ ⭕ $0.00 ]            ← 使用 OpenCode Zen 时

模式 A — OpenCode Go (opencode-go)

  • 自适应环:实时 SVG 环,显示能回答问题的那个窗口。已经用尽的窗口——受限或到达上限——优先显示,从宽到窄(每月 → 每周 → 5 小时),因为月上限能解释一次拒绝,而 5 小时窗口解释不了。否则显示 5 小时窗口:它最早重置,是还能行动的那个。免费模型上环形以灰色空心显示——套餐上限与一笔无法产生的账单无关,对着 $0.00 亮红色会被读成"你的钱用完了"。
  • 语义色:低于 80% 为绿色(--dsw-alias-state-success-primary),≥80% 为琥珀色(--dsw-alias-state-warn-primary),达到上限为红色(--dsw-alias-state-error-primary)。
  • 点击面板:三行窗口,各带实时重置倒计时与一条进度条;一行会话消耗,写的是当前选中的模型;一行 Zen 余额;一条受限告警;以及可直接处理的链接。悬停触发器显示 Tooltip,列出全部三个窗口(5 小时 11% · 每周 33% · 每月 16%)——触发器只能印一个数字,而下一个问题永远是"另外两个呢?"。
  • 目录里没有价格的模型显示 —,不是 Free。 面板始终显示选择器上的模型名,即使 models.dev 还没有它的条目(目录按计划同步)——显示上一个模型的名字比显示没有价格更糟。
┌──────────────────────────────────────────────┐
│  OpenCode Go                          [Go Plan]│
├──────────────────────────────────────────────┤
│  • 5 hours        ███████░░░░░░░░░    42% │
│    Resets 3h 12m                             │
│  • Weekly         ███░░░░░░░░░░░░░    18% │
│    Resets 5d 8h                              │
│  • Monthly        █████████████░░░    65% │
│    Resets 22d 4h                             │
├──────────────────────────────────────────────┤
│  月度额度                                    │
│  mimo-v2.6-flash              Go $60 · Plus $120│
├──────────────────────────────────────────────┤
│  当前会话消耗                                │
│  deepseek-v4.1-flash · $0.15 / $0.6 per 1M   │
│                                        $0.42 │
│  可用 Zen 余额                               │
│  请求将自动从 Zen 余额中扣除            就绪 │
├──────────────────────────────────────────────┤
│  ⟳ 更新于 08:30            升级套餐  控制台 ›│
└──────────────────────────────────────────────┘

月度额度,以及为什么它是总额而不是余额。 三个窗口是某个模型月度美元额度的比例——官方原话:"Usage limits are defined as monthly dollar amounts… 5-hour — 20% of the monthly limit; weekly — 50%; and monthly — 100%." 所以计量表把这笔额度打印在窗口下面:mimo-v2.6-flash 是 Go $60 · Plus $120。它由官方 Go 文档生成(pnpm run limits:shim,CI 定时校验),因为页面明说*"usage limits may change"*——手写表一个版本内就会过期,而过期的美元数字看起来很权威。

它同时显示两个档位而不是只显示一个,因为档位查不到:/limits、/plan、/subscription、/account、/credits 全部 404,/models 也没有额度字段。猜一个档位,金额就会差 2–3 倍。

它刻意不是剩余额度。GET /zen/go/v1/usage 不接受 model 参数,所以返回的百分比是账号级的,而额度是按模型的——两者相乘得到的数字没有指代对象。真实余额在控制台,那里是准确的。

模式 B — OpenCode Zen (opencode)

Zen 路线上不出现任何 Go 的数字:根本不画圆环(没有可量的量规就是装饰),标签是消费额而不是百分比,Go 套餐被限流也不会把 Zen 按钮染成告警色,悬停则带上价格(当前会话消耗 $0.00 · 按量计费)。Go 的窗口是关于一条本路线永不结算的套餐的事实,所以在这里不出现。

  • Zen 触发器:标签是本会话累计花费(未计价时为 $0.00,计价后如 $0.42),且不画圆环——圆环是量规,而 Zen 没有可量的窗口。这个数字是唯一能拿到的真实数字:OpenCode 的 Zen 余额只能通过 console 的 server action 读取,API 密钥读不到。
  • 按量计费面板:带 Pay-as-you-go 徽标的头部、会话消耗行(价格开关开启时),以及一个链接——OpenCode 控制台,标为充值:按量计费的人点进去就是要充钱,而充值本身没有独立的地址。
┌───────────────────────────────────────────────┐
│  OpenCode Zen                [Pay-as-you-go]  │
├───────────────────────────────────────────────┤
│  Session Spend                                │
│  Space Bunny Free · 免费               $0.00│
├───────────────────────────────────────────────┤
│  ⟳ 更新于 08:30                      控制台 ›│
└───────────────────────────────────────────────┘

Zen 余额与溢出。 若配置了 OPENCODE_API_KEY(或 oc_sk_…),Zen 按量计费会被自动检测。Zen 面板不显示余额行:该余额只能通过 console 的 server action 读取,需要浏览器会话,因此面板直接链接到 OpenCode 控制台,而不是冻结一个无法保持最新的数字。余额行放在 Go 面板上——那里它回答的是一个面板真能回答的问题:超出配额的 Go 请求是否真的会从该余额扣费。


⚙ 配置参考

仅配置项 (cordis.patch.yml)

这些项存在于 schema 中,却不渲染任何控件——每一项都是字面值、标记或引用,其默认值适用于所有有文档记载的配置。它们仍可在该行的 config 中编辑;cordis.patch.yml 是参考。

配置项 默认值 保留在配置中的原因
providers opencode, opencode-go, opencode-responses, opencode-anthropic 要拦截的路由 id;必须覆盖本层声明的每一条路由
gatewayUrls opencode.ai/zen 标记网关流量的 URL 子串;只有镜像或中继才会改它们
userAgent 空(= 规范 CLI UA) 字面覆盖;默认值就是网关所期望的值
originClient cli x-opencode-client 的字面值
sessionIdEnv OPENCODE_SESSION_ID 指定一个仅当调用方提供会话 id 时才使用的环境变量名
freeModelMarker free 模型 id 子串;* 强制启用回退,'' 关闭回退
usageBaseURL https://opencode.ai/zen/go/v1 端点覆盖;从组合中自动发现
debug / debugFile false / — 诊断用 JSONL 日志,不是谁会在 UI 里调节的行为
# cordis.patch.yml — the plugin's row; every key is optional.
- insert:
    - id: dsh-opencode-patch
      name: "dsh-opencode-patch"
      config:
        providers:
          - opencode
          - opencode-go
          - opencode-responses
          - opencode-anthropic
        gatewayUrls:
          - opencode.ai/zen
        sessionIdEnv: "OPENCODE_SESSION_ID"
        freeModelMarker: "free"
        usageBaseURL: "https://opencode.ai/zen/go/v1"
        keySource: "auto" # auto | request | configured
        injectUserAgent: true
        injectOriginHeaders: true
        originClient: "cli"
        injectProject: true # attach workspace folder (or 'global'); false omits the header
        injectCoreTools: true
        enrichModels: true # merge models.dev specs + active free models into listings
        usageEnabled: true # off = no meter and no price row
        # File-level only debug options:
        debug: false
        debugFile: "/tmp/dsh-opencode-debug.jsonl"

宿主重载规则: 任何宿主变更(lib/index.mjs)之后都要重启 dsh web;客户端 UI 变更(lib/client.js)刷新浏览器即可。


🛠 故障排查

症状 可能原因 解决方法
免费模型报 403 FreeTierError 网关请求头被剥离,或缺少工具定义 保持恢复 User-Agent、注入客户端来源头与自动补全核心工具开启
400 MissingSessionID 未附带会话请求头 确认 dsh-opencode-patch 已列入 profile 的 dsh.profile.bundles
Auto Review 报 TRANSPORT: Connection error 更新后宿主进程未重启 停止并重启 dsh web,让新的 lib/index.mjs 加载
Go 模型始终不显示额度环 解析不到 Go 凭据,或当前激活的网关不是 opencode-go 在 DSH Credentials 中存储 OPENCODE_GO_API_KEY,并经 opencode-go 路由
目录中的模型在 Settings → Fetch 里缺失,或已下线的模型仍然存在 宿主模块过期、补全关闭,或适配器用自带目录作答 pnpm run build、重启 dsh web、保持使用 models.dev 补全模型列表开启、从匹配的路由获取、搜索精确 id(如 space-bunny-free)
弹出面板以红色显示“已达限额” 滚动 / 每月窗口已用到 100% 打开 OpenCode 控制台并启用 使用余额(Use balance),以溢出到 Zen 额度
非 OpenCode 模型行为异常 与本补丁无关 发往其他网关的流量原样通过

📜 兼容性与验证

已在 DeepSeek Harness 0.2.0-rc.2(Node 24+)上验证

层面 目标
插件包 dsh-opencode-patch
宿主 profile DSH Web profile(patchReload: live)
声明的路由 opencode、opencode-go、opencode-responses、opencode-anthropic
网关 opencode.ai/zen/v1(/responses、/chat/completions、/messages、:streamGenerateContent)、zen/go/v1(/chat/completions)
支持的模型 claude-sonnet-4-5、gpt-5.4、gemini-3.8-flash、deepseek-v4.1-flash、muse-spark-1.3-contributor-free、qwen3.8-flash
验证关卡 vp check 干净、266 个确定性测试全绿、完整 schema 校验、消费者安装 + 加载(scripts/check.ts)

🔍 深入解析

请求头注入矩阵、会话谱系、工作区归属、API 层级与目录内部实现,都在 docs/deep-dive.md。本 README 只讲你看得见的界面和要配的东西。

👥 署名与许可

灵感来自 @nobu121 的 nobu121/dsh-opencode-session,该项目开创了 OpenCode on DSH 的会话 ID 处理。本插件由 @viztor 独立实现:沿用了这个思路,其余部分都是重写的——Zen 免费层网关兼容、分层子代理谱系、动态工作区项目归属、实时双模式 Go 额度与 Zen 余额监控,以及原生 Web UI 集成。

链接: npm · 仓库 · 问题 · 更新日志 · 贡献指南 · OpenCode · models.dev

以 MIT License 授权。