🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

OpenCode 免费模型接入 DragonHorizon-xtihc/dsh-connect-opencode-free

DeepSeek Harnessv0.2.0
DragonHorizon-xtihc17 天前有提交已提供中文文档扫描:含敏感能力

把 OpenCode Zen 的免费模型接进 DeepSeek…

30.7/100
综合分
1
GitHub 星标
195
npm 周下载
—/5
用户评分

数据截至 2026/10/15 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-connect-opencode-free
npm 源 · dsh-connect-opencode-free 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · model
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功(非静态推断);本次验证针对插件 v0.4.0,当前版本为 v0.2.0
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 17 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-connect-opencode-free @ 0.2.0
✓Node 引擎要求 >=18 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/13 09:39:42

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 34 处,展示前 34 条)
  • 读取本地文件
    lib/index.js:88
    return JSON.parse(readFileSync(path, 'utf8'))
  • 写入本地文件
    lib/index.js:96
    mkdirSync(join(path, '..'), { recursive: true })
  • 写入本地文件
    lib/index.js:97
    writeFileSync(path, `${JSON.stringify(value)}\n`, 'utf8')
  • 发起网络请求
    lib/client.cjs:701
    const response = await fetch(`http://127.0.0.1:${firstPort + offset}/catalog`, {
  • 发起网络请求
    lib/client.cjs:728
    const response = await fetch(
  • 发起网络请求
    lib/discovery.js:48
    const response = await fetch(url, {
  • 读取本地文件
    lib/quota.js:37
    return JSON.parse(readFileSync(path, 'utf8'))
  • 写入本地文件
    lib/quota.js:45
    mkdirSync(dirname(path), { recursive: true })
  • 写入本地文件
    lib/quota.js:46
    writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, 'utf8')
  • 读取本地文件
    lib/quota.js:296
    return existsSync(path)
  • 读取本地文件
    lib/availability.js:69
    const parsed = JSON.parse(readFileSync(this.file, 'utf8'))
  • 写入本地文件
    lib/availability.js:78
    mkdirSync(dirname(this.file), { recursive: true })
  • 写入本地文件
    lib/availability.js:79
    writeFileSync(this.file, `${JSON.stringify({ savedAt: Date.now(), entries: this.entries })}\n`, 'utf8')
  • 发起网络请求
    lib/keycheck.js:112
    response = await fetch(url, {
  • 执行外部命令来自配置/变量
    lib/gate-tools.js:34
    exec(command, {
  • 读取本地文件
    lib/gate-tools.js:92
    const text = await readFile(path, 'utf8')
  • 发起网络请求
    lib/provider.js:1375
    if (!this.egressEnabled()) return fetch(url, init)
  • 发起网络请求
    lib/provider.js:1384
    return fetch(url, init)
  • 读取本地文件
    lib/oc-session.js:69
    if (!existsSync(path)) {
  • 读取本地文件
    lib/zen-egress.js:78
    if (path && existsSync(path)) return { command: path, env: {}, label: `bun:${path}` }
  • 读取本地文件
    lib/zen-egress.js:81
    if (path && existsSync(path)) {
  • 执行外部命令来自配置/变量
    lib/zen-egress.js:144
    const child = spawn(runtime.command, [RUNNER], {
  • 发起网络请求
    lib/zen-egress.js:227
    return await fetch(`http://127.0.0.1:${port}/`, { ...init, headers })
  • 读取本地文件
    lib/zen-prompt.js:32
    cachedPrompt = readFileSync(PROMPT_FILE, 'utf8')
  • 执行外部命令来自配置/变量
    lib/oc-bridge.js:425
    const child = spawn(this.command, args, {
  • 发起网络请求
    lib/oc-bridge.js:450
    const res = await fetch(`${base}/api/info`, {
  • 发起网络请求
    lib/oc-bridge.js:514
    await fetch(`${base}/session/${session}`, {
  • 发起网络请求
    lib/oc-bridge.js:540
    await fetch(`${base}/session/${session}/interrupt`, {
  • 发起网络请求
    lib/oc-bridge.js:582
    const res = await fetch(`${base}${path}`, {
  • 发起网络请求
    lib/oc-bridge.js:591
    const res = await fetch(`${base}${path}`, {
  • 发起网络请求
    lib/zen-egress-runner.mjs:46
    async fetch(request) {
  • 发起网络请求
    lib/zen-egress-runner.mjs:67
    const upstream = await fetch(url.toString(), {
  • 发起网络请求
    src/client.js:299
    const response = await fetch(`http://127.0.0.1:${firstPort + offset}/catalog`, {
  • 发起网络请求
    src/client.js:326
    const response = await fetch(
扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery@deepseek-ai/dsh-llm
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 接入免费模型
  • 逐个启停模型
  • 一键写入provider
展开完整介绍(92 字)↓

把 **OpenCode Zen** 的免费模型接进 **DeepSeek Harness**:实时发现官方免费档模型,在设置里逐个启停,一键写入 provider。**免 key**

README

由 DeepSeek 最新模型翻译生成
dsh-connect-opencode-free 把 OpenCode Zen 的免费模型接进 DeepSeek Harness:实时发现官方免费档模型,在设置里逐个启停,一键写入 provider。免 key,认证固定为 Bearer public。 OpenCode Zen 是第三方公开服务,其可用性、模型清单与限流策略由其运营方决定。本插件只是客户端适配层。 功能 - 免 key 接入 OpenCode Zen 免费档:匿名通道复用公开额度,认证固定为 Bearer public;为通过免费档校验,适配器会伪装成 OpenCode CLI(特定的 User-Agent、x-opencode-session 会话 id,以及 bash/read 工具名)。 - bash/read 是真工具,不是占位:免费档网关要求请求里必须出现 bash 与 read 两个名字,只挂名字不实现会让模型去调一个永远无法执行的工具、整轮被带偏(这正是同一模型在 OpenCode 里流畅、走本插件却报错的原因)。因此插件把这两个名字注册成真实可执行的工具(见 lib/gate-tools.js)。
展开完整文档(README 剩余 1 节)
dsh-connect-opencode-free

把 OpenCode Zen 的免费模型接进 DeepSeek Harness:实时发现官方免费档模型,在设置里逐个启停,一键写入 provider。免 key,认证固定为 Bearer public。

OpenCode Zen 是第三方公开服务,其可用性、模型清单与限流策略由其运营方决定。本插件只是客户端适配层。

功能

- 免 key 接入 OpenCode Zen 免费档:匿名通道复用公开额度,认证固定为 Bearer public;为通过免费档校验,适配器会伪装成 OpenCode CLI(特定的 User-Agent、x-opencode-session 会话 id,以及 bash/read 工具名)。
- bash/read 是真工具,不是占位:免费档网关要求请求里必须出现 bash 与 read 两个名字,只挂名字不实现会让模型去调一个永远无法执行的工具、整轮被带偏(这正是同一模型在 OpenCode 里流畅、走本插件却报错的原因)。因此插件把这两个名字注册成真实可执行的工具(见 lib/gate-tools.js)。
- 双源模型发现:实时合并 opencode.ai/zen/v1/models(此刻真实存在哪些 id)与 models.dev/api.json 的 opencode 段(哪些免费、上下文多大、支持什么能力),任一源不可达仍沿用上次结论,一次网络抖动不会让模型凭空消失。
- 设置页一键管理:搜索、逐个启停、全部启用/停用、限流状态与恢复时间一目了然。
- 三档使用规则:默认仅使用公共额度,配了 Key 也不动用;另外两档(公共优先 / 私有优先)需要先通过 Key 校验才能选用。详见使用规则。
- 一键写入 provider:把当前免费模型列表写入 llm-pi-ai.providers.opencode-zen(也支持移除与手动补模型)。
- 额度与重试:429 区分「公共额度」与「自有账号」记账、Key 轮换、退避重试。
- 流式健壮性:工具调用块边界、推理档位、结束原因兜底;以及 wire 边界的防御——工具结果按 role:tool 序列化、空 arguments 归一为 {}、压缩/续接后孤立的工具结果改标为 user 而不发非法请求。

架构

| 半边 | 文件 | 职责 |
| 共享纯逻辑 | lib/core.js | 模型集合合并、provider 条目生成、命名空间解析。无依赖,宿主与浏览器共用 |
| 运行时配置 | lib/config.js | schemastery Config,除缓存目录外全部 volatile |
| 模型发现 | lib/discovery.js | 合并 opencode.ai/zen/v1/models 与 models.dev/api.json |
| 额度与密钥 | lib/quota.js | 429 记账与恢复时间估算、密钥解析(凭据库优先、环境变量兜底)、公共通道冷却、缓存读写 |
| Key 校验 | lib/keycheck.js | 用一个最小请求证明 Key 被 Zen 接受;按指纹持久化,不存 Key 本身 |
| Key 录入 | lib/core.js + lib/index.js | 行号 → 环境变量名的映射(apiKeyNameFor)与注入/撤回 |
| 宿主 | lib/index.js | 注册 opencode-free 路由、周期发现、目录缓存、本机目录桥 |
| 适配器 | lib/provider.js | 免 key 的 OpenAI 兼容 adapter:流式块边界、工具调用、推理档位、退避重试 |
| 浏览器 | src/client.js | React 设置页,注册进 settings.section 槽位 |

浏览器半边的产物 lib/client.cjs 由 scripts/build.mjs 从 lib/core.js + src/client.js 生成 —— 客户端模块系统的 require 只能解析包名和平台种子词,无法解析相对路径,所以共享逻辑必须内联。构建脚本还会拒绝任何未被内联的相对 import。

安装

本插件是一个 DSH cordis 插件包(bundle),通过 profile 的 package.json 接入。

方式一:npm 安装(推荐)

1. 进入目标 profile 目录(如 ~/.dsh/profiles/<profile-name>/)。
2. 在 package.json 的 dependencies 中加入: "dsh-connect-opencode-free": "^0.4.0"

3. 在 dsh.profile.bundles 数组中加入包名: "dsh-connect-opencode-free"

4. 在该 profile 目录执行安装:
pnpm install

5. 重启 dsh(如 dsh web),打开「设置 → OpenCode 免费模型」即可使用。

方式二:从源码运行(开发态)

1. clone 本仓库到本地。
2. 在 profile 的 package.json 的 dependencies 中用 link: 指向源码绝对路径: "dsh-connect-opencode-free": "link:/absolute/path/to/dsh-connect-opencode-free"

3. 同样在 dsh.profile.bundles 数组中加入包名。
4. 执行 pnpm install,重启 dsh。

依赖要求:Node ≥ 18,且宿主提供 @deepseek-ai/dsh-llm(peer 范围 >=0.1.7-rc.1 <0.3.0)。cordis.patch.yml 由 package.json 的 dsh.bundle.patch 自动加载,无需手动处理。

提示:宿主半边的 lib/ 为源码,lib/client.cjs 是 npm run build 生成的浏览器半边产物;从源码运行前请先执行 npm run build。

模型发现

目录由两个数据源合并,任一单独都不够:

- https://opencode.ai/zen/v1/models —— 哪些 id 此刻真的存在;
- https://models.dev/api.json 的 opencode 段 —— 哪些免费、上下文多大、支持什么能力。

免费与否由 models.dev 决定(cost 全 0),供应与否由 Zen 决定。一个源不可达时沿用上次的判断(冷启动则按免费目录推定并在设置页注明),因此一次网络抖动不会让模型凭空消失。目录缓存写在 <DSH_HOME>/cache/opencode-zen-catalog.json。

Zen 的模型接口不返回 Access-Control-Allow-Origin,浏览器读不到,所以发现动作在宿主完成,再由一个仅监听 127.0.0.1 的目录桥交给设置页(端口可配,被占用时自动顺延,响应带标记头并通过 Access-Control-Expose-Headers 暴露)。

请求

- 块边界:上游只发扁平 delta,从不声明块的起止,所以边界由适配器决定——任何时刻只开一块:思考一旦有正文就闭合,工具一开始就把思考与正文都闭合。三个 token 计数互斥:缓存命中从输入里扣除,推理 token 单列。
- 结束原因:网关经常不给 finish_reason(工具调用帧尤其如此),缺省时按这一轮是否产出过工具调用判定。
- 推理档位:目录标为 reasoning 的模型声明 off/low/high/max 四档,选择器据此提供选项,显式指定的档位才不会被 llm 服务以 UNSUPPORTED_REASONING_EFFORT 拒绝;off 用省略字段表示,因为 Zen 不接受该取值。不设默认档,保留 Zen 自己的默认。
- 额度顺序:由 quotaPolicy 决定(详见使用规则),默认 publicOnly——只用公共免费额度。每个 Key 名先查 Harness 凭据库(设置里保存的 key 在这里,不在环境变量里),再查进程环境。
- 失败提示:429 区分「公共额度」与「自有账号」,401/403 说明是凭据被拒还是没给,并给出下一步(去哪取 key、检查哪个账号),而不是只抛一个状态码。两条额度都用尽时,报第一个失败的 Key 而不是最后一个——要修的是它。

设置

打开「设置 → OpenCode 免费模型」。所有字段 volatile,改完立即生效,不需要重启。

| 字段 | 默认值 | 说明 |
| enabled | true | 总开关;关闭后所有免费模型撤下 |
| zenBaseURL | https://opencode.ai/zen/v1 | Zen 接口根 |
| catalogURL | https://models.dev/api.json | 模型目录 |
| autoSync | true | 持续发现 |
| pollIntervalMs | 3600000 | 发现间隔 |
| catalogTimeoutMs | 20000 | 目录请求超时 |
| includeDeprecated | false | 是否列出已下架的免费模型。默认不列出任何已下架的:models.dev 标记弃用的,以及 Zen 已下架的。勾选后两者都列出,但 Zen 已下架的仍标记为不可用,勾了也无法调用 |
| apiKeys | [] | 私有额度 Key 列表,逐行填值即可;保存后按 OPENCODE_API_KEY、OPENCODE_API_KEY_1… 自动写入进程环境变量,删行即撤回 |
| apiKeyEnv | '' | 高级:自管密钥时填已有环境变量名(逗号分隔轮换),仅在 apiKeys 为空时生效 |
| quotaPolicy | publicOnly | 使用规则:publicOnly 仅用公共额度;preferPublic 公共优先、被拒时回退私有;preferKey 私有优先、失败时回退公共。后两者需先通过 Key 校验 |
| maxRequestAttempts | 10 | 单次请求最多尝试次数(429 与所有 5xx 都会退避重试;上限 20) |
| retryBackoffMs | 800 | 重试初始退避,按次数指数增长 |
| defaultContextWindow | 200000 | 目录未给大小时的兜底 |
| realGateTools | true | 把网关要求的 bash/read 注册成真实可执行工具;关闭后它们只是占位,模型调用会被忽略并提示(即时生效,无需重启) |
| disabledModels | [] | 停用的模型 id |
| manualModels | [] | 手动添加的模型 |
| bridgePort | 18766 | 目录桥端口 |
| cachePath | '' | 缓存文件;留空用 <DSH_HOME>/cache/ |
| debug | false | 宿主输出详细日志 |

页面提供:搜索、逐个启停、全部启用/停用、一键写入 llm-pi-ai.providers.opencode-zen、移除 provider、手动添加模型、公共额度的限流/被拒状态与恢复时间、三档使用规则选择,以及逐行录入的 Key 列表与校验按钮。

使用规则:三档额度策略

「设置 → OpenCode 免费模型」底部的 使用规则 决定一次调用可以花谁的额度。默认是 仅使用公共额度——本插件的意义是免 key,所以一个恰好躺在环境变量里的 Key 不会自动变成所有调用的计费对象。

| 规则 | 值 | 顺序 | 需要 Key 校验 |
| 仅使用公共额度 | publicOnly | 只用公共额度,配了 Key 也不动用 | 否(默认) |
| 优先使用公共额度 | preferPublic | 公共 → Key1 → Key2 → … | 是 |
| 优先使用私有额度 | preferKey | Key1 → Key2 → … → 公共 | 是 |

后两档必须先通过 Key 校验才可选用。校验方式是向 Zen 发一个最小请求,只问一个问题——「这个 Key 被接受了吗」。只有 401/403 算不通过;400(模型/参数问题)、429(该账号限流)都发生在鉴权之后,因此反而证明 Key 是真的。校验结果带 Key 的指纹持久化(不存 Key 本身),换了 Key 自动失效,需要重新校验。

未校验通过时选了后两档,Host 会按 publicOnly 处理并在日志里说明原因——设置页可能是旧的、只读的、被绕过的,所以这道闸门由 Host 把守,不靠 UI 自觉。

规则一:仅使用公共额度(默认)

第 1 次尝试:公共免费额度(Bearer public,免 key) └─ 成功 ──────► 返回 └─ 失败 ──────► 报错,不动用任何 Key

429 与 5xx 仍会按退避重试公共额度(等待可能让额度恢复),但永远不会落到 Key 上。

规则二:优先使用公共额度

第 1 次尝试:公共免费额度 ├─ 成功 ───────────────────────► 返回(不消耗任何 Key) ├─ 429 公共额度已用尽 ──────────► 回退到 Key ├─ 403 拒绝了匿名免费档调用 ────► 回退到 Key └─ 其它错误(400/401/404…)────► 直接失败,不动用 Key

1. 只有两种回答会解锁 Key:429 和 403 FreeTierError。其它错误在付费通道上会以同样方式失败(例如模型 id 不存在),花你的额度重发一遍只买到同一个错误。
2. 已知关闭就不再空跑。 公共额度被限流时记下恢复时间,被拒时记 10 分钟冷却;期间下一次调用直接从 Key 开始,省掉一次注定失败的往返。窗口一过自动回到公共额度优先,不需要任何操作。

规则三:优先使用私有额度

第 1 次尝试:Key1 → Key2 → … → 公共免费额度

只有 Key 说「我不能服务」时才交棒:429(该账号额度用尽)、401/403(Key 无效或被拒)。模型不存在之类的 400 同样不会回退——公共额度也会给出同一个错误。

三档共用的规则

- 多 Key 轮换。 列表里第 N 个 Key 会按 OPENCODE_API_KEY、OPENCODE_API_KEY_1… 轮换使用;只有真正动用到 Key 的请求才推进轮换起点,被公共额度服务的请求不会打乱它。
- 一轮就是预算。 一次调用最多把当前规则序列里的凭据各试一次;此后只有可重试的错误(429/5xx)才值得再等一轮,避免在同一个答案上空转。
- 两条都失败时报 Key 的错误。 公共额度不可用你已经知道了,需要修的是 Key;报错会带上出事的凭据名,多 Key 时不会被最后一个误导。

配置私有额度 Key

Key 是可选的。公共额度能用的时候不需要它。

1. 到 <https://opencode.ai/zen> 领取 Key。
2. 打开「设置 → OpenCode 免费模型」,在 私有额度 Key 一栏点「添加 Key」,逐行粘贴 Key 的值——不需要配置环境变量,变量名由行号自动生成(第 1 行 OPENCODE_API_KEY,第 2 行 OPENCODE_API_KEY_1,依此类推),显示在每行左侧,只读。
3. 保存即生效:插件把每行的值写进进程环境变量(重启后由配置重新注入,不会丢),并立即刷新凭据池。
4. 点 「校验 Key」。通过后,后两档规则才变为可选;设置页会显示「已配置 N 个 Key · 当前规则:X」。

为什么要这样做:早先版本要求使用者自己建环境变量再重启进程。两步都容易漏——尤其是重启,改完环境变量不重启,运行中的进程根本读不到,插件会一直认为"没有 Key",校验按钮也就一直点不动。现在 Key 存在插件配置里,环境变量只是它的投影,改了立刻可见。

Key 值以明文存在 profile 的配置补丁中(与宿主凭据库同等级别)。若你的部署已有自己的密钥管理,可改用高级字段 apiKeyEnv:填上已有的环境变量名(逗号分隔),它只在 Key 列表为空时生效,两者不会同时生效。

删除某一行会同时撤回对应的环境变量,不会留下一个页面已删、进程仍在用的 Key。

报错:「Key 校验未通过:Key 未被接受(HTTP 403)」

Key 校验未通过:Key 未被接受(HTTP 403):
{"type":"error","error":{"type":"FreeTierError", …}}

这通常不代表 Key 是错的。 Zen 网关在读取凭据之前就会检查请求是否像 OpenCode CLI 发出的;没通过就返回 403 FreeTierError,而它对「Key 错误」和「请求形态不对」给的是同一个错误。校验探针必须同时带上四个信号(2026-10-14 实测,缺任意一个都变回 403):

| # | 信号 | 由谁提供 |
| 1 | User-Agent 以 opencode/ 开头 | 插件固定伪装 |
| 2 | x-opencode-session 是真实的会话 id | 读本机 opencode.db,不是伪造 |
| 3 | body 的 tools 含 bash / read | 插件注入 |
| 4 | stream: true | OpenCode CLI 从不发非流式请求 |

第 4 条尤其隐蔽:探针原本用 stream: false「只要状态码不要正文」,看起来更省,实际直接被网关判成非 CLI 流量。现在的探针与真实请求走完全相同的构造。

若四个信号都在(当前版本即如此)仍然 403,那才是 Key 真的有问题。独立复核: sh
node scripts/verify-key-fix.mjs oc_sk_你的key

三个模型全 PASS 即 Key 有效。

另一个方向的误解也要避免:公共额度能不能用,与 Key 无关。公共通道只靠前三个信号(它本来就没有 Key)。若公共额度报同一个 403,去查会话与伪装,不要去翻 Key。

报错:OpenCode Zen 拒绝了匿名免费档调用(HTTP 403)

OpenCode Zen 拒绝了匿名免费档调用(HTTP 403 FreeTierError): OpenCode's free tier can only be used from within OpenCode. …
(本次请求:session 来源=cache,网关工具=bash+read 齐全,工具总数=247)

这句话的真实含义是:网关没把这次请求认成 OpenCode CLI 发起的。它同时放行必须满足三个条件,缺一个就 403:

| 条件 | 要求 | 本插件怎么满足 |
| User-Agent | 以 opencode/ 开头 | 匿名通道伪装成 CLI 的 UA |
| x-opencode-session | OpenCode 真实创建过的会话 id(ses_ + 26 位) | 从本机 OpenCode 的 SQLite 会话库读取最近一条 |
| 请求体的 tools | 必须含 bash 和 read | 把这两个名字注册成真实可执行的工具 |

排查顺序

括号里的诊断行就是为此准备的——它直接说明这次请求到底带了什么,不用猜:

- session 来源=无 → 本机读不到 OpenCode 会话库。跑一次 OpenCode CLI(例如 opencode run "hi")让它生成会话,插件会自动复用;或者干脆配置 Key。
- 网关工具=只有 bash / 都缺 → bash/read 没注册上(宿主工具服务不可用,或 realGateTools 被关)。把 realGateTools 打开并重启 dsh。
- 三项全绿仍 403 → 检查 User-Agent。宿主会叠加自己的归因头(deepseek-harness/…),若它与伪装的 UA 大小写不一致,两者会同时出现在请求里并在传输层互相覆盖 —— 2026-10-13 修的就是这个(lib/provider.js 的 assignHeader 现在按大小写不敏感替换,不再依赖拼写一致)。
- 以上都正常 → 公共免费额度通道确实不可用(运营方调整、来源限流)。这时才轮到 Key:按上面的步骤配置并通过校验,再把使用规则改成「优先使用公共额度」,插件就会在 429/403 时自动切过去。

仍想只用公共额度

保持默认的「仅使用公共额度」即可——这正是默认值,不需要配 Key 也不需要额外操作。此时 429/403 就是终点,插件不会去找 Key。

报错:设置页整页空白 / 「校验 Key」按钮不见了

页面整个区域是空的,但 Host 正常、模型照常调用,日志里只有一行:

[opencode-free] client page failed to load (host provider unaffected): TypeError: …

这是页面在渲染期抛了异常,被 apply 的 try/catch 吞掉后只剩这行日志——Host 半边完全不受影响,所以看起来"插件没坏,只是设置打不开"。渲染期抛错只有三种来源:

| 现象 | 根因 | 现状 |
| 整页空白 | 页面组件里一个 const 在声明前被读取(TDZ),渲染时抛 ReferenceError | 已修(2026-10-14)。npm run smoke 的 tdz-scan 静态扫描 + 渲染冒烟双保险守住 |
| 链接显示成 zenKey 这样的原始键名 | 页面请求了一个语言表里不存在的翻译键 | 已修(2026-10-14,zenKey 中英双语补全)。smoke-page.mjs 会逐个核对 |
| 改了页面源码、重启后仍是旧行为 | lib/client.cjs 是生成产物,重启不重建它 | 见开发:npm run build + 重启 + 浏览器硬刷新 |

最后一栏是这次实际踩的坑:TDZ 缺陷在 src/client.js 里早就修好了,但产物没重建,于是重启 dsh web 多少次都还是白屏,按钮自然也一直点不到。先 npm run smoke 确认三关全过,再重启。

报错:点任何复选框都要等一下才生效

已修(2026-10-14)。根因不在页面,页面整块重绘只要 0.09ms;慢在点击会牵连整个应用重载模型目录。

一次设置写入会触发 loader/volatile-update,插件原先在这里无条件调用 providerHandle.replace([PROVIDER])。这个 replace() 不是幂等的 setter:它删掉再重新注册路由,并广播 llm/adapters-updated。而这个广播有四个订阅者——模型选择器、设置页模型列表、子智能体列表、账号页——每一个都通过 ctx.remote.$on 跨线应答,重新拉一次完整目录。catalog.invalidate() 会丢弃 in-flight 请求,所以这些刷新不会被合并,点一个模型行的勾选框,全应用要重载四份目录。

现在有两道处理:

1. 只在路由真的变了才 replace()(routesChanged(),抽在 core.js 里有测试)。改一个模型的启停状态不再动路由表;只有「启用插件」开关本身会改,而它本就应该广播——暂停时路由要撤掉,否则模型选择器会留着旧列表。
2. 勾选框乐观回显。点击立刻改本地状态,Host 确认后对齐;写入被拒则弹回原值。这样即使写入要经过一次往返,手感也是即时的。

refreshKeyed()(重查凭据池)刻意没有加同样的守卫:凭据也可能在插件之外被改(账号页),跳过查询会让徽标描述一把已经不存在的 Key。它是 fire-and-forget,本来就不在点击路径上。

兼容性

浏览器半边的 inject 只有 ['slots', 'locale'] —— 这两个服务在任何 Host 版本上都存在。设置服务 configForms 通过 ctx.get() 软探测:Cordis 的依赖门是硬性的,声明一个当前 Host 没有提供的服务会让 apply 永不执行,页面直接消失。

早期版本曾 inject 已下线的 settingsScope,导致浏览器条目永远 pending,dsh web 以 1 entry did not activate 启动失败。现在已改用 configForms(profile-backed forms)。

开发

npm run build # 生成 lib/client.cjs
npm run verify # 校验产物是否最新bash
npm run smoke # 构建 + TDZ 扫描 + 设置页渲染冒烟
npm test # 单元测试

改设置页后必须重新构建,光重启无效

lib/client.cjs 是生成产物(scripts/build.mjs 把 lib/core.js + src/client.js 内联成浏览器工厂),浏览器加载的是它,不是 src/client.js。因此改完页面源码后:

1. npm run build(或 npm run smoke,它包含构建);

1. 重启 dsh web;
2. 浏览器硬刷新(Ctrl+F5),否则拿到的是缓存里的旧工厂。

只重启进程、不重新构建,页面会继续表现旧代码的行为——包括旧缺陷。已经因此吃过一次:页面空白的根因(一个 const 在声明前被读取)早已在 src/client.js 里修好,但产物没重建,重启多少次都还是白屏。

npm run smoke 守住的正是这类只在渲染期现形的缺陷:

- scripts/tdz-scan.mjs —— 静态扫描页面组件里「先读后声明」的局部变量。这类错误 node --check 看不见,单元测试也不导入页面,只会在渲染时抛 ReferenceError,表现为整页空白。扫描前会用一份还原了该缺陷的样件自证抓得到。
- scripts/smoke-page.mjs —— 用 mock 的 locale/slots/configForms 驱动真实产物,把页面在 6 种配置状态(空 Key、已填 Key、两种 Key 门禁规则、手动模型、弃用开关)下各渲染一遍,逐个核对页面请求的翻译键都存在于语言表里。

致谢

机制参考 randomix777/dsh-opencode-zen(双源发现、持久缓存、限流记账、流式块边界与失败提示、凭据库取 key)与 xiaozhe7772222/dsh-opencode-zen(429/5xx 退避、Key 轮换、推理档位),均为 MIT。

许可证

MIT
预览分享卡片长什么样 ↓DragonHorizon-xtihc/dsh-connect-opencode-free
分享 · DeepSeek Harness Hub
DragonHorizon-xtihc/dsh-connect-opencode-free

把 OpenCode Zen 的免费模型接进 DeepSeek…

回到顶部