dsh-model-hub
Verified@fhxgs/dsh-model-hub · v0.2.3 · MIT · Web UI
DeepSeek Harness plugin: provider sign-in, model catalog, and selection routing over a loopback-only /model-hub channel
Install
dsh plugin add @fhxgs/dsh-model-hub Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Tags
Creators
Readme
@fhxgs/dsh-model-hub 是 DeepSeek Harness (DSH) 的一体化模型管理插件。它在单个 npm 包中集成了 Node.js Host 服务端与按需加载的浏览器 Client 端。凡是本插件自己承担的业务——提供方生命周期、登录会话、模型目录、会话选择——都走它自有的、仅限本机访问的 /model-hub Loopback RPC 通道;凡是 Harness 本就承担的能力则继续走官方 /api:settings 的读写、凭据的描述与写入、端点模型发现,以及全局默认模型背后的宿主模型列表。插件是接入 Harness,而不是绕开它自己另开一套通道。
安装本插件后,它将完整接管原本分散的模型配置体验:提供统一的设置页面、输入框模型选择气泡以及 /model 快捷指令。
核心特性
| 功能模块 | 说明 |
|---|---|
| 提供方登录 (Sign-in) | 在浏览器中直接驱动 OAuth / 设备码授权流程。支持实时状态轮询、交互问答面板以及 30 分钟 Host 硬超时。凭据直达底层安全存储,绝不进入快照、日志或监控指标。 |
| 生命周期管理 | 提供明确的写操作入口(activate 激活、deactivate 停用、logout 登出、useRecord 改用凭据),每次操作均附带明确的影响提示与二次确认,杜绝黑盒覆盖。 |
| 提供方参数编辑 | 支持安全写入 API Key(通过 credentials.set,绝不混入 settings 配置)、自定义 Base URL、通信协议、显示名称及模型映射,支持端点模型自动发现与自定义 Provider 声明。 |
| 增强型模型目录 | 原生 llm.models 的严格超集,补齐了原生裁剪掉的 inputModalities(输入模态)、contextWindow(上下文窗口)与 defaultMaxTokens。具备版本感知缓存、8 并发请求限流与单模型故障隔离。 |
| 模型策展策略 (Curation) | 支持全量展示 (all) 或白名单筛选 (include)。设置页勾选与输入框选择器实时联动,确保两者看到的数据完全一致。 |
| 会话选择与路由 | 内存级会话模型绑定,配合 { prepend: true } 确保路由准确注入。子代理支持 3 级路由策略(显式配置 > 静态默认 > 继承父会话)。前端主动拦截不可路由的消息发送。 |
| 思考强度调节 (Effort) | 离散滑块档位根据当前模型实际支持的 reasoning.efforts 动态派生(如 gpt-5.6-sol 显示 xhigh/max,非推理模型自动隐藏)。 |
| 极速模式 (Fast Mode) | 元数据驱动的开关,仅对声明了加速服务梯队(如 service_tier: priority)的模型展示。 |
| 内置自建提供方 | 自带独立适配器与 OAuth 实现,内置支持 qwen-code(千问代码:基于 chat.qwen.ai 的 RFC 8628 设备码授权)与 codex(OpenAI Codex:授权码 + PKCE 回调,支持 7 款 GPT-5.x 模型)。 |
| 原生双语界面 | 深度集成 Harness 语言服务,界面完整支持简体中文与英文,随系统语言自动切换。 |
| 安全隔离 | 所有 /model-hub 接口均强制限制为 authority: 'loopback'(仅限本机),采用严格的 Zod Schema 校验,对外输出脱敏的统一错误结构。 |
安装说明
[!NOTE]
dsh plugin底层通过 pnpm 进行包管理 (spawnSync('pnpm')),请确保当前环境变量PATH中包含 pnpm。若未全局安装,可通过 corepack 启用:corepack enable pnpm
将插件添加至目标 Profile:
dsh plugin --profile web add @fhxgs/dsh-model-hub
安装后请重启该 Profile(组合变更不支持热重载)。
插件会自动应用自带的补丁配置 (cordis.patch.yml),启用 @deepseek-ai/dsh-authorization 并自动禁用官方默认的 ui-settings-models 与 ui-model-selection 组件,无需手动修改配置。
卸载插件:
dsh plugin --profile web remove @fhxgs/dsh-model-hub
卸载后会自动恢复官方原生的 Models 设置页与选择器。
关于 Peer 依赖告警
dsh plugin add 底层调用 pnpm,而 pnpm 只按 Profile 目录本身解析 peer 依赖。Harness 并不会把自己的包装进那个目录:Host 侧的 @deepseek-ai/* 导入与浏览器侧的平台模块,都由运行中的宿主在运行期通过模块表提供。因此这 18 个 peer 已声明为 optional——它们的版本范围表达的是「本次构建针对哪一版 Harness 编写」,而不是「请包管理器去下载」——冷装隔离 Profile 时不再报告它们缺失。
保留为必需的只有两个,因为它们是真的可能缺失、告警值得保留信号的两个:
Issues with peer dependencies found
✕ missing peer @earendil-works/pi-ai
✕ missing peer react
冷装时出现这段告警属于预期,插件照常加载——@earendil-works/pi-ai 由 Host 运行闭包提供,react 由 Web 宿主在任何插件工厂运行之前就写入冻结模块表。若告警里出现的是别的包名,那才值得深究。
快速上手
- 在 运行 DSH 的本机浏览器 中打开 Web 界面(所有操作均需 Loopback 权限)。
- 进入 设置 → Model Hub → 提供方。选择目标提供方并完成授权登录。
- 在卡片中点击 激活 启用该路由。
- 切换至 目录 选项卡,勾选希望在对话框中使用的模型。
- 在任意会话中,点击输入框下方的模型选择气泡或输入
/model即可切换模型与思考强度。
配置项
插件在 settings 的 model-hub 命名空间下管理配置:
model-hub:
picker:
mode: include # 'all' | 'include' (默认展示所有模型)
include: # 当 mode 为 'include' 时生效
- { provider: kimi-coding, model: k3 }
fastMode: # 已开启加速梯队的路由
- { provider: codex, model: gpt-5.6-sol }
preferredEffort: high # 可选:默认思考强度偏好
subagent: inherit # 'inherit' | { provider, model, reasoningEffort? }
- 空配置解析:缺省时解析为
{ picker: { include: [], fastMode: [] }, subagent: 'inherit' }。 - 全局默认模型:仍保存在官方
agent-default-model命名空间中。Host 端仅读取该配置,仅当用户在前端破坏性确认框中明确勾选时才会调用官方 CAS 接口进行修改。 - 内置 Provider 状态:独立保存在
model-hub-providers命名空间,避免策展策略变更时误触发模型目录缓存失效。
界面预览
截自 DSH 0.1.1-rc.2 冷装隔离 profile,暗色主题。
| Composer 选择气泡(Simple) | Providers 面板 | Catalog 面板 |
|---|---|---|
![]() |
![]() |
![]() |
架构概览
插件采用单包同构设计:
- Host 端 (Node.js ESM):注册
/model-hubRPC 路由、Settings 命名空间、内置 Provider 适配器以及模型拦截分发钩子。 - Client 端 (按需 CJS):在浏览器中动态挂载,提供设置页面、输入框选择组件与
/model弹窗。
src/
├── index.ts # Host 插件入口:注册 RPC 通道、命名空间与装配生命周期
├── rpc/ # 单层 Wire 信封定义、路由器与 7 个标准错误码
├── auth/ # Authorization 交互桥接与 auth.state 两轴状态投影
├── provider/ # 适配器绑定与四大生命周期动作 (activate/deactivate/logout/useRecord)
├── provider/native/ # 自建 Provider 域 (qwen-code 与 codex 的 OAuth 流程及适配器)
├── catalog/ # 增强型模型目录、策展过滤逻辑与 LRU 缓存
├── selection/ # 会话模型选择、思考强度决策链与子代理 3 级路由
├── settings/ # Settings Schema 定义
└── client/ # 浏览器 UI 源码 (设置卡片、输入框气泡、交互面板等)
打包产物位于 lib/index.js、lib/invariant.js、lib/client.js、lib/types/** 以及 cordis.patch.yml。
[!IMPORTANT]
./client导出仅供 Loader 使用。lib/client.js并不是任何一方去 import 的模块,它的整个函数体就是一次window.__ModuleLoader__.load({ ... })调用,由宿主把该文件读出来直接投给浏览器,而不是去解析它——无论文件后缀是什么,Node 的import()与打包器的require()都会抛错。声明这个导出只是为了让宿主能按名字定位该文件,它不是对外 API,DSH 浏览器运行时之外没有任何一方能消费它。这也正是publint对本包只报一条发现(建议把该文件改成.cjs)、而这条被明确记录并驳回而非静音的原因:改名只会让告警消失,并不会让这个导出变得可用。完整理由写在scripts/verify-manifest.mjs里,豁免按报告原文逐字匹配,因此换成另一条发现照样会让门禁变红。
开发与验证
corepack pnpm install
corepack pnpm run build # 编译 lib/types (tsc) 与运行时产物 (tsdown)
corepack pnpm run verify # 五道门:产物纯度、Patch 键名、SourceMap 链接、发布路径、manifest
corepack pnpm run test # 运行 Vitest 单元测试
corepack pnpm run smoke:p0 # 将 Tarball 冷装至隔离临时 Profile 进行冒烟验证
prepack会在npm pack或npm publish前执行verify门禁,拦截脏产物或路径泄漏;prepublishOnly还会先清空lib/重新构建并跑全量测试,因此一次发布不可能带上陈旧产物。- 冒烟测试 (
smoke:p0) 使用一次性临时目录 (DSH_HOME=$(mktemp -d)),绝对不会影响本地的~/.dsh配置。 - 关键功能与阻断项审计清单请参见
scripts/gate-p1.zh.md与scripts/gate-p3.zh.md。 - 标准发布流程请参见
scripts/release-checklist.zh.md。
上述三份文档位于
scripts/,该目录刻意不进 npm 发布包,因此这里一律使用绝对链接:无论从 npm 页面还是从代码检出,都能正常打开。
兼容性
| 依赖项 | 支持范围 |
|---|---|
| Node.js | ^22.19 || >=24 |
| DeepSeek Harness | 0.1.1-rc.2 |
@deepseek-ai/* 依赖 |
^0.1.1-rc.2 |
@deepseek-ai/cordis |
^4.0.1 |
@earendil-works/pi-ai |
~0.82.1(Peer 依赖,由 Host 运行闭包提供) |
| React | ^18.2.0(Peer 依赖) |
已知限制
- 仅限本机访问 (Loopback Only):非本机请求访问
/model-hub接口均返回 403。远程访问时界面展示只读标识,如需修改模型配置请在运行 DSH 的机器上操作。 - 官方模型设置接管:插件生效期间,官方原生 Models 页面与相关向导步骤会被自动禁用。
- 无后台主动长连接推送:界面刷新依赖 Host 事件转发以及登录面板活跃期的定时轮询。
- 零消息会话选择不持久化:未发送过任何消息的新建会话,其临时选中的模型在服务重启后不会保留(与官方
/model行为一致)。 - 前端输入拦截边界:前端输入框的未配置路由拦截仅作用于 Web 界面;Headless 或 SDK 发起的请求由 Host 服务端直接校验并拦截。
- Codex 计费显示为零:由于 OpenAI Codex 属于套餐订阅制,与普通 API Token 计费模式不同,因此用量计费显示为 0。
- OAuth 回调端口固定:内置 Codex 路由只能在其客户端注册的那一个端口上接收浏览器回调(
http://localhost:1455/auth/callback),因此同一台机器同时只能进行一次该路由的登录;端口被占用时登录会立即以LOOPBACK_PORT_IN_USE结束而不是干等,且该路由没有设备码回退方式。回调接收器会同时绑定两个回环地址;若::1无法绑定,登录继续在127.0.0.1上完成,并记录LOOPBACK_IPV6_UNAVAILABLE。 - Grant 可指定 API Host:OAuth 授权可以带回该账号被路由到的 API Host,但只有其颁发方确实会下发该字段的路由才会采纳(Qwen Code 会,Codex 不会——该路由上存储的 Host 一律忽略)。被采纳的 Host 必须是 HTTPS,且不得带用户名密码、查询串或片段,也不得是另一个内置路由自己的 Host;不满足时该字段被丢弃,路由回落到厂商默认地址。明文 HTTP 一律拒绝,回环地址也不例外。
- 会话选择与消息准入分属两个 Owner:在本插件里选模型,和 Core 判定是否接收一条消息,是彼此独立的两套状态。Core 对每条消息都按自己的链(
picked▸ 请求头 ▸ 全局默认)判定,而本插件刻意从不写这条链。由此有两个后果:切换到支持图片的模型,并不能让已经放进输入框的那张图片发出去——Core 仍按自己链上的模型判定,拒绝信息里写的也是那个模型,而不是刚选中的;以及当全局默认指向没有任何适配器提供的 Provider 时,对尚未成功发出过请求的会话,Core 会拒绝所有消息(文本同样如此,不只是图片),无论这里选了什么,需要先改选或清除全局默认才能解除。 - 会话选择的持久化边界:上文零消息会话选择不持久化的成因。会话的选择存放在内存引用里,真正的持久记录是
request/header事件,只有成功发出一次请求才会写下。在那次请求之前,任何读取该持久记录的一方都看不到这次选择——包括 Core 自己的准入链——服务重启后该会话回落到全局默认。 - 被 Core 自身模型切换接管过的会话:即使官方 Models 页面被禁用,Core 自己的
session.selectModel仍可被 ACP 与 SDK 客户端调用;它写入的那一层在 Core 链中优先级高于请求头,且在该会话的 Agent 存活期内没有任何清除路径。被这样切换过的会话,此后一律按 Core 的选择做准入,在本插件里的选择不再与之收敛,无论该会话之后又成功发出过多少次请求。
开源协议
MIT © 2026 FHGS


