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

子代理选模器 wld12314/dsh-subagent-switch

DeepSeek Harness
wld123145 天前有提交已提供中文文档扫描:含敏感能力

按任务能力与额度为每个子代理自动挑选最省钱的模型

29.7/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

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

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add wld12314/dsh-subagent-switch
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么[目录]—
dsh 原生插件 · model
暂无时间戳·判据 →
装得上吗[实装验证]—
本站尚未做安装检查
暂无时间戳·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/04·判据 →
还在维护吗[仓库]✓
活跃:最近一次提交在 5 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

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

验证判据与等级说明 →

可信度证据

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

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

读写/删除本地文件发起外部网络请求
查看扫描证据(共 8 处,展示前 8 条)
  • 读取本地文件
    lib/apply.js:356
    const raw = readFileSync(new URL("../config.example.json", import.meta.url), "utf8");
  • 写入本地文件
    lib/apply.js:1226
    appendFileSync(LOG_FILE, line + "\n", "utf8");
  • 发起网络请求
    lib/ui/client.js:23
    const res = await fetch(API + path, { headers: { accept: 'application/json' } })
  • 发起网络请求
    lib/ui/client.js:38
    const res = await fetch(API + path, {
  • 读取本地文件
    lib/overrides.js:76
    raw = readFileSync(target, "utf8");
  • 写入本地文件
    lib/overrides.js:149
    mkdirSync(dirname(target), { recursive: true });
  • 写入本地文件
    lib/overrides.js:150
    writeFileSync(tmp, `${JSON.stringify(clean, null, 2)}\n`, "utf8");
  • 写入本地文件
    lib/overrides.js:151
    renameSync(tmp, target);
扫描于 2026/11/4(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

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

社区实装还没有人打卡 · 装过的第一个就是你
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

DSH 插件:把每个子代理路由到最合适的本地模型,优先免费、感知配额、匹配能力,还带控制面板。

README

由 DeepSeek 最新模型翻译生成
dsh-subagent-switch 给 DeepSeek Harness(DSH)的子代理自动挑模型的插件。 每次 spawn 子代理时,它先看一眼你本机所有接入的模型,再按「便宜优先 + 能力匹配 + 额度够不够」替这次任务选一个,把结果写进子代理的会话配置。默认只花订阅里已经付过的钱;便宜档确实没人擅长这件事时,才允许升到付费档。 DSH 里的子代理默认继承父会话所在的模型。如果你在几个服务商下挂了 100 多个模型,这意味着你的调研子代理正烧着 x1.62 的 token 去回答一个一句话的问题 —— 或者你的编码子代理跑在那个明确不擅长代码的免费模型上。这个插件拦截 spawn,改成按任务逐个挑。 如果你觉得它有用,给个 Star ⭐ —— 这类小工具没什么曝光,一颗星就是最直接的反馈,也让后面需要它的人更容易搜到。
展开完整文档(README 剩余 1 节)
dsh-subagent-switch

给 DeepSeek Harness(DSH)的子代理自动挑模型的插件。 每次 spawn 子代理时,它先看一眼你本机所有接入的模型,再按「便宜优先 + 能力匹配 + 额度够不够」替这次任务选一个,把结果写进子代理的会话配置。默认只花订阅里已经付过的钱;便宜档确实没人擅长这件事时,才允许升到付费档。

DSH 里的子代理默认继承父会话所在的模型。如果你在几个服务商下挂了 100 多个模型,这意味着你的调研子代理正烧着 x1.62 的 token 去回答一个一句话的问题 —— 或者你的编码子代理跑在那个明确不擅长代码的免费模型上。这个插件拦截 spawn,改成按任务逐个挑。

如果你觉得它有用,给个 Star ⭐ —— 这类小工具没什么曝光,一颗星就是最直接的反馈,也让后面需要它的人更容易搜到。

它解决什么问题

一台接了几个账号服务商的 DSH 机器,模型目录长这样(真实数据,107 个):

| 服务商 | 模型数 | 能读余额 | 说明 |
| workbuddy | 20 | ✅ | 显示名里带价格倍率,如 Hy3 · x0.00、DeepSeek-V4.1-Flash · x0.11 |
| trae | 35 | ❌ | 账号订阅,有余额接口但拿不到 |
| our-free-model | 33 | ❌ | 无账号、无登录态,额度随时清零 |
| qoder | 17 | ✅ | |
| deepseek-account | 2 | ❌ | |
| cline | 459 | — | 默认整块排除,见下文 |

面对这样一堆模型,手动选是选不过来的:你不知道哪个擅长什么,不知道哪个快没额度了,也懒得为每个子代理点一次。这插件干的就是这件事 —— 而且每次选择都告诉你理由(matched / score / tier / reason),不是黑箱。

功能

- 按能力挑模型,不只看价格。 每个模型带 goodAt / badAt,每个任务归到 coding / research / writing / summarize / analysis 等域,命中打分(required 域 +10、optional +2、不擅长 −10/−2)。
- 免费优先,但不是免费的都先选。 free 和 free_quota 合成一个档带按能力分一起排 —— 一个明确标了「不擅长编码」的免费模型,不会赢过一个 x0.08 真擅长编码的模型。两档都是订阅里已付的钱,价差四舍五入为零。
- 条件升级付费。 只有便宜档里没有任何模型真正声明擅长这个域时,才往付费档升 —— 且只升到「真匹配」的模型,绝不为另一个 0 分猜测花 4~8 倍的钱。升级是可见的:面板标「升级付费」,日志写明被跳过了什么、它几分。
- 路由门禁:只让账号接入的模型进子代理。 这类模型才有可能读到余额、才有可能稳定存在。our-free-model 那种随时清零的仍然列在面板里,但不参与自动选择(可以手动「放行」)。
- 能力标签来源分三层,且标出可信度。 手写 config > 内置系列预设 > 无标签。面板上分别标「手写 / 推测 / 无标签」—— 猜出来的标签和手写的看起来一样权威,不标出来,由它们驱动的路由就没法审计。
- 中英文标签都认。 "goodAt": ["编码"] 和 ["coding"] 等价,内部统一翻成英文 canonical id(下游比的是字符串相等,存中文会静默失效)。
- 手动优先级压过一切。 面板里 ↑/↓ 调到 >0,就压过能力分、压过档位顺序、并且不再升级付费。想让某个模型只用它,就钉住它。
- 一个独立面板(不改动原模型选择栏):模型表、六类任务的路由预览、优先级、放行/收回、黑名单。
- 额度真读。 直接打 provider 插件自己的 HTTP 端点拿余额,余额写回该 provider 下每个模型,读到 0 整体退出选择。
- 零依赖、零联网。 纯 Node 内置模块;不装任何包,不发任何外部请求(除了读本机 provider 的额度端点)。
- 143 个单测,纯 node:test。

安装

插件走 DSH 的标准 profile 机制(和官方插件一样),三处改动,都可逆。

1. 放进 profile

假设插件源码在 <插件目录>,profile 在 <profile 目录> 官方 profile 一般在 ~/.dsh/profiles/desktop

编辑 <profile>/package.json:

{
"dependencies": {
"@local/subagent-switch": "link:<插件目录>" },
"dsh": {
"profile": {
"bundles": [
"@local/subagent-switch" // 加这一行 ] } } }

2. 建软链接

Linux / macOS
ln -s <插件目录> <profile>/node_modules/@local/subagent-switch

Windows(Git Bash 里同样可用上面的命令;PowerShell 用 junction) New-Item -ItemType Junction -Path "<profile>\node_modules\@local\subagent-switch" -Target "<插件目录>"

3. 重启 DSH

bundle 只在启动时加载。重启后左侧栏会多一个「子代理模型切换」入口。

改了插件代码也要重启。 宿主半(lib/*.js)是启动时 import 的;浏览器半(lib/ui/client.js)在页面刷新时重新加载。

装上之后

左侧栏多了「子代理模型切换」入口,点进去就是上面说的那个面板。不需要改任何配置:默认就是免费优先 + 能力匹配 + 门禁,装上即用。

怎么让插件市场扫到

本插件自带一个 format-1 市场索引,里面写了标题、分类、标签、关键词(中英双语 40 个)、版本、git 源和 commit。DSH 的「发现」页就是读这种索引文件。

两条路把它加进去:

A. 在界面里加(推荐)

1. 左侧栏 → 插件 → 实验性插件 旁的 更多 → 启用 自定义插件市场
2. 回到插件页,切到 发现 标签
3. 在索引源里填:

https://raw.githubusercontent.com/wld12314/dsh-subagent-switch/main/market-index.json

4. 搜「子代理」「模型路由」「quota」「routing」都能命中,然后点安装

B. 直接写进 profile

<profile>/cordis.patch.yml 里给 plugin-market 那行加 indexes:

- id: plugin-market
name: '@deepseek-ai/dsh-plugin-market'
config:
indexes:
- https://raw.githubusercontent.com/wld12314/dsh-subagent-switch/main/market-index.json

⚠️ 索引不会自动被扫描到。 DSH 的插件市场没有中央注册表,indexes 默认是空数组,不会主动去 GitHub 上找插件 —— 只读你加进去的那几个 URL。所以「上架」这件事在这个生态里就等于「产出一个索引文件 + 把它的地址告诉用户」,没有别的一步。

本地已经装过(link: 装机)的话不用走这条路,那只是给别人用的分发方式。

怎么用

打开面板,从上到下:

1. 顶部说明区 —— 当前档位顺序、已忽略的 provider、参与自动路由的 provider、升级付费是否开启、手动优先级钉住了几个、overrides 文件在哪。
2. 本次路由预览 —— 六类任务(编码/调研/写作/摘要/分析/通用)现在会选中谁、什么档位、匹配还是兜底。这是最有信息量的一栏:兜底 0 分 · 8 个并列 就是在明说「这次不是能力匹配,是按目录顺序撞的」。
3. 模型表 —— 每个模型的档位、状态、擅长、不擅长、优先级。状态列的「放行」能让被门禁挡掉的模型重新参与。
4. 任务类型指定 —— 想给某类任务钉死一个模型,走 routingTable。

路由怎么决定(按顺序)

1. routingTable 指定 —— 你给这个类别钉了模型就用它(不经过打分,matched: null)。
2. 全局 pin —— 有优先级 >0 的模型,按优先级 → 能力分排,直接返回(reason 里写 pinned, band order bypassed)。
3. 档带内排序 —— free+free_quota 合成一层,按「优先级 → 能力分」排。
4. 命中就返回 —— 这一层有模型真声明擅长(matched: true),或者有 pin,就定了。
5. 条件升级 —— 这一层没人擅长,且 escalateOnMiss 开着,就往上找 matched: true && score >= escalateMinScore 的模型。找不到就留着便宜档那个。
6. 兜底 —— defaultModel → 任意可用模型。

select() 的返回体把每一步都摊开:

{
"reason": "band-match free+free_quota score=10",
"tier": "free_quota",
"band": ["free", "free_quota"],
"score": 10,
"matched": true, // false = 兜底;null = routingTable 指定 "candidates": 15, "priority": 0, "pinned": false, "escalated": false, "skipped": null,
"model": { "id": "workbuddy/deepseek-v4.1-flash", "name": "DeepSeek-V4.1-Flash · x0.11", "tagSource": "preset" }
}

实测(真实 107 个模型)

| 任务 | 选中 | 档位 | 匹配 |
| 编码 | Qwen3.7-Plus · x0.1 | free_quota | +10 真匹配 |
| 调研 | DeepSeek-V4-Pro | paid(升级) | +10 —— 便宜档 research 擅长 0 / 不擅长 10 |
| 写作 | MiniMax-M3 · x0.2 | paid(升级) | +10 —— 便宜档 writing 全中性 |
| 摘要 | Hy3 · x0.00 | free | +10 |
| 分析 | Qwen3.7-Plus · x0.1 | free_quota | +10 |
| 通用 | Hy3 · x0.00 | free | +10 |

即只有真没法在便宜档解决的两类任务才花钱,其余一分不多花。

配置

配置有两个入口,都认:

- <profile>/cordis.patch.yml 里给本插件那行写 config:(扁平键值)
- 或改插件的 config.example.json(嵌套在 "subagent-switch" 下)

两种形状都会被剥壳合并,最终交给路由器的永远是扁平对象。数组是替换不是追加 —— 写 [] 就是清空,想收窄不会被撑宽。

关键项

| 键 | 默认 | 说明 |
| enabled | true | 总开关 |
| modelTiers | ["free","free_quota","paid"] | 分层顺序 |
| cheapBand | ["free","free_quota"] | 合并成一个档带按能力排;[] 退回逐档走 |
| escalateOnMiss | true | 便宜档无匹配时是否允许升到付费档 |
| escalateMinScore | 1 | 升级要求的最低分(10 = 只认 required 域强匹配) |
| pricing | {freeMultiplierMax:0, quotaMultiplierMax:0.15} | 价格倍率 → 档位阈值 |
| tierMap | {} | 把某个模型 id 显式钉到某档(最高优先级) |
| tags | {} | 手写能力标签,键支持 provider/model / 裸 id / 显示名 |
| blacklist | [] | 移出候选池 |
| excludedProviders | 内置 ["cline"] | 整 provider 排除,在发起 adapter 请求之前就跳过 |
| requireQuotaAware | true | 路由门禁总开关 |
| routableOverrides | [] | 门禁的逐模型例外 |
| autoRouteSubagents | true | 是否拦截 startActivation 自动改模型 |
| forceOverride | false | 为 true 时连调用方显式指定的模型也改写 |
| overridesPath | "" | 手动优先级文件位置,空 = ~/.dsh/subagent-switch-overrides.json |

手动优先级文件

{
"priority": { "workbuddy/glm-5.3": 2 },
"routable": { "our-free-model/big-pickle": true }
}

- 原子写(tmp + rename),写失败不影响运行时,只是重启后会丢(面板红字提示)。
- 只用正向声明:0 和 false 都删键,文件里只看得到你真正做过的决定。
- 删掉这个文件就全部回到自动。 插件绝不碰 DSH profile 的 cordis.patch.yml。

HTTP 接口

宿主半在 webServer 上注册一个前缀路由 /subagent-switch-api,面板就靠它取数据(这也是本机构建上验证过的方式,不需要 RPC):

| 方法 | 路径 | 作用 |
| GET | /state | 标题、档位顺序、门禁名单、配额、优先级表、overrides 路径 |
| GET | /models | 全部模型 + 档位/状态/标签/优先级/可否路由/额度 |
| GET | /routes | 六类任务各会选中谁(含 matched / score / escalated) |
| GET | /select?category=&description= | 单次选择 |
| POST | /priority {id, priority} | 设优先级 |
| POST | /routable {id, routable} | 放行 / 收回 |

只监听 loopback。没有鉴权(和同机其它本地插件一致),它只写插件自己的 overrides 文件,不碰 DSH 配置。

已知限制

诚实点名,都没修:

1. 50 个模型没有能力标签(107 个里 57 个有)。其中 18 个可路由:qoder 6 个(auto/ultimate/performance/efficient/smodel/cmodel)、trae 12 个(Doubao 系 / glm-5 / glm-4.7 / kimi-k2.5 / minimax-m2.7 等)。全在 paid 档,不影响便宜档路由,但 paid 档内它们抢不过有标签的。补这些得按系列再写一轮预设,缺实测依据,我没猜。
2. qoder 额度读取在冷启动首轮偶尔 flaky(第二轮起稳定)。readQuotas 挂在 refreshModels 链上,首轮可能撞上 provider 尚未就绪。不影响路由结果。
3. resolveAllowedProviders 没接上真实会话策略。 如果你在设置里开启「子代理模型选择」并限定白名单,那个限定对手动选有效、对本插件自动改写无效 —— 因为插件在 startActivation 内部改模型,绕过了前面那道 policy.routes 预校验。默认 enabled: false,所以现在没有实际影响。扩展点已经留好(resolveAllowedProviders 钩子 + allowedProviders 静态名单),宿主把它接上即可。

另外两个刻意的取舍,不是 bug:

- 门禁可能选中刚清零的模型。 trae / deepseek-account 是账号接入但读不到余额,能进池靠的是「账号接入」这一层。备选方案是把它们也挡掉,但那样池子会小到只剩 3 家。
- 优先级按模型 id 记。 上游改 id 会静默失效,面板上该行会显示优先级 0。

为什么默认排除 cline

有意为之,不是 bug。

cline 是聚合型 provider,会把第三方模型重复收录一遍,其中大量是已经过时的老版本。实测:561 个模型里 459 个来自 cline(82%),用户自己的账号模型被埋在里面。

不排除的代价:面板被 561 行冲垮;每次刷新付 459 次 adapter 往返(resolveModelInfo 一个模型一次 IO);459 个名字相似、标签基本为空的老模型把候选池稀释掉。

排除后 561 → 102。规则写在 lib/models.js 的 DEFAULT_EXCLUDED_PROVIDERS(核心里,不只是配置)——全新安装即使配置文件缺失或被人手改,行为也一致。

想加回来:"excludedProviders": [] 表示完全不排除;写 ["cline"] 是追加语义,去不掉内置项(故意的)。排除的 provider 会显示在面板顶部,不会凭空消失。

单测

纯 Node、零依赖:

node --test test/unit.test.js
143 个用例,覆盖:模型归一、tier 推断(含 · xN.NN 价格后缀与 x0.12→x0.06 区间取高值)、能力匹配与 matched 三态、档带合并、升级付费的每一条边界、路由门禁四种判定、provider 级排除、safeGet 吞掉 Cordis 的 without inject 抛错、模型目录三段式读取的失败隔离、配置扁平/嵌套两种形状、数组替换语义、overrides 的原子写与容错、以及「优先级真能压过能力分」这类行为断言。

二次开发:这台构建上踩过的坑

如果你也在给 DSH 写插件,下面几条是实测出来的,能省你几天:

1. 宿主侧没有 ctx.listModels()。 那是浏览器 RPC 客户端(harness-client.mjs)的方法。宿主侧的真源是 Cordis llm 服务:ctx.llm.listProviders() → 每 provider ctx.llm.listModels(id) → 每模型 ctx.llm.resolveModelInfo(provider, id)。DSH 自己的模型栏就是这么建的。
2. Cordis 的 ctx.effect() 只作用于 apply() 的同步体。 apply() 里一旦出现 await,后面的 ctx.effect(() => server.register(...)) 会被静默丢弃 —— 不报错,路由就是不存在。这就是「面板出现了但 API 全 404」的根因。要做的事放在第一个 await 之前。
3. 未在 inject 声明的属性,get 会直接 throw。 不是返回 undefined。所以 typeof ctx.foo === "function" 这种探测本身就会炸,必须包一层 safeGet()。
4. 子代理的模型走 spec.request.agentOptions = {provider, model, reasoningEffort},唯一拦截点是包住 ctx.subagents.startActivation。注意 dsh-tool-subagent 在调用它之前会用 policy.routes 预校验,从内部改写会绕过那道校验(见「已知限制 3」)。
5. 面板不是一个 row。 宿主半靠 cordis.patch.yml 插一行;浏览器半靠 package.json 的 dsh.client {platform:"web", inject:[...]} + exports["./client"],DSH 把 lib/ui/client.js 塞进浏览器模块表,里面用 ctx.slots.inject('sidebar.panellist', ...) 注册左侧栏入口、ctx.slots.inject('main', ...) 注册主面板。
6. ctx.listModels() 不返回额度。 余额得自己打 provider 插件的 HTTP 端点(/plugins/<插件名>/usage 之类,路径不带 npm scope)。有些 provider 根本没有这个端点 —— 那就读不到,别指望。
7. 价格可能被写进显示名。 workbuddy 的 workBuddyDisplayName() 把 creditMultiplier 拼成 Name · x0.11,因为 listModels() 把价格字段整个丢了。解析显示名是这个插件唯一能拿到价格的途径。

许可

MIT —— 随便用,出事别找我。

如果它帮你省下了一次跑错模型的冤枉钱,点个 Star ⭐
预览分享卡片长什么样 ↓wld12314/dsh-subagent-switch
分享 · DeepSeek Harness Hub
wld12314/dsh-subagent-switch

按任务能力与额度为每个子代理自动挑选最省钱的模型

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部