@huai_xin/dsh-model-picker 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -115
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,140 +1,53 @@
|
|
|
1
1
|
# dsh-model-picker
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
给 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)的输入框换一个**模型选择器**:可搜索、按 provider 分组,模型和思考档位分成两个独立控件。
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
```sh
|
|
8
|
-
dsh plugin --profile <profile> add /path/to/dsh-model-picker # 本地目录
|
|
9
|
-
dsh plugin --profile <profile> add <包名> # 发布后
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
装完**刷新页面**(客户端模块在页面启动时加载)。
|
|
13
|
-
|
|
14
|
-
## 四个"很难查"的坑(都踩过)
|
|
5
|
+
DSH 自带的模型选择器是按两级菜单设计的。模型一多,找一个模型要翻两层;思考档位藏在二级菜单里,改一次要点三次。这个插件把它换成一次点开的列表:直接搜、直接选。
|
|
15
6
|
|
|
16
|
-
|
|
17
|
-
客户端模块扫描器是用 `createRequire(...).resolve('<包名>/package.json')` 定位包的,
|
|
18
|
-
解析失败就**直接跳过这个 entry**——没有报错、没有日志,插件只是永远不出现在浏览器里。
|
|
19
|
-
(`test/unit/client.mjs` 里钉住了这一条;skill 自带的模板恰好缺它。)
|
|
20
|
-
2. **不要在插件对象上声明 fiber 级 `inject`;把等待放进 `apply`。**
|
|
21
|
-
这一条是踩了很久才查清的。Cordis 里 fiber 级 `inject` 是一条**必须被满足的依赖边**:
|
|
22
|
-
服务不可见,`apply` 就永远不被调用,entry 停在 `loading`,然后整个渲染进程启动失败:
|
|
7
|
+
## 它做了什么
|
|
23
8
|
|
|
24
|
-
|
|
25
|
-
Error: web boot: 1 entry did not activate
|
|
26
|
-
@huai_xin/dsh-model-picker: loading
|
|
27
|
-
RendererStartupFailure: Renderer boot failed for 1 plugin(s)
|
|
28
|
-
```
|
|
9
|
+
**可搜索的模型列表** — 点开即输入,按名字或 id 过滤,按 provider 分组显示。
|
|
29
10
|
|
|
30
|
-
|
|
31
|
-
不会拖垮整个 boot。**注意这跟 `dsh.client.inject`(清单里那条)是两回事**——后者只影响
|
|
32
|
-
激活顺序,早先把它清空并没有解决问题,日志里一直是"`factory` 已运行、`apply` 未调用"。
|
|
33
|
-
3. **`ctx.remote` / `ctx.remote.session` 必须经 `inject`(或 `ctx.get`)读取。**
|
|
34
|
-
属性代理对未声明的服务直接抛 `cannot get property "remote.session" without inject`,
|
|
35
|
-
而这条异常发生在**组件首次渲染时**,表现为整个槽位消失:entry 被 `abdicate`
|
|
36
|
-
(`listSubTree` 里 `active: false`),官方 occupant 又已被压住,于是两边都不渲染。
|
|
37
|
-
4. **客户端半边要等宿主重启后才会被收录**:改完 `client.js` 只刷新页面,常常还是旧的。
|
|
11
|
+
**模型与思考档位分开** — 两个按钮、两个浮层,互不干扰。换模型不会顺手改掉档位,改档位也不会换模型。
|
|
38
12
|
|
|
39
|
-
|
|
40
|
-
`[model-picker] 客户端自检:…`(浏览器侧各阶段上报,见 `index.js` 的路由)。
|
|
41
|
-
宿主那行有、客户端一行都没有 ⇒ 模块没被下发;只有 `factory 已运行` 而没有 `apply 已调用`
|
|
42
|
-
⇒ entry 卡在依赖上(就是第 2 条)。启动失败时 `%APPDATA%/DSH Desktop/diagnostics/*.zip`
|
|
43
|
-
里的 `dsh-*.error.log` 会指名是哪个 entry。
|
|
13
|
+
**和官方选择器共用一份状态** — 不是另起炉灶。它复用 DSH 自己的 `modelDirectories` 服务,所以你在输入框里选的模型,和 `/model` 命令弹窗里看到的,永远是同一个。切换过程中(Host 还没确认时)两个入口都会显示"正在切换"。
|
|
44
14
|
|
|
45
|
-
|
|
15
|
+
**跟着模型列表实时变** — 别的插件改了可用模型(开关模型、导入账号、探测到模型不可用……),这个列表会立刻跟着变,不用刷新页面。
|
|
46
16
|
|
|
47
|
-
|
|
48
|
-
(可用性、促销、繁忙度…)。替换者想保住这些装饰,就得自己声明并渲染这个子槽位——**但这在
|
|
49
|
-
当前 slots 架构下做不到**,原因是两条硬规则:
|
|
17
|
+
**新会话显示默认模型** — 而不是空着或显示"没有可用模型"。
|
|
50
18
|
|
|
51
|
-
|
|
52
|
-
(只是被 priority 压住不渲染),它的声明仍然有效,我们再声明一次就抛错。
|
|
53
|
-
2. **`renderSlot` 只能渲染自己声明的子槽位**,否则抛 `SlotOwnershipError`。不声明就无权渲染。
|
|
19
|
+
**子会话里自动隐藏** — 在被指派的 subagent 会话里,Host 不允许换模型,控件直接不渲染,而不是给你一个点了必失败的按钮。
|
|
54
20
|
|
|
55
|
-
|
|
56
|
-
**整个选择器消失**。所以现在**不声明 `children`**:picker 正常渲染,但 provider 挂在
|
|
57
|
-
`conversation.input.model.label` 上的装饰在这个槽位里不显示。
|
|
58
|
-
|
|
59
|
-
真要同时满足两者,只有让官方 `ui-model-selection` 的 occupant 完全缺席(停用那个 bundle),
|
|
60
|
-
代价是它的 `/model` 命令贡献一起消失。这是架构限制,不是本插件的实现缺陷。
|
|
61
|
-
|
|
62
|
-
## 数据层(与官方 `ui-model-selection` 同一套契约)
|
|
63
|
-
|
|
64
|
-
数据不是自己拼的,走的是和官方相同的三层结构,`test/unit/client.mjs` 逐条钉住:
|
|
65
|
-
|
|
66
|
-
**1. 全局目录(`ModelCatalogDirectory` 的等价物)**
|
|
67
|
-
- 一份、按 Host generation 作用域,所有会话共用;in-flight 请求去重。
|
|
68
|
-
- 每次失效递增 generation,旧请求的迟到响应**直接丢弃**,不会覆盖新值。
|
|
69
|
-
- `reasoning` 记住每个模型的档位元数据,**模型离开目录后仍能显示档位名字**。
|
|
21
|
+
## 安装
|
|
70
22
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
而不是模型名,也不是"没有可用模型"。
|
|
75
|
-
- `pending` 在 RPC 前后发布,触发器能显示正在结算的选择。
|
|
76
|
-
- `select()` 带 generation 竞态保护:**后发起的胜出**,旧回复即便晚到也不写入。
|
|
77
|
-
(单测用突变验证过:去掉这个守卫,断言立刻失败。)
|
|
78
|
-
- 切模型时带上适配器的默认档位,与官方提交的三元组一致。
|
|
79
|
-
- `failures` 渲染为提示行,某个 provider 组加载失败不再静默消失。
|
|
23
|
+
```sh
|
|
24
|
+
dsh plugin --profile <你的 profile> add @huai_xin/dsh-model-picker
|
|
25
|
+
```
|
|
80
26
|
|
|
81
|
-
|
|
82
|
-
- **优先复用官方已注册的服务**——这正是让输入框和 `/model` 弹窗共享同一份选择状态(含在途
|
|
83
|
-
状态)的关键。官方服务存在时不重复注册(重复 `ctx.provide` 会抛错)。
|
|
84
|
-
- 官方服务缺席时(bundle 被停用、旧版本),本插件以同名提供一份**同契约**的实现,组件无感知。
|
|
85
|
-
- 按 sessionId 键控目录,不按 binding 对象:binding 访问器可能每次返回新包装,按它做键会导致
|
|
86
|
-
每次重挂载都新建目录、丢掉在途状态。
|
|
27
|
+
装完**重启宿主**,再刷新页面。客户端代码是在页面启动时加载的,只刷新页面通常还是旧的。
|
|
87
28
|
|
|
88
|
-
|
|
89
|
-
的旧版会在改设置/凭据后继续提供宿主已拒绝的模型。
|
|
29
|
+
也可以从本地目录装:
|
|
90
30
|
|
|
91
|
-
|
|
92
|
-
|
|
31
|
+
```sh
|
|
32
|
+
dsh plugin --profile <你的 profile> add /path/to/dsh-model-picker
|
|
33
|
+
```
|
|
93
34
|
|
|
94
|
-
|
|
95
|
-
这两项属于官方 bundle,不在"替换输入框选择器"的职责内。
|
|
35
|
+
不想要了就直接停用或移除这个 bundle,不会影响模型本身是否可用。
|
|
96
36
|
|
|
97
|
-
##
|
|
37
|
+
## 已知限制
|
|
98
38
|
|
|
99
|
-
|
|
39
|
+
**会丢掉 provider 插件挂在模型名上的装饰。** DSH 官方选择器声明了一个子槽位 `conversation.input.model.label`,provider 插件往那里挂徽标、可用性提示、促销标记之类的东西。替换者没法继承这个声明——子槽位只能声明一次,官方占用者即使被压住不渲染,它的声明依然有效。所以在这个选择器里,模型名后面不会有那些装饰。
|
|
100
40
|
|
|
101
|
-
|
|
102
|
-
src/
|
|
103
|
-
client.js 入口:模块注册 + 插件对象 + 装配(含服务复用与槽位注册)
|
|
104
|
-
core/
|
|
105
|
-
store.js snapshot store 契约(getSnapshot/subscribe/set/update)
|
|
106
|
-
selection.js readSelection(兼容各代字段拼写)、isAvailable(子会话探测)
|
|
107
|
-
catalog.js 全局目录:共享缓存、generation 失效、reasoning 记忆
|
|
108
|
-
directory.js 会话级目录:current/routable/pending/failures + 竞态保护
|
|
109
|
-
directories.js modelDirectories 服务的自带实现(官方缺席时使用)
|
|
110
|
-
ui/
|
|
111
|
-
ModelPicker.js 组件:触发器 + 模型面板 + 档位面板
|
|
112
|
-
Chevron.js 展开箭头
|
|
113
|
-
dict.js 中英字典
|
|
114
|
-
styles.js 样式表(仅 --dsw-alias-* 主题令牌)
|
|
115
|
-
scripts/build.mjs 零依赖打包器:按依赖顺序拼接 src/ → client.js
|
|
116
|
-
client.js 构建产物,由 scripts/build.mjs 生成
|
|
117
|
-
```
|
|
41
|
+
想同时要装饰和这套交互,只能停用官方的 `ui-model-selection` bundle,代价是它的 `/model` 命令一起消失。这是 DSH 槽位架构的限制,不是本插件能绕过的。
|
|
118
42
|
|
|
119
|
-
|
|
120
|
-
(`exports["./client"]`),并以 classic script 执行,不会在运行时解析插件的 ESM 依赖图。
|
|
121
|
-
所以源码拆开维护、构建时拼接成单文件,是既能保持可维护性、又不要求宿主提供它没有的能力的做法。
|
|
43
|
+
## 兼容性
|
|
122
44
|
|
|
123
|
-
|
|
124
|
-
拼接。它不做 tree-shaking,也不解析裸模块——遇到裸 import 会直接报错(宿主模块应从 factory
|
|
125
|
-
的 `require` 取)。
|
|
45
|
+
需要 DSH `0.2.1-rc` 及以上。模型数据全部走 DSH 自己的 client remote(`session.modelCatalog` / `session.selectModel`),插件本身不认识任何具体 provider——任何 provider 插件注册的模型都会出现在列表里。
|
|
126
46
|
|
|
127
|
-
|
|
128
|
-
node scripts/build.mjs # 或 pnpm build:重新生成 client.js
|
|
129
|
-
node test/unit/client.mjs # 或 pnpm test:构建 + 离线单测
|
|
130
|
-
```
|
|
47
|
+
## 开发
|
|
131
48
|
|
|
132
|
-
|
|
133
|
-
任何源文件),避免改完源码忘记重新构建、让测试跑在过期产物上。
|
|
49
|
+
见 [AGENTS.md](AGENTS.md)。
|
|
134
50
|
|
|
135
|
-
##
|
|
51
|
+
## License
|
|
136
52
|
|
|
137
|
-
|
|
138
|
-
pnpm build # 重新生成 client.js
|
|
139
|
-
pnpm test # 构建 + 离线单测(无依赖,自带迷你 React)
|
|
140
|
-
```
|
|
53
|
+
MIT
|