converk/dsh-ux-kit--plugins-model-capabilities ↗★ 0
dsh-plugin-model-capabilities
在 DSH 设置→模型的官方模型行里补上思考强度(llm-pi-ai)与多模态(llm-pi-ai / llm-deepseek),并提供 1M/128K 容量快捷填入
安装
此插件尚未提供可验证的 bundle,或兼容性检查未通过。请先阅读仓库说明。 阅读完整 README ↗
说明文档
阅读完整 README ↗dsh-plugin-model-capabilities
在 DSH Web GUI 的 设置 → 模型 → 编辑提供方 → 自定义设置 → 模型行(展开「容量」) 里,给官方模型编辑器补上官方没有的能力字段。插件同时挂两个提供方卡片 key(同一份实现):
llm-pi-ai:- 思考强度:只提供 5 档
off / low / medium / high / max;每档可以改「过线拼写」(例如high在别的网关叫ultra)。一个档都不勾 = 该模型不支持思考(写reasoningEfforts: false)。 - 多模态:单选
文本/文本+图片(写input)。
- 思考强度:只提供 5 档
llm-deepseek:只加 多模态(写inputModalities)。DeepSeek 适配器没有每模型思考强度——档位由提供方级reasoningEffort: off|low|high|max+thinking: enabled|disabled控制(settings.yaml的llm-deepseek分节根级),模型行里只读显示当前提供方级值。
两个命名空间都给官方「上下文窗口 / 最大输出」输入框加 1M / 128K 快捷按钮;新增模型行展开容量时自动填 1000000 / 131072。
展开模型行时,控件顶部显示该模型当前配置摘要(DeepSeek 的「思考」位置显示提供方级值):
当前:思考 high + max · 多模态 文本+图片 · 上下文 1000000 / 输出 131072
改完点官方那个「保存」即可——插件会在官方写入完成后,把字段写进对应分节(llm-pi-ai 的 input / reasoningEfforts,llm-deepseek 的 inputModalities)的模型条目里。纯 client 侧插件,宿主半区为空壳;运行时零第三方依赖,只 require 页面种子模块 react / react/jsx-runtime。
交互与同步时机
设置 → 模型 → 某个 llm-pi-ai / llm-deepseek 提供方 → 编辑 → 自定义设置 → 模型行 → 点「容量」箭头展开
当前:思考 high + max · 多模态 文本+图片 · 上下文 1000000 / 输出 131072
思考强度 [协议预置 ▾] ← llm-pi-ai 才有;llm-deepseek 显示一行提供方级提示
☑ off [留空 = 不发参数]
☐ low [low]
☑ medium[medium]
☑ high [high]
☑ max [ultra]
都不勾选 = 该模型不支持思考(写 false)
多模态 ○ 文本 ○ 文本+图片
[1M / 128K] 已改,点官方「保存」后写入
-
llm-deepseek卡片里没有「思考强度」这一块,只显示一行提示:DeepSeek 适配器没有每模型思考强度;档位由提供方级 reasoningEffort / thinking 控制(settings.yaml)。;其余(多模态单选、1M / 128K、摘要)相同。 -
官方模型行内部没有 Slot(官方只声明了
settings.models.provider-card卡片级席位),所以插件在席位挂载后观察卡片 DOM,把控件注入到模型行展开后的「容量」区域(_modelAdvanced)。锚点是官方 class 的稳定后缀_modelEntry/_modelRow/_modelAdvanced/_editorActions,并带 DOM 兜底;DSH 升级若改这些结构,需要重新核对(见「对接契约」)。 -
展开即显示当前值:控件从用户层已存条目回填;摘要同时读官方「上下文窗口 / 最大输出」输入框的当前值(没有值就显示官方占位符 = 提供方默认)。
-
同步时机:官方卡片把模型列表存在它自己的 React 草稿里,点「保存」才一次性写 settings。插件在官方写入之后再写自己的字段,避免被官方草稿用旧值覆盖。因此:
- 控件改动先挂起,行内提示「已改,点官方『保存』后写入」;
- 点官方「保存」:官方先写它自己的字段;插件捕获主按钮点击,等
settings/document-updated(或卡片关闭且官方没有产生写入)后用最新 revision 合成一次settings.mutate; - 点官方「取消」:挂起改动丢弃;
- 写失败(例如
settings/conflict、settings-rejected)原样显示 Host 的 code/message,挂起改动保留,可再点一次「保存」重试。
-
上下文 / 最大输出归官方管:插件不解析、不写这两个字段,只通过
1M / 128K按钮走官方输入框的input事件(React 受控输入)把值收进官方草稿;官方「保存」时自然写入。 -
没有「跟随目录」:插件总是写显式值——不勾档位写
false,勾了就写 dict,多模态写['text']或['text','image']。内置目录的minimal/xhigh不显示也不管理(保存时若原本有这两个键,会被钉成「不支持」)。 -
添加提供方草稿卡片(
configured === false)不注入:官方添加卡片用它打开时的 revision 一次性创建整个 profile,插件此时写入会抢 revision;等提供方保存后再进编辑页配置。
写入位置
一律使用 owner props 的 provider.settingsPath(指向 providers.),不硬编码路径。
| 官方编辑器显示的内容 | 插件写哪里 |
|---|---|
用户层已有 models[] | 改数组里的条目(整组改动合成一次 set models;其它条目与 name / compat / contextWindow / maxTokens 原样保留) |
用户层没有 models[](显示的是内置目录的继承行) | 用官方编辑器当前列出的全部模型 id 物化 models[]:未编辑的条目只写 id,其余字段按 id 继续继承内置目录;同时删掉可能存在的 modelOverrides |
第二种和官方编辑器你改任一官方字段时的行为一致(官方也会把继承行整体物化成
models[]),不会把目录压成一行:models[]里列出了官方编辑器显示的全部 id。如果用户层同时有models[]与modelOverrides(本体拒绝的组合),插件直接报storage-conflict,不写。
字段映射与校验(对齐 dsh-llm-pi-ai / dsh-llm-deepseek)
llm-pi-ai(reasoningEfforts + input):
| 控件 | 字段 | 规则 |
|---|---|---|
| 思考强度:一个档都不勾 | reasoningEfforts: false | 该模型不支持思考 |
| 思考强度:勾了档位 | reasoningEfforts: { : } | 只写勾选的 5 档;未勾选的档位不写键,本体把它们钉成「不支持」;off 勾选且留空写 null(不发参数);非 off 档必须非空字符串;只勾 off 会被拒(等于「不发参数」却没有思考档) |
| 多模态:文本 | input: ['text'] | 词表只有 text / image |
| 多模态:文本+图片 | input: ['text', 'image'] | |
1M / 128K 按钮 | 官方 contextWindow / maxTokens 输入框 | 写入 1000000 / 131072;由官方编辑器保存 |
llm-deepseek(只有多模态;思考强度是提供方级):
| 控件 | 字段 | 规则 |
|---|---|---|
| 多模态:文本 | inputModalities: ['text'] | 词表只有 text / image(注意字段名是 inputModalities,不是 input) |
| 多模态:文本+图片 | inputModalities: ['text', 'image'] | |
1M / 128K 按钮 | 官方 contextWindow / maxTokens 输入框 | 写入 1000000 / 131072;由官方编辑器保存 |
| 思考强度 | 不写每模型字段 | DeepSeek 适配器没有每模型 reasoningEfforts;档位由 llm-deepseek 分节根级 reasoningEffort: off|low|high|max + thinking: enabled|disabled 控制,模型行只读显示当前提供方级值 |
- 协议预置只有 OpenAI 兼容 / Anthropic 两个(外加「自定义」),只填档位表,绝不写
compat;compat.thinkingFormat等协议字段仍由settings.yaml管理。 models[]是数组,settings 服务的 path op 把数组当不透明值处理(不穿下标),所以模型数组的改动合成一次set models,从保存前重新读到的最新用户层重建。
构建与检查
pnpm install
node build.mjs # lib/index.js + lib/capability.js + lib/client.js(+.map)
node node_modules/typescript/bin/tsc --noEmit # 0 错
node scripts/selftest.mjs # 纯逻辑 24 项全过
pnpm 10+ 默认拦截依赖构建脚本(会提示
ERR_PNPM_IGNORED_BUILDS: esbuild);esbuild 的平台二进制经 optionalDependencies 分发,postinstall 并非必需——直接node build.mjs/node node_modules/typescript/bin/tsc --noEmit即可,不要走pnpm run。
产物协议(AGENTS.md 3.4):lib/client.js 是经典脚本(非 ES Module),自注册 window.__ModuleLoader__.load({ id: "dsh-plugin-model-capabilities", factory }),工厂体内只 require("react") / require("react/jsx-runtime"),并带合法 sourcemap trailer //# sourceMappingURL=client.js.map。
安装到 DSH
本包是独立 npm 包,不依赖仓库内任何其他插件:
dsh plugin --profile add D:\env\Projects\dsh-plugins\plugins\model-capabilities
然后在 profile 的 cordis.patch.yml 里用 insert 形制挂载(裸 - name: 会被 dsh-app-boot 的 applyEntryPatches 跳过并 warn patch: id is required for non-insert patches):
- insert:
- id: model-capabilities
name: dsh-plugin-model-capabilities
patchReload: live 下保存 patch 即可重新组合,但浏览器需要刷新一次才会拿到新的 window.__DSH_BOOT__。
对接的真实契约(本机 DSH 0.1.2-rc.1,部署于 D:\env\node-global\dsh-stable)
- 席位:
dsh-client-ui-settings-models/lib/types/client/slot-contract.d.ts(settings.models.provider-card,kindkeyed,scoperoot,owner =ProviderCardExtrasOwnerProps)与store.d.ts(ProviderDirectoryEntry的settingsPath/declared等);席位渲染点见该包lib/client.js。 - 官方模型行 DOM(注入锚点):同包
lib/client.js的DeepSeekModelsEditor/ModelListEditor——section._modelCatalog > div._modelList > div._modelEntry > div._modelRow(模型 ID、显示名称、容量箭头、删除),展开后div._modelAdvanced里是两个input(上下文窗口 / 最大输出);官方保存/取消按钮在div._editorActions里(最后一个是主按钮)。 - 字段语义:
dsh-llm-pi-ai/lib/types/{config,catalog}.d.ts与lib/index.js的resolveModelReasoning/resolveRouteModels;reasoningEfforts未勾选档写null、off可留空、models[]条目按 id 继承目录字段。 - DeepSeek 字段语义:
dsh-llm-deepseek/lib/index.js的catalogModelschema——每模型只有contextWindow/maxTokens/inputModalities/imagePixelBudget/imageMaxBytes,没有reasoningEfforts;Config.reasoningEffort: off|low|high|max与Config.thinking: enabled|disabled是分节根级字段。 - Remote:
dsh-api-settings-controller/lib/typert.remote-client.d.ts(settings/describe、settings/mutate);一元调用一律解析为RemoteResult。inject必须声明remote.settings——ctx.remote的子命名空间是 cordis Guard 代理,未声明直接抛cannot get property "remote.settings" without inject。 - settings 服务:
dsh-settings/lib/types/index.js的applyPathOp(数组不透明)与mutate(revision 冲突settings/conflict)。 - locale:
dsh-client-locale/lib/types/client/index.d.ts的三参非类型化register(ns, locale, dict)+bind(ns);插值{name}。 - 样式令牌:官方
dsh-client-ui-settings-models/lib/client.js内嵌 CSS(输入 h32 radius8、border--dsw-alias-border-l4、bg-layer-1;小按钮 h28 radius14;label 12px/500);CSS 以style[data-plugin-css="/.module.css"]+data-plugin幂等注入。
写入后 DSH 如何生效(本机实测 + 官方讨论)
插件写的是 llm-pi-ai.providers..models[] 条目里的 reasoningEfforts / input、llm-deepseek.providers..models[] 条目里的 inputModalities(上下文与最大输出由官方输入框写同一条目的 contextWindow / maxTokens)。这不是「写了没人读」:
- 本机实测:用部署里的官方
@deepseek-ai/dsh-llm-pi-ai的apply+PiAiAdapter解析一份与插件写出的完全相同的配置(input: [text, image]、reasoningEfforts: { off: null, low: low, medium: medium, high: high, max: ultra }、contextWindow: 1000000、maxTokens: 131072),适配器返回:listModels('demo')→inputModalities: ['text','image']resolveModel('demo','demo-vision-thinker')→context.contextWindow: 1000000、defaultMaxTokens: 131072、reasoning.efforts: [off, low, medium, high, max](max的过线拼写是ultra)
- 图片门禁:
dsh-api-session-controller在附加图片时检查inputModalities,不包含image就直接拒绝(Model "..." does not support image input.);dsh-llm的请求投影也用它决定是否把图片发给模型。所以input: ['text','image']就是「这个模型支持多模态」的开关。 - DeepSeek 多模态:
dsh-llm-deepseek的catalogModelschema 里inputModalities就是每模型字段(默认["text"],允许["text","image"]);适配器把它投影成LlmModelInfo.inputModalities,同一个dsh-api-session-controller门禁生效。官方讨论 #5267 正是要求把inputModalities暴露到 Web 模型编辑器,插件补的就是这个入口。 - 思考强度:官方 README(
dsh-llm-pi-ai/README.zh.md)明确reasoningEfforts的每个键是选择器提供的等级、值是该等级过线的拼写(max: ultra可以改名);dsh-client-ui-model-selection从model.reasoning.efforts生成 composer 的档位选项。DeepSeek 侧没有每模型字段,档位来自Config.reasoningEffort/Config.thinking(提供方级)。 - 官方讨论:
- #5267 Expose per-model image-input capability in the web Models editor:官方 Web 模型编辑器没有这个入口;解决方案就是每模型
input,语义为继承(删掉 input)/ 仅文本 [text] / 文本+图片 [text, image],解析链是「条目input→ 目录base.input→ 路由 fallback」。本插件按用户要求去掉了「继承」,只留后两档。 - #5837 custom provider entries silently resolve to text-only …:自定义提供方不写
input就会被解析成 text-only,正是插件要补的洞。 - #636 第三方模型设置思考强度报错:
reasoningEfforts的 YAML 示例与「界面出现可选档位」的确认。
- #5267 Expose per-model image-input capability in the web Models editor:官方 Web 模型编辑器没有这个入口;解决方案就是每模型
- 写入位置就是官方唯一读的地方:
providers..models[](或modelOverrides.);dsh-llm-pi-ai的resolveRouteModels把input/contextWindow/maxTokens/reasoningEfforts合并成 pi-aiModel,适配器再通过modelInfo暴露给会话与 composer。
自测覆盖
scripts/selftest.mjs 直接 import 构建产物 lib/capability.js,24 项覆盖:
- 只有
off/low/medium/high/max五档;draftFromEntry忽略minimal/xhigh; - 一个档都不勾 →
false;只勾off被拒;非off档空拼写被拒;未勾选档不写键;off留空写null; - 多模态始终显式
['text']/['text','image']; - 用户层已有
models[]→ 一次set models,保留其它条目与name/compat/description/contextWindow/maxTokens; - 没有
models[]→ 用官方编辑器可见 id 完整物化models[]并删modelOverrides;模型还没保存 / 没有可见 id /models与modelOverrides并存分别被拒; - 改动与已存值相同不产生 op;清空档位写
false且保留name/compat;协议预置只填档位表、绝不写compat;1M / 128K预设的确切值; - DeepSeek:
draftFromEntry读inputModalities;manageReasoning: false不产生reasoningEfforts;buildModelOps只写inputModalities并保留contextWindow/maxTokens/description/imagePixelBudget/imageMaxBytes,不加reasoningEfforts。
边界与已知限制
- 不 import / 依赖任何其他插件,不共享 utils;删除本插件不影响 DSH 与其他插件(AGENTS.md 4.1 / 4.4)。
- 只写
llm-pi-ai/llm-deepseek分节下providers.的模型条目(llm-deepseek只写inputModalities),不动其他用户配置。 - 在
settings.models.provider-card上占llm-pi-ai与llm-deepseek两个 key(同一份实现),不注册第二个席位。 - DeepSeek 适配器没有每模型思考强度;插件不伪造该字段,模型行只读显示提供方级
reasoningEffort/thinking,要改请直接改settings.yaml的llm-deepseek分节根级。 - 官方模型行内部无 Slot,注入依赖官方 DOM 结构;若 DSH 升级改了
_modelEntry/_modelAdvanced/_editorActions这些 class 后缀或行结构,控件可能不再出现,需要按「对接契约」重新核对。 - 官方模型目录头里的按钮(
恢复默认模型/获取可用模型)全部保留,插件不隐藏、不拦截它们。 - 必须点官方「保存」才会落盘;只改插件控件不点保存不会写入(行内有提示)。
- 没有「跟随目录」:插件总是写显式值,所以对内置目录的继承模型,第一次保存会把
reasoningEfforts/input(或inputModalities)写成显式值(不会再把目录能力当作默认)。