@zhz8888/dsh-radeon-cloud-patcher 1.1.1 → 1.2.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 CHANGED
@@ -2,12 +2,18 @@
2
2
 
3
3
  把 [AMD Radeon Cloud](https://amd-aim.github.io/radeon-cloud-docs/) 接入 DeepSeek Harness 的模型选择器,**完整支持模型思考(reasoning)功能**。
4
4
 
5
- 包名 `@zhz8888/dsh-radeon-cloud-patcher`。它把 provider 定义**按键合并**进你的 profile,由 DSH 自带的
6
- `dsh-llm-pi-ai`(通用 OpenAI 兼容 provider)承载——**不修改 DSH 本体,不需要自研适配器**。
5
+ 包名 `@zhz8888/dsh-radeon-cloud-patcher`。provider 定义由插件**自带的补丁层**声明,
6
+ 由 DSH 自带的 `dsh-llm-pi-ai`(通用 OpenAI 兼容 provider)承载——**不修改 DSH 本体,
7
+ 不需要自研适配器,也不往你的 profile 里写任何东西**。
7
8
 
8
- > 之所以叫 patcher 而不是 provider,是因为本项目**不承载任何 provider 逻辑**。
9
- > DSH 的补丁语义是整体替换,直接用补丁改写 `llm-pi-ai` 的 config 会把你自己添加的
10
- > provider 一并抹掉;因此写入被降到键的粒度,只动 `providers.radeon-cloud`。
9
+ > 之所以叫 patcher,是因为它靠**补丁层**交付:定义写在插件自己的
10
+ > `cordis.plugin.patch.yml` 里。这样做的直接后果有三个,也正是这个设计的目的——
11
+ >
12
+ > | 现象 | 原因 |
13
+ > | --- | --- |
14
+ > | 设置页**没有**「删除」按钮 | DSH 只在「该 provider 路径存在于用户层、且不存在于 base 层」时渲染它;定义在插件的 bundle 层,base 层永远有它 |
15
+ > | 卸载插件 = 删除供应商 | 定义只活在插件里,profile 中不留副本 |
16
+ > | 想删供应商只能卸载插件 | 同上;设置页里也点不到删除 |
11
17
 
12
18
  ![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)
13
19
  ![target: DSH](https://img.shields.io/badge/DSH-0.2.x-blue?label=target)
@@ -15,11 +21,49 @@
15
21
 
16
22
  ---
17
23
 
24
+ ## 快速上手
25
+
26
+ 装好 DSH、手上有 Radeon Cloud 的 API 密钥(`rc-…`),三步就能用起来,
27
+ **不需要编辑任何配置文件**:
28
+
29
+ 1. **装插件** —— 桌面版在左侧「插件」页里添加 `@zhz8888/dsh-radeon-cloud-patcher`;
30
+ 命令行执行
31
+ `dsh plugin --profile <profile> add @zhz8888/dsh-radeon-cloud-patcher`。
32
+ 装不上(pnpm 报 404)就改用[安装](#安装)里的方式三,从源码装同一个包。
33
+ 2. **重启 DSH** —— 定义写在补丁层的 config 行里,插件管理器对这类变更只会给出
34
+ `restart-required`,不会在运行中生效。重启后 provider 就位,没有其它后置步骤。
35
+ 3. **填密钥** —— 左下角「账号菜单 → 设置 → 模型」,找到 **Radeon Cloud** 一行,
36
+ 点「编辑」,在「API 密钥」里粘贴密钥,点「保存」。
37
+
38
+ 装好之后:那一行右侧出现**绿点**(悬停显示「API 密钥已配置」);回到对话,点输入框下方的
39
+ 模型选择器,Radeon 的 8 个模型会出现在列表里;选中模型后再选「推理等级」就是思考档位。
40
+
41
+ **怎么确认真的装好了** —— 三个现象同时成立,说明插件与它的补丁层都到位:
42
+
43
+ | 现象 | 在哪看 |
44
+ | --- | --- |
45
+ | 模型选择器里出现 Radeon Cloud 的模型 | 输入框下方的模型选择器 |
46
+ | 那一行**没有**「删除」按钮 | 设置 → 模型 → Radeon Cloud 行 |
47
+ | 那一行**不带**「自定义」标签 | 同上 |
48
+
49
+ **没生效怎么办** —— 插件不会静默消失:定义失效时它**直接让启动失败并写明原因**
50
+ (profile 里已有同 id 定义、profile 的 `llm-pi-ai` 配置把插件层整份盖掉……),
51
+ 逐条处置见[归属与删除](#归属与删除)。想手工核验真正生效的配置,用
52
+ `dsh --profile <profile> --dump-config`(`desktop` profile 例外,它由桌面应用独占管理)。
53
+
54
+ > 更细的安装方式(npm 源、镜像、GitHub、本地 tarball、手工清单)见[安装](#安装);
55
+ > 密钥、模型、档位、图片输入与升级卸载见[使用](#使用)。
56
+
57
+ ---
58
+
18
59
  ## 目录
19
60
 
61
+ - [快速上手](#快速上手)
20
62
  - [功能](#功能)
21
63
  - [环境要求](#环境要求)
22
- - [快速开始](#快速开始)
64
+ - [安装](#安装)
65
+ - [使用](#使用)
66
+ - [归属与删除](#归属与删除)
23
67
  - [项目结构](#项目结构)
24
68
  - [常用命令](#常用命令)
25
69
  - [可用模型](#可用模型)
@@ -76,7 +120,7 @@ Radeon 的响应体与流式分片都存在一个容易致错的形态:思考
76
120
 
77
121
  | 项 | 要求 | 说明 |
78
122
  | --- | --- | --- |
79
- | DSH | `0.2.x`(含预发布) | 由 `peerDependencies` 声明范围,版本不匹配会被安装器拒绝 |
123
+ | DSH | `>=0.2.0-rc.1`(含预发布,无上界) | 由 `peerDependencies` 声明范围,低于下界会被安装器拒绝;上界不写,DSH 升大版本不需要插件跟着重发(见[版本闸门](#版本闸门)) |
80
124
  | Node.js | `>=22` | 与 `package.json` 的 `engines.node` 一致;仅运行 `scripts/` 与 `test/` 下的脚本时需要 |
81
125
  | bash | 任意 | 仅 `scripts/radeon-api.sh` 需要 |
82
126
  | 操作系统 | macOS | `scripts/validate-config.mjs` 按 DSH 桌面版应用包的绝对路径加载校验用的 schema,路径为 `/Applications/DSH Desktop.app/Contents/Resources/app/node_modules`。其余脚本与系统无关 |
@@ -85,88 +129,191 @@ Radeon 的响应体与流式分片都存在一个容易致错的形态:思考
85
129
 
86
130
  ---
87
131
 
88
- ## 快速开始
132
+ ## 安装
133
+
134
+ 四种方式,任选其一。**装完都要重启 DSH**(原因见[快速上手](#快速上手)第 2 步):
135
+ 定义由插件自带的补丁层声明,不写入你的 profile,重启后即生效,没有其它后置步骤。
89
136
 
90
- ### 1. 安装插件
137
+ ### 方式一:DSH 插件管理器(推荐)
91
138
 
92
- 推荐用 DSH 的插件管理器安装(插件 → 添加插件,输入包名或本地目录路径):
139
+ 桌面版:应用左侧「插件」页 → 「添加插件」→ 输入包名或本地目录路径。
140
+
141
+ 命令行:`dsh plugin --profile <profile> …` 会把参数**原样转发**给 profile 目录里的 pnpm,
142
+ 因此 pnpm 支持的写法它都支持:
93
143
 
94
144
  ```bash
95
145
  dsh plugin --profile <profile> add @zhz8888/dsh-radeon-cloud-patcher
146
+ dsh plugin --profile <profile> remove @zhz8888/dsh-radeon-cloud-patcher # 卸载
96
147
  ```
97
148
 
98
- 也可以直接编辑 profile 的 `package.json`,在 `dsh.profile.bundles` 中加入本包名,
99
- 再执行 `dsh plugin --profile <profile> install`。
149
+ 装完 `dsh.profile.bundles` 会自动多出本包名——插件管理器按各包的 `dsh.bundle` 声明维护它,
150
+ 不必手改 profile 清单。
100
151
 
101
- > 本包通过 `peerDependencies` 里的 `@deepseek-ai/dsh-*` 范围声明支持 DSH `0.2.x`
102
- > (含 `0.2.0-rc.1` 及之后的全部预发布与正式版)。DSH 安装器会把每个
103
- > `@deepseek-ai/dsh-*` peer 与当前运行时版本比对,**不匹配则直接拒绝安装**,
104
- > 不会出现「装上了但思考功能失效」的情况。
152
+ > **桌面版的 `desktop` profile 由应用独占管理**:在普通终端里执行
153
+ > `dsh plugin --profile desktop …` 会被拒绝(`profile "desktop" is managed exclusively by
154
+ > the Electron application`)。两条正路:用应用内的插件页安装,或在应用右上角
155
+ > 「打开 DSH 终端」里执行同样的命令。`web` / `tui` / `headless` 等 profile 不受此限。
105
156
  >
106
- > 范围写成 `>=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-0 <0.3.0-0` 的显式双分支,
107
- > 是为了同时满足 DSH 的闸门判定与 npm/pnpm 的 peer 解析:node-semver 只有当范围里
108
- > 某个比较符与该版本的 `major.minor.patch` 元组完全一致、且自身也带预发布标签时,
109
- > 才会放行预发布版本。单写 `>=0.2.0-rc.1 <0.3.0-0` 会把 `0.2.1-alpha.1` 这类
110
- > **不同元组**的预发布静默排除。
157
+ > 若你的源上还没有这个包(pnpm 报 404),用方式三装同一个包。
111
158
 
112
- ### 2. 写入 provider 定义
159
+ ### 方式二:从 npm 源安装
113
160
 
114
- 插件只负责分发与看护;provider 定义要落到 profile 的 `cordis.patch.yml` 里,
115
- 由 `llm-pi-ai` 承载。这一步是**按键合并**,不是整体替换:
161
+ 方式一走的就是 npm registry;换源只需给 pnpm 传参,或写进 profile 自己的 `.npmrc`:
116
162
 
117
163
  ```bash
118
- # 先演练:会打印本次改动,以及被保留的其它 provider
119
- pnpm install:profile:dry
164
+ # 单次指定镜像源
165
+ dsh plugin --profile <profile> add @zhz8888/dsh-radeon-cloud-patcher \
166
+ --registry https://registry.npmmirror.com
120
167
 
121
- # 确认无误后写入(自动备份,断言其余条目与同级键逐字未变)
122
- pnpm install:profile
168
+ # 或给这个 profile 固定源(.npmrc 是 pnpm 自己的配置文件)
169
+ printf 'registry=https://registry.npmmirror.com\n' >> ~/.dsh/profiles/<profile>/.npmrc
170
+
171
+ # 指定版本或范围(pnpm 语法)
172
+ dsh plugin --profile <profile> add @zhz8888/dsh-radeon-cloud-patcher@<版本>
123
173
  ```
124
174
 
125
- 输出示例:
175
+ > 本包的 tarball 里只有插件自己的源码、定义与脚本(不含 `test/` 与 `docs/`),
176
+ > **没有安装期构建脚本**,因此换源、离线镜像、内网私服都不会碰到 pnpm 的
177
+ > `allowBuilds` 授权那一步。
126
178
 
127
- ```
128
- 【写入】 ~/.dsh/profiles/desktop/cordis.patch.yml
129
- 新增 providers.radeon-cloud(8 个模型)
130
- 保留的其它 provider:my-corp-proxy, another-one
131
- 顶层条目 12 个,增减 0 个
179
+ ### 方式三:从源码 / GitHub 安装
180
+
181
+ 三种源码形态都直接可用,原因同上:本项目零运行时依赖、没有构建步骤。
182
+
183
+ ```bash
184
+ git clone https://github.com/zhz8888/dsh-radeon-cloud-patcher.git
185
+
186
+ # ① 本地目录:pnpm 做 link,仓库里改了代码重启 DSH 即生效(开发本插件时最省事)
187
+ dsh plugin --profile <profile> add /绝对路径/dsh-radeon-cloud-patcher
188
+
189
+ # ② git 依赖:直接装公开仓库
190
+ dsh plugin --profile <profile> add github:zhz8888/dsh-radeon-cloud-patcher
191
+
192
+ # ③ 本地 tarball:适合内网分发(npm pack 在仓库目录里执行)
193
+ npm pack
194
+ dsh plugin --profile <profile> add ./zhz8888-dsh-radeon-cloud-patcher-<版本>.tgz
132
195
  ```
133
196
 
134
- > **合并粒度是键**:只新增或替换 `providers.radeon-cloud` 这一个键。
135
- > 你在 `llm-pi-ai` 下自行添加的 provider、你在补丁文件里写的注释、缩进风格与
136
- > 键序,都原样保留。合并采用文本级编辑,不会重新序列化你的文件。
137
- > 详见[已知限制](#6-与用户自有-provider-共存)。
197
+ > DSH 对「装 git 依赖」有一句提醒:带 `prepare` 脚本的插件会在安装期构建,
198
+ > 而 pnpm 默认拦住构建脚本,需要先在 profile 的 `pnpm-workspace.yaml` 里追加
199
+ > `allowBuilds` 才能装上。**本插件不属于这种情况**——它没有任何安装期脚本。
200
+
201
+ ### 方式四:手工改 profile 清单
202
+
203
+ 不用插件管理器时:
204
+
205
+ 1. 在 profile 的 `package.json` 里把本包加进 `dependencies`;
206
+ 2. 把包名追加进 `dsh.profile.bundles`(顺序即补丁层顺序,放在 `@deepseek-ai/dsh-base`
207
+ 与 `@deepseek-ai/dsh-web-app` 之后即可);
208
+ 3. 执行 `dsh plugin --profile <profile> install` 安装依赖。
209
+
210
+ ### 版本闸门
211
+
212
+ 本包的 DSH 兼容范围是 **`>=0.2.0-rc.1`——只有下界,没有上界**,
213
+ 三处声明逐字一致(`peerDependencies`、`engines.dsh`、`dsh.compatibility.dsh`,
214
+ 由 `pnpm test:manifest` 断言):
215
+
216
+ - DSH 安装器读的是 `peerDependencies`:运行时版本低于下界 → **直接拒绝安装**,
217
+ 不会出现「装上了但思考功能失效」;
218
+ - **上界故意不写**:写死上界意味着 DSH 每升一个大版本都要重发一版插件去放宽范围,
219
+ 代价大于收益。换来的代价是失去「未来版本被挡住」这层保护——改由插件自己的
220
+ 响亮失败兜底(定义校验、id 归属判定、启动收尾核验路由是否注册,
221
+ 见[归属与删除](#归属与删除));
222
+ - `@deepseek-ai/cordis` 同理放宽为 `>=4.0.2`:它**不参与**安装闸门(判定只看
223
+ `@deepseek-ai/dsh*` 开头的包),但它是 pnpm 的 peer,写死上界同样会在它升大版本时
224
+ 把安装卡住。
225
+
226
+ > **预发布的一个细节**:node-semver 规定,带预发布标签的版本只有在范围里存在
227
+ > 「元组相同、且自身也带预发布标签」的比较符时才被放行。
228
+ > `>=0.2.0-rc.1` 因此只覆盖 `0.2.0` 这一元组的预发布(`0.2.0-rc.2` ✓);
229
+ > `0.2.1-alpha.1`、`0.3.0-beta.1` 这类**新元组的预发布**在 npm/pnpm 的默认规则下
230
+ > 不被放行(DSH 自己的闸门带 `includePrerelease`,会放行——两者判定不一致时,
231
+ > 失败的是 pnpm 那一步)。真遇到时二选一:
232
+ >
233
+ > ```bash
234
+ > # ① 给该元组补一条分支,写回范围(例如到 0.2.1 的预发布)
235
+ > # ">=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-0"
236
+ >
237
+ > # ② 或让 pnpm 放宽 peer 校验(参数照样原样转发)
238
+ > dsh plugin --profile <profile> add @zhz8888/dsh-radeon-cloud-patcher \
239
+ > --config.strict-peer-dependencies=false
240
+ > ```
241
+ >
242
+ > 稳定版(`0.2.0`、`0.2.1`、`0.3.0`、`1.0.0`…)不受这个细节影响,一律放行。
243
+
244
+ ---
245
+
246
+ ## 使用
138
247
 
139
- ### 3. 配置 API 密钥
248
+ ### 1. 配置 API 密钥
140
249
 
141
- 密钥有两种提供方式,任选其一。
250
+ 密钥从 [AMD Radeon Cloud 文档](https://amd-aim.github.io/radeon-cloud-docs/) 里的入口申请
251
+ (本项目只覆盖 Public Free Model APIs 的共享端点)。拿到 `rc-…` 之后二选一:
142
252
 
143
- **方式一:在 DSH 的模型设置页录入(推荐给日常使用)**
253
+ **方式一:设置页(推荐给日常使用)**
144
254
 
145
- 重启 DSH,在「设置 → 模型」找到 Radeon Cloud 行,填入密钥。DSH 会把它写进本机凭据存储
146
- `~/.dsh/.credentials.yaml` 的 `RADEON_CLOUD_API_KEY` 条目,明文不落配置、不回显。
147
- 此后 `scripts/` 下的脚本会自动读取该条目,无需再做任何设置。
255
+ 左下角「账号菜单 → 设置 → 模型」→ Radeon Cloud 行 →「编辑」→ 在「API 密钥」里粘贴
256
+ `rc-…` →「保存」。保存成功后该行右侧出现**绿点**(悬停显示「API 密钥已配置」;
257
+ 没配密钥时是红点,提示「API 密钥缺失」)。
258
+
259
+ 密钥写进本机凭据存储 `~/.dsh/.credentials.yaml` 的 `RADEON_CLOUD_API_KEY` 条目,
260
+ 明文不落配置文件、界面不回显;要换密钥就再次「编辑」并输入新值
261
+ (输入框会提示「已配置——输入新值可替换」)。`scripts/` 下的脚本读的是同一个条目,
262
+ 因此在设置页填过一次,脚本也就不用再配了。
263
+
264
+ **方式二:环境变量(不落盘,适合临时试用与 CI)**
265
+
266
+ ```bash
267
+ export RADEON_CLOUD_API_KEY=rc-你的密钥
268
+ ```
148
269
 
149
- **方式二:环境变量(适合未安装 DSH 或不想在本机留存凭据文件的使用者)**
270
+ 凭据服务按「**继承的进程环境** → 插件写入的凭据存储 → 当前目录 `.env` → `$DSH_HOME/.env`」
271
+ 依次查找,环境变量优先级最高,一次性的写法也有效:
150
272
 
151
273
  ```bash
152
- export RADEON_API_KEY=rc-你的密钥
274
+ RADEON_CLOUD_API_KEY=rc-你的密钥 dsh --profile <profile>
153
275
  ```
154
276
 
155
- 单次调用也可以只对这一条命令生效:
277
+ > 注意名字:**插件运行时**读的是 `RADEON_CLOUD_API_KEY`(即 provider 定义里的
278
+ > `apiKeyEnv`),而**仓库脚本**用的是 `RADEON_API_KEY`。两者互不影响,
279
+ > 脚本那一套见下面的表格。
280
+
281
+ ### 2. 选模型与思考档位
282
+
283
+ 对话输入框下方点模型选择器 → 选 Radeon Cloud 的模型 → 再选「推理等级」,
284
+ 那就是思考档位。档位按模型逐个声明(见[可用模型](#可用模型)):
285
+ GLM 不接受 `none`,因此没有关闭档位;`MiniCPM5-2B` 不是推理模型,
286
+ 不会出现档位下拉。选了服务端不接受的档位是**硬失败**(400 / 422),不会静默降级。
287
+
288
+ ### 3. 图片输入
289
+
290
+ 支持图片:DeepSeek-V4.1-Flash、DeepSeek-V4-Flash-Vision-Exp、MiMo-V2.6-Flash、
291
+ Qwen3.8-27B、Qwen3.8-Flash-Next。只收文本:DeepSeek-V4-Flash、GLM-5.3-Flash、MiniCPM5-2B。
292
+
293
+ ### 4. 升级与卸载
156
294
 
157
295
  ```bash
158
- RADEON_API_KEY=rc-你的密钥 ./scripts/radeon-api.sh GET /models
296
+ dsh plugin --profile <profile> add @zhz8888/dsh-radeon-cloud-patcher@latest # 升级
297
+ dsh plugin --profile <profile> remove @zhz8888/dsh-radeon-cloud-patcher # 卸载
159
298
  ```
160
299
 
161
- 两种方式共存时,脚本按「环境变量优先、凭据文件次之」取用密钥;
162
- 两者都没有时直接报错并提示上述做法。
300
+ 升级不需要额外动作:定义由插件层声明,装上新版本重启即生效
301
+ (在市场里升级同样要重启,原因与安装一致)。
302
+ **卸载即删除供应商**——插件层消失,provider 与设置页那一行一起消失。
303
+
304
+ > 例外:如果你曾用 `pnpm install:profile` 把定义合并进 profile(见[归属与删除](#归属与删除)),
305
+ > profile 里那份副本会**盖住**插件层的新定义——升级后要么重跑一次
306
+ > `pnpm install:profile`,要么删掉那份副本,让插件独占管理。
307
+
308
+ ### 5. 仓库脚本用的环境变量
163
309
 
164
- ### 4. 相关环境变量
310
+ 下面这些只影响仓库里的 `scripts/`(`radeon-api.sh`、`probe-efforts.mjs`),
311
+ 与插件运行时无关:
165
312
 
166
313
  | 变量 | 作用 | 默认值 |
167
314
  | --- | --- | --- |
168
- | `RADEON_API_KEY` | API 密钥字面值,优先级高于凭据文件 | 无 |
169
- | `RADEON_KEY_REF` | 从凭据文件读取时使用的条目名 | `RADEON_CLOUD_API_KEY` |
315
+ | `RADEON_API_KEY` | 脚本用的密钥字面值,优先级高于凭据文件 | 无 |
316
+ | `RADEON_KEY_REF` | 脚本从凭据文件读取时用的条目名 | `RADEON_CLOUD_API_KEY` |
170
317
  | `RADEON_BASE` | 端点基础 URL,可指向独占端点 | `https://developer.amd.com.cn/radeon/api/v1` |
171
318
  | `DSH_HOME` | DSH 数据目录,凭据文件据此定位 | `~/.dsh` |
172
319
 
@@ -175,6 +322,50 @@ RADEON_API_KEY=rc-你的密钥 ./scripts/radeon-api.sh GET /models
175
322
 
176
323
  ---
177
324
 
325
+ ## 归属与删除
326
+
327
+ 本插件要求 `radeon-cloud` 这个 id **由它独占**,因此:
328
+
329
+ - 设置页那一行**不会出现「删除」按钮**。DSH 的判据是「该 provider 路径存在于用户层、
330
+ 且不存在于 base 层」——定义写在插件的 bundle 层,base 层永远有它,按钮因此永不渲染;
331
+ - **删除供应商 = 卸载插件**。卸载后插件层消失,provider 随之消失。
332
+ 若你曾用 `pnpm install:profile` 把定义合并进 profile,profile 里会留下一份副本
333
+ (那份副本的所有权归你,卸载后设置页里也会重新出现删除按钮,可自行清理);
334
+ - **「自定义」标签**:DSH 对**任何不在内置模型目录里的 provider** 硬编码了这个标记
335
+ (`dsh-llm-pi-ai` 的 `declared: !catalog.has(provider)`),配置层面无从去掉。
336
+ 本插件随包带了一个极小的客户端补丁(`client/client.js`),用一条 CSS 规则把
337
+ **本 provider 那一行**的标签隐藏掉:
338
+
339
+ ```css
340
+ li:has(button[aria-label*="(radeon-cloud)"]) span[class*="rowTag"] { display: none !important }
341
+ ```
342
+
343
+ 代价说清楚:它依赖 DSH 的 DOM 结构与类名片段,DSH 改版后可能静默失效——
344
+ 标签重新出现,功能不受影响。
345
+
346
+ ### 定义归谁管:启动时的判定
347
+
348
+ DSH 的非 insert 补丁是**整体替换**:`llm-pi-ai` 的 config 只要在 profile 里出现过,
349
+ 就会整份盖掉插件层的定义。插件启动时静态读 profile / home / 命令行 overlay 三层,
350
+ 判定这个 id 现在由谁说了算:
351
+
352
+ | 情况 | 结果 |
353
+ | --- | --- |
354
+ | 没人碰过 `llm-pi-ai` 的 config | 插件层的定义生效,正常启动 |
355
+ | profile 里有一份**与本插件一致**的同 id 声明 | 放行,并提示它会在卸载后残留(通常是你在设置页保存过一次、被物化出的副本) |
356
+ | profile 里有一份**与之不一致**的同 id 声明 | **启动失败**:报「provider id 冲突」,并指名第一处差异字段 |
357
+ | profile 给 `llm-pi-ai` 写了 config 却没有本 provider | **启动失败**:报「定义未生效」,并打印可直接执行的合并命令 |
358
+ | 那一层的 config 是 `!!js` 表达式,或本次运行读不到 profile 目录 | 跳过判定并告警——静态判断不了,不等于失败;真正生效的是哪一份以 `dsh --profile <profile> --dump-config` 为准 |
359
+
360
+ 后两种是**失败 + 原因**,而不是静默接管或静默消失——这是本项目的取舍:id 冲突要响亮。
361
+ 处置方式(按键合并、`--force`、输出示例)见[已知限制](#6-与用户自有-provider-共存)。
362
+
363
+ > 想彻底消除标签、不留 DOM 依赖,只能让插件自己注册 provider 路由,也就意味着自己实现
364
+ > 传输、思考解析与用量计量。本项目定位是「薄壳 + 由 DSH 自带的 pi-ai 承载」,
365
+ > 因此选择了 CSS 隐藏这条路。
366
+
367
+ ---
368
+
178
369
  ## 项目结构
179
370
 
180
371
  ```
@@ -182,8 +373,8 @@ dsh-radeon-cloud-patcher/
182
373
  ├── README.md 本文件
183
374
  ├── AGENTS.md 智能体与协作者指引
184
375
  ├── LICENSE MIT 许可证
185
- ├── package.json 插件包清单(版本闸门、导出、脚本)
186
- ├── cordis.plugin.patch.yml bundle 补丁:只插入本插件行
376
+ ├── package.json 插件包清单(版本闸门、导出、客户端声明、脚本)
377
+ ├── cordis.plugin.patch.yml bundle 补丁:provider 定义 + 本插件行
187
378
  ├── pnpm-workspace.yaml 本仓库的安装期构建授权(不影响用户安装)
188
379
  ├── pnpm-lock.yaml 依赖锁文件
189
380
  ├── .gitignore
@@ -191,54 +382,59 @@ dsh-radeon-cloud-patcher/
191
382
  ├── provider/ 配置本体
192
383
  │ └── radeon-cloud.yml provider 定义的唯一真源
193
384
  │
385
+ ├── client/ 客户端补丁
386
+ │ └── client.js 隐藏本行的「自定义」标签(DSH 客户端模块格式,非 ESM)
387
+ │
194
388
  ├── src/ 插件实现(薄壳,不含 provider 逻辑)
195
- │ ├── index.js 插件入口:启动时校验定义,失效则响亮失败
196
- │ ├── merge.js 键级合并的纯函数实现
389
+ │ ├── index.js 插件入口:启动校验、所有权判定、收尾核验
390
+ │ ├── ownership.js 所有权判定的纯函数(四种归属结局 + dynamic 哨兵、差异定位)
391
+ │ ├── merge.js 键级合并的纯函数(补救路径使用)
197
392
  │ ├── patch-text.js 文本级合并,保留用户注释与格式
198
393
  │ ├── validate.js 定义的结构校验
199
394
  │ └── yaml.js 定位 YAML 解析器
200
395
  │
201
396
  ├── scripts/ 运维与校验工具
202
- │ ├── apply-to-profile.mjs 按键合并进 profile(带断言与自动备份)
397
+ │ ├── sync-bundle-patch.mjs 把定义真源渲染进补丁的生成区块
398
+ │ ├── apply-to-profile.mjs 补救路径:按键合并进 profile(冲突时拒绝覆盖)
203
399
  │ ├── validate-config.mjs 用 llm-pi-ai 真实 schema 校验定义
204
400
  │ ├── probe-efforts.mjs 实测逐模型 reasoning_effort 档位表
205
401
  │ └── radeon-api.sh API 调用助手(密钥取自环境变量或凭据存储)
206
402
  │
207
- ├── test/ 离线测试
208
- │ ├── merge.test.mjs 合并语义与幂等性
209
- │ ├── patch-text.test.mjs 不破坏用户 provider 与注释
403
+ ├── test/ 离线测试(自研 runner,无测试框架)
404
+ │ ├── *.test.mjs 清单不变式、合并语义、文本级编辑、
405
+ │ │ 所有权判定、启动看护、补丁与客户端一致性
210
406
  │ ├── verify-reasoning.mjs 思考文本取回的回归测试
211
- │ └── fixtures/
212
- │ └── stream-reasoning.sse 实测抓取的真实流
407
+ │ └── fixtures/stream-reasoning.sse 实测抓取的真实流
213
408
  │
214
409
  └── docs/ 技术文档
215
- ├── plugin-parity-assessment.md 与其他 provider 插件的使用方式差异评估
216
- ├── feasibility-assessment.md 可行性评估与线格式差异矩阵
217
- ├── implementation-plan.md 实施计划与检查点结论
218
- ├── live-verification.md 真机联调实测记录
219
- ├── reference-commandcode-provider.md 第三方 provider 插件参考实现分析
220
- └── market-submission.md 插件市场投稿说明
410
+ └── market-submission.md 插件市场投稿与 npm 发布说明
221
411
  ```
222
412
 
223
- > 插件壳只做四件事:**分发**(npm 包,可被插件市场安装)、**版本闸门**
224
- > (版本不匹配直接拒装)、**写入**(按键合并进 profile,可演练、带断言、自动备份)、
225
- > **看护**(启动时校验定义,失效则让插件启动失败)。
226
- > 实际的传输、思考字段解析、多轮回传、用量计量仍全部由 DSH 自带的
227
- > `llm-pi-ai` 承担。
413
+ > 插件壳只做四件事:**分发**(npm 包,可被插件市场安装)、**声明**(provider 定义写在
414
+ > 自带的补丁层里,profile 只读不改)、**版本闸门**(版本不匹配直接拒装)、
415
+ > **看护**(启动时校验定义、判定 id 归属,不该继续就让插件启动失败并说明原因)。
416
+ > 实际的传输、思考字段解析、多轮回传、用量计量仍全部由 DSH 自带的 `llm-pi-ai` 承担。
228
417
 
229
418
  ---
230
419
 
231
420
  ## 常用命令
232
421
 
233
422
  ```bash
234
- # 全量测试(合并语义、冲突防护、思考字段取回)
423
+ # 全量测试(合并语义、所有权判定、启动看护、补丁一致性、思考字段取回)
235
424
  pnpm test
236
425
 
426
+ # 只跑启动看护(四种归属结局 + dynamic 跳过)与补丁一致性
427
+ pnpm test:guard
428
+ pnpm test:patch
429
+
237
430
  # 校验 provider 定义:结构校验 + llm-pi-ai 真实 schema 校验
238
431
  # 这是判断「字段在当前 DSH 版本里是否仍然有效」的那道校验,建议升级 DSH 后跑一次
239
432
  pnpm validate
240
433
 
241
- # 演练合并进 profile,打印改动与被保留的其它 provider
434
+ # 改完 provider/radeon-cloud.yml 后,把定义同步进补丁的生成区块
435
+ pnpm sync:patch
436
+
437
+ # 补救路径:把定义按键合并进 profile(冲突时拒绝覆盖,--force 才覆盖)
242
438
  pnpm install:profile:dry
243
439
 
244
440
  # 重新实测某个模型支持的思考档位
@@ -266,7 +462,7 @@ pnpm probe --only Qwen3.8-27B
266
462
  | MiniCPM5-2B | 131,072 | ❌ | (不返回分离思考,故不提供档位) |
267
463
 
268
464
  `off` 表示发送 `reasoning_effort: "none"` 关闭思考。
269
- **GLM-5.3-Flash 与 Qwen3.8-27B 不支持 `none`**,因此没有关闭档位;其中 Qwen 系在「不传档位」时也会思考(AMD 服务端行为)。
465
+ **GLM-5.3-Flash 不接受 `none`**,因此没有关闭档位;Qwen 系靠 `none` 关闭,而**不传档位时仍会思考**(AMD 服务端行为)。
270
466
 
271
467
  > **档位表不在 API 里**(`GET /v1/models` 不返回该信息),只能实测。模型上下架会导致此表过期,
272
468
  > 请用 `pnpm probe` 复测后再更新 `provider/radeon-cloud.yml`。
@@ -315,31 +511,55 @@ DSH 将其归为可重试的 `SERVER`。偶发,重试即可。
315
511
 
316
512
  ### 6. 与用户自有 provider 共存
317
513
 
318
- provider 定义写入 profile 时采用**键级合并**:只新增或替换 `providers.radeon-cloud`
319
- 这一个键,同一命名空间下你自己添加的 provider 原样保留。
514
+ provider 定义由插件的 bundle 层声明,插件**只读 profile、不写 profile**。
515
+ DSH 的非 insert 补丁是整体替换,因此你自己写的 `llm-pi-ai` config 会整份盖掉插件层的定义:
516
+ 插件启动时会判定这件事,响亮失败并给出处置,而不是让 provider 无声消失
517
+ (判据见[归属与删除](#归属与删除))。
518
+
519
+ 想与自有 provider 共存,就用补救命令做按键合并:
520
+
521
+ ```bash
522
+ # 在插件仓库里
523
+ pnpm install:profile:dry # 演练:打印改动与被保留的其它 provider
524
+ pnpm install:profile # 写入(自动备份,断言其余条目与同级键逐字未变)
525
+
526
+ # 只装了插件、没有克隆仓库时,脚本在包里;启动失败信息会直接打印完整命令
527
+ node ~/.dsh/profiles/<profile>/node_modules/@zhz8888/dsh-radeon-cloud-patcher/scripts/apply-to-profile.mjs \
528
+ ~/.dsh/profiles/<profile>/cordis.patch.yml
529
+ ```
530
+
531
+ 输出示例:
532
+
533
+ ```
534
+ 【写入】 ~/.dsh/profiles/desktop/cordis.patch.yml
535
+ 新增 providers.radeon-cloud(8 个模型)
536
+ 保留的其它 provider:my-corp-proxy, another-one
537
+ 顶层条目 12 个,增减 0 个
538
+ ```
320
539
 
321
- 合并采用文本级编辑,不会重新序列化你的补丁文件,因此你写的注释、缩进风格与
322
- 键序也不会被改动。写入前会断言:除 `radeon-cloud` 外的同级 provider 键集合一致、
323
- `llm-pi-ai` 之外的顶层条目逐字符未变,任一不成立即中止且不落盘。
540
+ 合并粒度是键:只新增或替换 `providers.radeon-cloud` 这一个键,你添加的其它 provider、
541
+ 你写的注释、缩进风格与键序都原样保留(文本级编辑,不重新序列化你的文件)。
542
+ 写入前会断言:除 `radeon-cloud` 外的同级 provider 键集合一致、`llm-pi-ai` 之外的顶层条目
543
+ 逐字符未变,任一不成立即中止且不落盘;已存在不一致的同 id 定义时**拒绝覆盖**并打印第一处
544
+ 差异,确认要覆盖再加 `--force`。
324
545
 
325
- 需要注意的仍有一处:**不要另写一条 `llm-pi-ai` 的非 insert 补丁**去覆盖 config——
326
- 那会整体替换该行,绕过上面的按键合并。若要用补丁改 `llm-pi-ai` 的其它字段,
327
- 请只改你需要的那个字段,不要整块替换 `config`。
546
+ 另有一处要注意:**不要另写一条 `llm-pi-ai` 的非 insert 补丁**去改该行的其它字段——
547
+ 那会整体替换这一行,把 `providers` 一并带走。要改就只改你需要的那一个键。
328
548
 
329
549
  ---
330
550
 
331
551
  ## 兼容性
332
552
 
333
- - 目标 DSH 版本:`0.2.x`(范围由 `peerDependencies` 声明)
334
- - 模型目录与档位表采集日期:**2026-10-04**
335
- - 收录的模型当前均为 `stability: experimental`
553
+ - 目标 DSH 版本:`>=0.2.0-rc.1`,**无上界**(三处声明一致,见[版本闸门](#版本闸门))
554
+ - 收录的模型当前均为 `stability: experimental`——上游接口行为可能变,档位失效时先复测
336
555
  - 仅覆盖 Public Free Model APIs(共享端点);独占端点的基础 URL 每次实例重启都会变化,不在本项目范围内
556
+ - 客户端补丁依赖 DSH 的 DOM 结构,DSH 改版后可能静默失效(机制与代价见[归属与删除](#归属与删除))
337
557
 
338
558
  ---
339
559
 
340
560
  ## 许可证
341
561
 
342
- 本项目以 [MIT 许可证](LICENSE) 开源。
562
+ 本项目以 [MIT 许可证](https://github.com/zhz8888/dsh-radeon-cloud-patcher/blob/main/LICENSE) 开源。
343
563
 
344
564
  MIT 许可证允许你自由使用、复制、修改、合并、发布、分发、再许可及销售本项目的副本,
345
565
  唯一义务是在所有副本或实质性部分中保留版权声明与许可声明。
@@ -0,0 +1,62 @@
1
+ /**
2
+ * 客户端补丁:把「设置 → 模型」里本插件那一行的「自定义」标签藏掉。
3
+ *
4
+ * 为什么需要它:dsh-llm-pi-ai 对「不在内置模型目录中的 provider」硬编码
5
+ * `declared: !catalog.has(provider)`,而 dsh-client-ui-settings-models 只看到这个
6
+ * 标记就渲染「自定义」标签。本插件的 provider 必然落在这一支,配置层面无从去掉。
7
+ *
8
+ * 做法是纯 CSS,不碰 DSH 的任何模块、不做 DOM 写入:
9
+ *
10
+ * li:has(button[aria-label*="(radeon-cloud)"]) span[class*="rowTag"] { display: none }
11
+ *
12
+ * 判据取自 DSH 自己拼的 aria-label(`编辑 Radeon Cloud (radeon-cloud)`)——它与
13
+ * 界面语言无关,只随 provider 路由名变化;`rowTag` 是 CSS Modules 源类名,会以
14
+ * 片段形式保留在哈希类名里。
15
+ *
16
+ * 代价说清楚:这依赖 DSH 的 DOM 结构与类名片段。DSH 改版后它会静默失效——
17
+ * 标签重新出现,但功能不受影响;本行「删除」按钮则根本不由本补丁负责,
18
+ * 它由 provider 定义落在补丁层这件事本身永久消除(见 cordis.plugin.patch.yml)。
19
+ *
20
+ * 文件格式注意:本文件不是 ESM,而是 DSH 客户端模块的包装格式
21
+ * (window.__ModuleLoader__.load),因此不要把它 import 进任何 Node 代码。
22
+ * test/client-patch.test.mjs 用 node:vm 在桩环境里跑它。
23
+ */
24
+ window.__ModuleLoader__.load({
25
+ // 必须等于 package.json 的 name —— DSH 按包名给客户端模块建表,写错会「加载了却没注册」。
26
+ id: '@zhz8888/dsh-radeon-cloud-patcher',
27
+ factory: (require) => {
28
+ var module = { exports: {} }
29
+ var exports = module.exports
30
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
31
+
32
+ /** 本插件负责的 provider 路由名。 */
33
+ var PROVIDER = 'radeon-cloud'
34
+ /** 注入的样式元素 id,便于重复加载时复用而非叠加。 */
35
+ var STYLE_ID = 'dsh-radeon-cloud-hide-custom-tag'
36
+ /** 隐藏标签的规则;只匹配带本 provider 路由名的行。 */
37
+ var CSS = 'li:has(button[aria-label*="(' + PROVIDER + ')"]) span[class*="rowTag"]{display:none !important}'
38
+
39
+ /**
40
+ * 注入样式表。用 ctx.effect 登记,插件卸载时一并移除。
41
+ *
42
+ * @param ctx 客户端插件上下文
43
+ */
44
+ function apply(ctx) {
45
+ if (typeof document === 'undefined') return
46
+ ctx.effect(function () {
47
+ var existing = document.getElementById(STYLE_ID)
48
+ var style = existing === null ? document.createElement('style') : existing
49
+ style.id = STYLE_ID
50
+ style.textContent = CSS
51
+ if (existing === null) document.head.append(style)
52
+ return function () {
53
+ style.remove()
54
+ }
55
+ })
56
+ }
57
+
58
+ exports.name = 'radeon-cloud-client'
59
+ exports.apply = apply
60
+ return module.exports
61
+ },
62
+ })