OpenCode 免费路由 mirelconstantin/dsh-opencode-router
DeepSeek Harnessv1.1.1免密钥调用 OpenCode 免费模型,自动切换代理出口
31.4/100
综合分
2
GitHub 星标
31
npm 周下载
—/5
用户评分
数据截至 2026/10/15 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-opencode-routernpm 源 · dsh-opencode-router 的发布者账号已校验与本仓库一致
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · model
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 18 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包dsh-opencode-router @ 1.1.1
✓Node 引擎要求 ^22.19.0 || >=24.0.0 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/15 17:42:42
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 75 处,展示前 74 条)
- 执行外部命令来自配置/变量lib/index.js:856
const child = spawn(this.agentPath, this.args, { stdio: [ - 执行外部命令固定命令lib/index.js:1000
const killer = spawn("taskkill", force ? [ - 读取本地文件lib/index.js:1225
await stat(path); - 读取本地文件lib/index.js:1237
const existing = (await readFile(paths.tokenPath, "utf8")).trim(); - 写入本地文件lib/index.js:1241
await mkdir(dirname(paths.tokenPath), { recursive: true }); - 写入本地文件lib/index.js:1242
await writeFile(paths.tokenPath, token + "\n", { encoding: "utf8" }); - 写入本地文件lib/index.js:1243
if (platform !== "win32") await chmod(paths.tokenPath, 384).catch(() => {}); - 写入本地文件lib/index.js:1267
await mkdir(paths.dataDir, { recursive: true }); - 写入本地文件lib/index.js:1269
await writeFile(tmpPath, JSON.stringify(config, null, 2), "utf8"); - 删除本地文件lib/index.js:1270
await rm(paths.configPath, { force: true }); - 写入本地文件lib/index.js:1271
await rename(tmpPath, paths.configPath); - 发起网络请求lib/index.js:1799
const response = await fetch(providerBaseURL(port) + "/models", { - 发起网络请求lib/index.js:1814
const response = await fetch(`http://127.0.0.1:${port}/healthz`, { signal: controller.signal }); - 读取本地文件lib/index.js:2104
for (const sibling of [join(here, "..", "..", "..", "legacy", "agent", exe), join(here, "..", "..", "legacy", "agent", exe)]) if (existsSync(sibling)) - 发起网络请求lib/catalog-DdaxGYNb.js:428
const response = await withTimeout(this.#fetch(this.#metadataUrl, { headers: { accept: "application/json" } })); - 写入本地文件lib/catalog-DdaxGYNb.js:574
await mkdir(dirname(path), { recursive: true }); - 写入本地文件lib/catalog-DdaxGYNb.js:575
await writeFile(tmp, JSON.stringify(cache), "utf8"); - 删除本地文件lib/catalog-DdaxGYNb.js:576
await rm(path, { force: true }); - 写入本地文件lib/catalog-DdaxGYNb.js:577
await rename(tmp, path); - 读取本地文件lib/catalog-DdaxGYNb.js:580
const raw = JSON.parse(await readFile(path, "utf8")); - 读取本地文件lib/messages-CLaBfusR.js:69
data: (await readFile(path)).toString("base64"), - 发起网络请求lib/admission-BKoqnnTQ.js:623
const echo = await withTimeout(deps.undici.request(ipEcho, { dispatcher: agent }), timeout, "probe"); - 发起网络请求lib/admission-BKoqnnTQ.js:704
const request = (url, init = {}) => withTimeout(deps.undici.request(url, { - 发起网络请求lib/ip-pool-C9FThTdV.js:1139
const report = parseSubscription(await this.#fetch(url)); - 发起网络请求lib/ip-pool-C9FThTdV.js:1208
async #fetch(url) { - 读取本地文件lib/ip-pool-C9FThTdV.js:1484
if (existsSync(bin)) return bin; - 执行外部命令来自配置/变量lib/ip-pool-C9FThTdV.js:1488
const child = spawn(bin, ["version"], { stdio: "ignore" }); - 写入本地文件lib/ip-pool-C9FThTdV.js:1504
await mkdir(this.#dataDir, { recursive: true }); - 写入本地文件lib/ip-pool-C9FThTdV.js:1506
await writeFile(tmp, JSON.stringify(config, null, 2), "utf8"); - 删除本地文件lib/ip-pool-C9FThTdV.js:1507
await rm(this.#configPath, { force: true }); - 写入本地文件lib/ip-pool-C9FThTdV.js:1508
await rename(tmp, this.#configPath); - 执行外部命令来自配置/变量lib/ip-pool-C9FThTdV.js:1510
const child$1 = spawn(binary, [ - 执行外部命令来自配置/变量lib/ip-pool-C9FThTdV.js:1521
this.#child = spawn(binary, [ - 执行外部命令固定命令lib/ip-pool-C9FThTdV.js:1579
const killer = spawn("taskkill", [ - 执行外部命令固定命令lib/ip-pool-C9FThTdV.js:1592
const killer = spawn("taskkill", [ - 发起网络请求src/adapter/catalog.ts:384
const response = await withTimeout(this.#fetch(this.#metadataUrl, { headers: { accept: 'application/json' } })) - 写入本地文件src/adapter/catalog.ts:555
await mkdir(dirname(path), { recursive: true }) - 写入本地文件src/adapter/catalog.ts:556
await writeFile(tmp, JSON.stringify(cache), 'utf8') - 删除本地文件src/adapter/catalog.ts:557
await rm(path, { force: true }) - 写入本地文件src/adapter/catalog.ts:558
await rename(tmp, path) - 读取本地文件src/adapter/catalog.ts:562
const raw = JSON.parse(await readFile(path, 'utf8')) as MetadataCache - 读取本地文件src/adapter/messages.ts:144
const bytes = await readFile(path) - 执行外部命令来自配置/变量src/agent-process.ts:89
const child = spawn(this.agentPath, this.args, { stdio: ['ignore', 'pipe', 'pipe'] }) - 执行外部命令固定命令src/agent-process.ts:265
const killer = spawn('taskkill', args, { stdio: 'ignore', windowsHide: true, timeout: GRACEFUL_STOP_TIMEOUT_MS }) - 读取本地文件src/config.ts:225
await stat(path) - 读取本地文件src/config.ts:238
const existing = (await readFile(paths.tokenPath, 'utf8')).trim() - 写入本地文件src/config.ts:242
await mkdir(dirname(paths.tokenPath), { recursive: true }) - 写入本地文件src/config.ts:243
await writeFile(paths.tokenPath, token + '\n', { encoding: 'utf8' }) - 写入本地文件src/config.ts:245
await chmod(paths.tokenPath, 0o600).catch(() => {}) - 写入本地文件src/config.ts:273
await mkdir(paths.dataDir, { recursive: true }) - 写入本地文件src/config.ts:275
await writeFile(tmpPath, JSON.stringify(config, null, 2), 'utf8') - 删除本地文件src/config.ts:276
await rm(paths.configPath, { force: true }) - 写入本地文件src/config.ts:277
await rename(tmpPath, paths.configPath) - 写入本地文件src/index.ts:126
void writeFile( - 读取本地文件src/index.ts:401
if (existsSync(sibling)) return sibling - 发起网络请求src/pool/admission.ts:80
deps.undici.request(ipEcho, { dispatcher: agent }), - 发起网络请求src/pool/admission.ts:226
deps.undici.request(url, { dispatcher: agent!, ...init }), - 发起网络请求src/pool/dispatcher.ts:160
this.#direct = new options.undici.Agent() - 发起网络请求src/pool/dispatcher.ts:196
const agent = new seam.ProxyAgent({ - 发起网络请求src/pool/dispatcher.ts:199
new seam.Agent({ ...(opts as object), pipelining: 0 }), - 读取本地文件src/pool/singbox.ts:317
if (existsSync(bin)) return bin - 执行外部命令来自配置/变量src/pool/singbox.ts:322
const child = spawn(bin, ['version'], { stdio: 'ignore' }) - 写入本地文件src/pool/singbox.ts:343
await mkdir(this.#dataDir, { recursive: true }) - 写入本地文件src/pool/singbox.ts:345
await writeFile(tmp, JSON.stringify(config, null, 2), 'utf8') - 删除本地文件src/pool/singbox.ts:346
await rm(this.#configPath, { force: true }) - 写入本地文件src/pool/singbox.ts:347
await rename(tmp, this.#configPath) - 执行外部命令来自配置/变量src/pool/singbox.ts:351
const child = spawn(binary, ['check', '-c', this.#configPath, '-D', this.#dataDir], { stdio: 'ignore' }) - 执行外部命令来自配置/变量src/pool/singbox.ts:360
this.#child = spawn(binary, ['run', '-c', this.#configPath, '-D', this.#dataDir], { - 执行外部命令固定命令src/pool/singbox.ts:415
const killer = spawn('taskkill', ['/T', '/PID', String(child.pid)], { stdio: 'ignore' }) - 执行外部命令固定命令src/pool/singbox.ts:427
const killer = spawn('taskkill', ['/T', '/F', '/PID', String(child.pid)], { stdio: 'ignore' }) - 发起网络请求src/pool/subscription-fetcher.ts:140
const body = await this.#fetch(url) - 发起网络请求src/pool/subscription-fetcher.ts:229
async #fetch(url: string): Promise<string> { - 发起网络请求src/provider.ts:121
const response = await fetch(providerBaseURL(port) + '/models', { - 发起网络请求src/provider.ts:137
const response = await fetch(`http://127.0.0.1:${port}/healthz`, { signal: controller.signal })
扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-client-locale@deepseek-ai/dsh-client-ui-plugin-manager@deepseek-ai/dsh-client-ui-primitives@deepseek-ai/dsh-client-ui-renderer@deepseek-ai/dsh-client-ui-settings@deepseek-ai/dsh-client-ui-slots@deepseek-ai/dsh-credentials@deepseek-ai/dsh-llm@deepseek-ai/dsh-settings@deepseek-ai/schemastery给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
OpenCode Zen 免费模型接入 DeepSeek Harness:原生 dsh-llm 适配器,无需 API key,代理出口池可扩展到数百个出口。
README
由 DeepSeek 最新模型翻译生成dsh-opencode-router
在 DeepSeek Harness 里免费用 OpenCode Zen 模型。不用 API key,不用账号,不用额外起进程。免费通道对你的 IP 限流时,一组代理出口会接手,模型选择器照常能用。
安装
装上就能用:出口池默认开启,会抓取公开代理列表,把你的请求轮流分发到每个真正能连上 API 的出口。不用配置,不用账号,不用 API key,也不用起隧道。在输入框下方的 composer 芯片里就能看到它工作,出口通过检测后会一个个出现——通常发第一条消息后一分钟内就有。
在 GUI 的 Add plugin 输入框里填:
dsh-opencode-router
或者在终端里执行,这里必须指定配置档名:
dsh plugin add dsh-opencode-router --profile web
其他方式
| 方式 | 命令 |
| 锁定版本 | dsh plugin add dsh-opencode-router@1.1.1 --profile web |
| 锁定 commit | dsh plugin add github:mirelconstantin/dsh-opencode-router#<sha> |
展开完整文档(README 剩余 1 节)
dsh-opencode-router
在 DeepSeek Harness 里免费用 OpenCode Zen 模型。不用 API key,不用账号,不用额外起进程。免费通道对你的 IP 限流时,一组代理出口会接手,模型选择器照常能用。
安装
装上就能用:出口池默认开启,会抓取公开代理列表,把你的请求轮流分发到每个真正能连上 API 的出口。不用配置,不用账号,不用 API key,也不用起隧道。在输入框下方的 composer 芯片里就能看到它工作,出口通过检测后会一个个出现——通常发第一条消息后一分钟内就有。
在 GUI 的 Add plugin 输入框里填:
dsh-opencode-router
或者在终端里执行,这里必须指定配置档名:
dsh plugin add dsh-opencode-router --profile web
其他方式
| 方式 | 命令 |
| 锁定版本 | dsh plugin add dsh-opencode-router@1.1.1 --profile web |
| 锁定 commit | dsh plugin add github:mirelconstantin/dsh-opencode-router#<sha> |
| Tarball | dsh plugin add ./dsh-opencode-router-1.1.0.tgz |
| 本地检出 | dsh plugin add /absolute/path/to/dsh-opencode-router |
重启配置档,打开模型选择器,从 opencode2dsh 分组里挑一个模型。所有东西都是可选的:出口池默认关闭,所以装上之后什么都不会变,直到你把它打开。
它做什么
插件注册了一个原生 dsh-llm 适配器,直接从 OpenCode Zen 的匿名免费通道流式拉取,也就是 OpenCode 自家命令行不登录时用的那批模型。请求会带上命令行的 user agent,以及它的 session、request、project 请求头,所以上游看到的就是普通命令行流量。
模型目录会自己保持最新,靠三步兜底:先取上游实时列表,再退回离线缓存,最后退回一份验证过的静态列表。哪些模型算免费,由 models.dev 的定价元数据决定。这一切都不需要 key。
你的 IP 被限流时,代理出口池会接手。 匿名通道按出口 IP 计量配额,所以一个 IP 用光了,别的 IP 还能继续服务。出口池从四个来源填充(公开免费列表、你粘贴的地址、订阅 URL,以及 GoProxy 转换器),逐个探测候选出口,再把每个请求都走最健康的那个出口:
| 来源 | 来自哪里 |
| free | 公开代理列表,抓取并筛选。默认开启 |
| manual | 你粘贴的地址,每行一个 |
| subscription | Clash 或 V2ray 订阅 URL。明文节点直接拨号,加密节点经 sing-box 转换 |
| goproxy | GoProxy 适配器贡献的节点 |
出口返回 429 就进入冷却,按指数退避,池里其余节点继续服务。
模型级拒绝通常源于地区封锁,而不是代理坏了,因此只封禁那一对「出口 + 模型」。
出口连续失败多次后会被剔除。真实流量会边走边教这个池子:路由器看到的每个响应,都会更新探测器写入的同一份健康数据,所以不会把探测配额浪费在已经在用的出口上。
查看并选择你正在用的代理
只要插件已加载,输入框下方就有一个紧凑控件——底部停靠栏里、上下文计量条旁边。
这个标签显示你上一次请求走的出口;没有走代理时显示 direct;它的颜色表示该出口是否仍可用。
池子开着但没有可用出口,或者它把全局调度器交给了另一个插件,都会如实说明,因为「池子已启用」和「你的流量走了代理」是两回事。
点它就能选。 选择器给出三种答案,它们不能互换:
| 行 | 含义 |
| No proxy (direct) | 什么都不路由。每个请求都直连。 |
| Automatic | 让池子自行选择和轮换。 |
| 某个出口行 | 固定用该出口,直到你改动。可以故意固定一个已死的出口,用来调试坏节点。 |
每个出口行都带一个状态点、延迟,以及它花掉了你多少:
- Tokens —— 该出口承载的总量,覆盖所有已完成的请求,并做了拆分,让重对话一目了然。
旁边是来自提示词缓存的那部分占比:缓存前缀的计费远低于全新前缀,所以占比高意味着该出口在便宜地承载长会话。悬停提示会解释这个数字。
- 上次使用时间 —— 3 uses · 2m ago。
行的排序方式跟你的选择逻辑一致:当前固定的,然后健康出口,然后真正承载过流量的,其余按最近使用排。经过验证的出口胜过同等健康度但没测过的出口。
No proxy 刻意不等同于 Automatic:不固定仍然会路由,而你可能想在不碰设置的情况下关掉路由。
它的优先级也高于固定项,所以早先留下的固定不会悄悄把路由复活。
标签读取的是设置页轮询的同一个 /status 桥,并通过受回环保护的 POST /pin 写入,所以不新增传输层,也不新增配置。它刻意不依赖设置页是否打开:池子出问题时,你在对话中途就需要它。
标签跟随你的路由决策,而不只是已完成的流量: No proxy 和固定某个出口,都会在你点击的那一刻改变标签,因为下一个请求会走那里。还没承载过流量的 pin 会标成 pin,而不会声称有请求走过它。什么都没 pin 时,读数回退到最近一次完成的请求,所以在一次 429 轮换和下一次之间,它显示的是会话即将离开的出口——tooltip 会说明这一点,而不是藏着。
用量统计和 token 计数都在内存里:它们统计的是本进程的路由,不是持久记录,重启就清零。
一个出口能撑多久
Zen 免费通道按 IP、按 UTC 日 计量——桶的键是 ratelimit:ip:<ip>:<YYYYMMDD>,不是按 API key,也不是全局。由此有两点,也正是轮换值得做的原因:
- 新的出口 IP 就是一个全新的空桶。 轮换真的能把当天的额度翻倍,而不只是把它摊开。
- 重置点是 UTC 午夜,不是你第一个请求之后的 24 小时。 UTC 23:00 撞上 429,大约一小时就清了;UTC 01:00 撞上,要等将近 23 小时。这是上游自己的 retry-after 值,池子现在收到它就原样使用,收不到就自己算出同样的重置时间。
新 IP 比老 IP 待遇更宽:在生命周期阈值以下,上游给两倍的每日上限,所以最近才出现的出口比长期使用的更值钱。
上限本身没有公开。 这个数字在服务端的密钥里;被广泛引用的「200 requests/day」是社区传说,没有官方来源。按你自己实测的吞吐量来规划,别按那个数字。还要注意,上限计的是请求数,不是 token——一个很长的智能体回合和一句提示词各算一次——而且免费模型即使已认证也按 IP 计量,因为那是模型的属性,不是请求的属性。
两个看起来像 bug 的轮换细节:IPv6 会被截断到 /64,所以在同一个 /64 里轮换地址没用;而且只公开 retry-after 头,没有 x-ratelimit-remaining 可读。
还要注意,上面这个限流器最后一次开源是 2026-10-11,Zen 现在只是指向一个闭源服务的薄代理,所以这些数字可能随时变。来源以及已确认/推断/未知的划分见 research/opencode-zen-rate-limits.md。
管理大量代理
设置页在 Plugins 页面上,位于插件那一行下面,每次改动保存即生效,不用重启。
粘贴一整份列表。 两个地址字段都有批量粘贴框。丢进几百行,摘要会在你提交前告诉你结果:会加进去多少、多少因重复被跳过、多少读不出来。等价写法算重复,所以 http://Host:080 和 http://host:80 是一条而不是两条。空行和 # 注释会被忽略。
一眼看出池子有多满。 容量计量条显示可用出口对比目标,状态徽章显示 healthy、low、critical 或 depleted。出口表列出每个出口的来源、区域、延迟、质量等级、实时状态和模型封禁,你可以在任意一行上探测或固定该出口。
固定主出口,前提是你有一个信任的节点,比如本地 Clash 混合端口。固定会让该出口始终排在轮换池前面。开启绝对固定后,失败会直接透传,而不是轮换——当这个节点必须使用时,这正是你要的行为。
设置
默认值开箱即用。在配置档的 cordis.patch.yml 中覆盖它们:
- id: opencode2dsh
name: 'dsh-opencode-router'
config:
providerId: opencode2dsh
refreshSeconds: 300
ipPool:
enabled: true
free:
enabled: true
targetSize: 200
| 选项 | 默认值 | 说明 |
| providerId | opencode2dsh | 在 DSH 中显示的服务商名称。 |
| refreshSeconds | 300 | 实时目录刷新间隔。 |
| ipPool.enabled | true | 开箱即开——全新安装无需任何配置即可路由和轮换。 关闭意味着每个请求都直连,就像池不存在一样。 |
| ipPool.free.enabled | true | 抓取并收录公开代理列表。 |
| ipPool.free.targetSize | 30 | 保留多少个可用免费出口。schema 接受 1 到 1000;选择和收录的耗时随这个数字线性增长。30 适合实测的收录率:抓到的候选里大约只有 11% 真能连上 API,而免费代理通常撑不过一小时,所以目标值太小会让池大部分时间处于紧急状态。 |
| ipPool.free.blockedCountries | ["CN"] | 拒绝其出口的 ISO 国家代码。 |
| ipPool.manual | [] | 代理地址,http://host:port 或 socks5://host:port。 |
| ipPool.subscription.urls | [] | 订阅 URL,按 refreshMs 刷新。 |
| ipPool.subscription.refreshMs | 1800000 | 订阅拉取间隔。 |
| ipPool.singbox.path | sing-box | 加密订阅节点的转换核心。 |
| ipPool.probeModels | [] | 用来探测出口的模型。留空表示使用默认值 big-pickle。 |
| ipPool.maxConcurrentProbes | 3 | 跨不同出口的并发探测数,1 到 32。同一个出口始终串行探测。 |
| ipPool.pinnedExitId | "" | 主出口的地址。 |
| ipPool.pinnedStrict | false | 绝对固定:失败即报错,不轮换也不直连。 |
| firstEventMs | 30000 | 首个流事件到达时间的看门狗。 |
| bodyIdleMs | 120000 | 响应中途静默的看门狗。 |
| responsesBodyIdleMs | 300000 | 仅 Responses 模型使用的同一静默下限。 |
每项设置都能在 Plugins 页面修改,通常比改补丁文件更方便。
扩展
池的设计目标是容纳数百个出口,同时路由路径不必为没在用的出口付出代价。
- 选择只扫一遍。 每个请求只遍历出口一次并保留最优者,而不是构建并排序一份包含所有出口的列表。健康状态和冷却状态从池中读取,只有池真正发生变化时才会重建候选列表。
- 封禁记录按出口建立索引。 某个出口对其模型是否已耗尽,是与该出口自己的封禁集合做计数比较,而不是扫描整张封禁表。池状态、准入配额和状态页所需的可用性信息,只需遍历一遍出口即可得出。
- 出口地址已建立索引。 添加候选时会在地图中检查其出口 IP,因此准入几百个候选是线性开销,而不是平方级开销。
- 代理智能体的淘汰是常数时间。 每个出口的智能体缓存维护自己的最近使用顺序,默认上限为 64,因此工作集超过旧上限后不再反复重拨。
- 筛选认领任务按索引进行。 粗筛把下一个候选直接交给每个工作线程,而不是移动队列,从而消除了在多达 20,000 个候选的列表上的平方级开销。
以上改动都没有改变选择顺序:固定出口可用时优先,其次是会话的上一个出口,然后是健康优于未知,然后是连续 429 更少,然后是延迟更低,最后是在同等条件下随机挑选。
健康与故障排查
插件在每次刷新后写入状态快照:
~/.opencode2dsh/adapter-status.json
{
"status": "ready",
"total": 64,
"exposed": 9,
"lastError": "",
"writtenAt": "2026-08-29T07:01:54.915Z"
}
| 症状 | 可能原因与修复 |
| 只出现 3 个模型 | 首次目录抓取与你的网络发生了竞争。重试会在大约一分钟内成功;检查状态文件中的 lastError。 |
| lastError: "fetch failed" 持续存在 | 到 opencode.ai 的出站 HTTPS 被阻断。检查你的代理或 VPN 规则。 |
| 聊天时出现限流错误 | 匿名通道按 IP 计算配额。打开池、添加出口,或切换网络节点。 |
| 某个模型报告 REGION_BLOCKED | Zen 拒绝了该模型当前所在的网络区域。出口池将其视为出口与模型的封禁,因此其他模型仍可在该出口上正常工作。 |
| 所有服务商都失败并报 routing dispatcher closed | 出口路由层的生命周期超过了插件。重启配置档。 |
| 手动条目显示健康,但始终无法填充其出口 IP | 地址中已经带了 scheme。请只写一次 http://host:port 或 socks5://host:port。 |
| 加密订阅节点始终无法准入 | sing-box 未安装或不在 PATH 中。设置 ipPool.singbox.path。 |
| 安装失败并报 ERR_PNPM_IGNORED_BUILDS | pi-ai 的传递依赖带有运行时不需要的构建脚本。在配置档的 pnpm-workspace.yaml 中,把 allowBuilds: 下的两项都设为 false。 |
安全
- 不涉及任何密钥。 免费通道的密钥就是字面字符串 public。不存储任何内容,也不上报任何遥测数据。
- 请求直接从你的机器发出,发往 opencode.ai 和 models.dev,或经由你配置的代理出口发出。
- 公开免费代理不可信。 它们能看到流量。只把它们用在免费通道上,这条通道不携带任何凭据;其他用途请优先用你自己的节点或订阅。
- 安装时不会有任何依赖偷偷装构建脚本。
开发
pnpm install
pnpm run typecheck
pnpm test
pnpm run build # node bundle
pnpm run build:client # browser bundle
pnpm run verify:all 检查对外发布的接口:清单承诺的每个入口点是否都存在,bundle 补丁安装的包是否就是清单里写的那个,行 id 是否与插件自己导出的 name 一致,以及客户端 bundle 注册用的 id 是否是 shell 加载器期望的那个。pnpm run clean 会在构建前删掉 lib/,因为 node 构建会保留自己的输出目录,模块一旦改名,上一次的 chunk 就会留在那里。
pnpm run build 构建前会先清理 lib/,而 build:client 必须跟在它后面跑——产物测试读的是构建出来的 bundle,lib/ 缺失时它们会以 ENOENT 失败,而不是因为真正的原因失败。
发布。 每次上线的改动都要有新版本号——改 package.json 里的 version,或者直接跑 npm version patch --no-git-tag-version 让它帮你改。npm 会拒绝重复的版本号,所以 1.1.1 不可能再变成 1.1.1;你发布的那个号就是真实存在的那个号。完整流程,包括客户端 bundle 的导入规则和必须在两处同时改的配置默认值,都在 AGENTS.md 里。
测试套件覆盖出口池的选择顺序、冷却与封禁语义、批量粘贴解析器,以及构建产物本身:它会导入 lib/,遍历 chunk 图证明没有孤儿文件被发布出去,并用打包后的代码搭出一个能用的池。每次 push 和 pull request,CI 都会在 Linux 和 Windows 上跑类型检查、测试套件和两项结构检查。
目录结构
src/
adapter/ the Zen adapter: streaming, message shaping, request identity
pool/ the exit pool: selection, health, probing, screening, routing
ip-pool-settings/ the settings schema and the status bridge the page reads
client/ the browser half: the IP pool page and its dictionaries
致谢
本插件源自 @FishBottle7 的 opencode2dsh,该项目采用 MIT 协议,至今仍是目录回退链、请求伪装细节和匿名通道实现的源头。完整署名见 NOTICE。
- OpenCode,用来跑免费的匿名 Zen 通道。
- models.dev,提供模型元数据,决定哪些模型免费。
- @earendil-works/pi-ai,适配器流式传输所走的线路层。
协议
MIT,见 LICENSE。
由 Pazalo 构建。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
mirelconstantin/dsh-opencode-router
免密钥调用 OpenCode 免费模型,自动切换代理出口
相关插件 · 模型接入
API 中继安全审计toby-bridges/api-relay-audit
面向 AI API 中转与 LLM 代理的本地安全审计。
★ 897已汉化DSH
69.4
免费搜索插件DDDMUC/dsh-free-search
DeepSeek Harness 免费搜索插件 —— 无需 API key,零成本,多引擎可切换。 一个给…
★ 761已汉化DSH
68.7
免费联网搜索liustack/modsearch
在官方 App 里模型能联网,切到 API 就不能了。ModSearch 把联网补回来:网页搜索、X…
★ 863已汉化DSH
68.5
OpenCode 免费模型FishBottle7/opencode2dsh
在 DSHDeepSeek Harness里原生使用 OpenCode Zen 的免费匿名模型。
★ 379已汉化DSH
67.8
AI 模型网关tingly-dev/tingly-box
Tingly Box Web UI 演示
★ 373已汉化DSH
67.4
WorkBuddy 账号池XDTrees/dsh-xdpool
把你能白嫖的模型额度接进 DSH 同一个池:WorkBuddy 多账号自动容错、十三家扫码登录、76…
★ 359已汉化DSH
67.1