dsh-opencode-patch
已验证@viztor/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 @viztor/dsh-opencode-patch 用 dsh --profile default --dump-config 确认 layer 已生效 —— 参见安装指南。
源码
标签
说明文档
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)原样通过。
三条主线。
- 最小侵入。 不做全局补丁:只拦截本插件声明的路由,其他厂商——DeepSeek、OpenAI、Anthropic、GitHub——的请求原样通过。计量器离"彻底消失"只差一个开关,而不是离"勉强忍受"隔着一个页面。
- 原生 DSH 插件。 一个
apply()、一张绑定自己命名空间的设置卡、用ctx.inject声明服务、按宿主自己的方式填充插槽,文案随宿主语言切换。没有私有钩子,不改宿主代码。 - 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. 安装到你的 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 没有对应协议,选了只会失败,而且行里没有任何东西能告诉你原因。 - 一份凭据。 所有路由模型共用你已配置的
opencodekey,经凭据服务解析,不会重新问你要。
如果你更想自己声明路由: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 授权。