@zhz8888/dsh-radeon-cloud-patcher 1.2.0 → 1.3.0

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
@@ -30,13 +30,15 @@
30
30
  命令行执行
31
31
  `dsh plugin --profile <profile> add @zhz8888/dsh-radeon-cloud-patcher`。
32
32
  装不上(pnpm 报 404)就改用[安装](#安装)里的方式三,从源码装同一个包。
33
- 2. **重启 DSH** —— 定义写在补丁层的 config 行里,而插件市场的**热挂载只支持纯 insert 行**,
34
- 因此安装后市场会提示「重启后生效」,这一步不能省。重启后 provider 就位,没有其它后置步骤。
33
+ 装好后插件列表里显示的条目名是 **Radeon Cloud**(中文界面为「Radeon Cloud 提供方」),
34
+ 而不是上面那串包名——名称取自包内的 `locale/`,见[项目结构](#项目结构)。
35
+ 2. **重启 DSH** —— 定义写在补丁层的 config 行里,插件管理器对这类变更只会给出
36
+ `restart-required`,不会在运行中生效。重启后 provider 就位,没有其它后置步骤。
35
37
  3. **填密钥** —— 左下角「账号菜单 → 设置 → 模型」,找到 **Radeon Cloud** 一行,
36
38
  点「编辑」,在「API 密钥」里粘贴密钥,点「保存」。
37
39
 
38
40
  装好之后:那一行右侧出现**绿点**(悬停显示「API 密钥已配置」);回到对话,点输入框下方的
39
- 模型选择器,Radeon 的 8 个模型会出现在列表里;选中模型后再选「推理等级」就是思考档位。
41
+ 模型选择器,Radeon 的 10 个模型会出现在列表里;选中模型后再选「推理等级」就是思考档位。
40
42
 
41
43
  **怎么确认真的装好了** —— 三个现象同时成立,说明插件与它的补丁层都到位:
42
44
 
@@ -109,7 +111,7 @@ Radeon 的响应体与流式分片都存在一个容易致错的形态:思考
109
111
 
110
112
  ### 其他
111
113
 
112
- - 8 个对话模型全部可选,思考档位按模型独立声明
114
+ - 10 个对话模型全部可选,思考档位按模型独立声明
113
115
  - 视觉模型支持图片输入
114
116
  - 限流退避遵循服务端 `Retry-After`
115
117
  - 附带离线校验、回归测试与档位实测工具
@@ -172,8 +174,9 @@ printf 'registry=https://registry.npmmirror.com\n' >> ~/.dsh/profiles/<profile>/
172
174
  dsh plugin --profile <profile> add @zhz8888/dsh-radeon-cloud-patcher@<版本>
173
175
  ```
174
176
 
175
- > 本包的 tarball 里只有 JSON / YAML / JS,**没有安装期构建脚本**,
176
- > 因此换源、离线镜像、内网私服都不会碰到 pnpm 的 `allowBuilds` 授权那一步。
177
+ > 本包的 tarball 里只有插件自己的源码、定义与脚本(不含 `test/` 与 `docs/`),
178
+ > **没有安装期构建脚本**,因此换源、离线镜像、内网私服都不会碰到 pnpm 的
179
+ > `allowBuilds` 授权那一步。
177
180
 
178
181
  ### 方式三:从源码 / GitHub 安装
179
182
 
@@ -281,13 +284,15 @@ RADEON_CLOUD_API_KEY=rc-你的密钥 dsh --profile <profile>
281
284
 
282
285
  对话输入框下方点模型选择器 → 选 Radeon Cloud 的模型 → 再选「推理等级」,
283
286
  那就是思考档位。档位按模型逐个声明(见[可用模型](#可用模型)):
284
- GLM 与 Qwen 系不接受 `none`,因此没有关闭档位;`MiniCPM5-2B` 不是推理模型,
285
- 不会出现档位下拉。选了服务端不接受的档位是**硬失败**(400 / 422),不会静默降级。
287
+ GLM-5.3-Flash 与 MiniCPM-V-4.7-1B 不接受 `none`,因此没有关闭档位;
288
+ `MiniCPM5-2B` 不是推理模型,不会出现档位下拉。
289
+ 选了服务端不接受的档位是**硬失败**(400 / 422),不会静默降级。
286
290
 
287
291
  ### 3. 图片输入
288
292
 
289
293
  支持图片:DeepSeek-V4.1-Flash、DeepSeek-V4-Flash-Vision-Exp、MiMo-V2.6-Flash、
290
- Qwen3.8-27B、Qwen3.8-Flash-Next。只收文本:DeepSeek-V4-Flash、GLM-5.3-Flash、MiniCPM5-2B。
294
+ MiniCPM-V-4.7-1B、MiniCPM-V-4.7-35B-A3B、Qwen3.8-27B、Qwen3.8-Flash-Next。
295
+ 只收文本:DeepSeek-V4-Flash、GLM-5.3-Flash、MiniCPM5-2B。
291
296
 
292
297
  ### 4. 升级与卸载
293
298
 
@@ -297,7 +302,7 @@ dsh plugin --profile <profile> remove @zhz8888/dsh-radeon-cloud-patcher #
297
302
  ```
298
303
 
299
304
  升级不需要额外动作:定义由插件层声明,装上新版本重启即生效
300
- (在市场里升级同样会提示「重启后生效」,原因与安装一致)。
305
+ (在市场里升级同样要重启,原因与安装一致)。
301
306
  **卸载即删除供应商**——插件层消失,provider 与设置页那一行一起消失。
302
307
 
303
308
  > 例外:如果你曾用 `pnpm install:profile` 把定义合并进 profile(见[归属与删除](#归属与删除)),
@@ -336,7 +341,7 @@ dsh plugin --profile <profile> remove @zhz8888/dsh-radeon-cloud-patcher #
336
341
  **本 provider 那一行**的标签隐藏掉:
337
342
 
338
343
  ```css
339
- li:has(button[aria-label*="(radeon-cloud)"]) span[class*="rowTag"] { display: none }
344
+ li:has(button[aria-label*="(radeon-cloud)"]) span[class*="rowTag"] { display: none !important }
340
345
  ```
341
346
 
342
347
  代价说清楚:它依赖 DSH 的 DOM 结构与类名片段,DSH 改版后可能静默失效——
@@ -354,6 +359,7 @@ DSH 的非 insert 补丁是**整体替换**:`llm-pi-ai` 的 config 只要在 p
354
359
  | profile 里有一份**与本插件一致**的同 id 声明 | 放行,并提示它会在卸载后残留(通常是你在设置页保存过一次、被物化出的副本) |
355
360
  | profile 里有一份**与之不一致**的同 id 声明 | **启动失败**:报「provider id 冲突」,并指名第一处差异字段 |
356
361
  | profile 给 `llm-pi-ai` 写了 config 却没有本 provider | **启动失败**:报「定义未生效」,并打印可直接执行的合并命令 |
362
+ | 那一层的 config 是 `!!js` 表达式,或本次运行读不到 profile 目录 | 跳过判定并告警——静态判断不了,不等于失败;真正生效的是哪一份以 `dsh --profile <profile> --dump-config` 为准 |
357
363
 
358
364
  后两种是**失败 + 原因**,而不是静默接管或静默消失——这是本项目的取舍:id 冲突要响亮。
359
365
  处置方式(按键合并、`--force`、输出示例)见[已知限制](#6-与用户自有-provider-共存)。
@@ -383,9 +389,13 @@ dsh-radeon-cloud-patcher/
383
389
  ├── client/ 客户端补丁
384
390
  │ └── client.js 隐藏本行的「自定义」标签(DSH 客户端模块格式,非 ESM)
385
391
  │
392
+ ├── locale/ 插件页显示名称(DSH 按包内 locale 读取,缺了就回落成 npm 包名)
393
+ │ ├── en.json 英文:{"meta":{"title":"Radeon Cloud",…}}
394
+ │ └── zh.json 中文:{"meta":{"title":"Radeon Cloud 提供方",…}}
395
+ │
386
396
  ├── src/ 插件实现(薄壳,不含 provider 逻辑)
387
397
  │ ├── index.js 插件入口:启动校验、所有权判定、收尾核验
388
- │ ├── ownership.js 所有权判定的纯函数(四种结局与差异定位)
398
+ │ ├── ownership.js 所有权判定的纯函数(四种归属结局 + dynamic 哨兵、差异定位)
389
399
  │ ├── merge.js 键级合并的纯函数(补救路径使用)
390
400
  │ ├── patch-text.js 文本级合并,保留用户注释与格式
391
401
  │ ├── validate.js 定义的结构校验
@@ -398,24 +408,15 @@ dsh-radeon-cloud-patcher/
398
408
  │ ├── probe-efforts.mjs 实测逐模型 reasoning_effort 档位表
399
409
  │ └── radeon-api.sh API 调用助手(密钥取自环境变量或凭据存储)
400
410
  │
401
- ├── test/ 离线测试
402
- │ ├── merge.test.mjs 合并语义与幂等性
403
- │ ├── patch-text.test.mjs 不破坏用户 provider 与注释
404
- │ ├── ownership.test.mjs 四种所有权结局与差异定位
405
- │ ├── apply-guard.test.mjs 启动看护:放行、或失败并说明原因
406
- │ ├── patch-sync.test.mjs 补丁与定义真源一致、结构符合「无删除按钮」
407
- │ ├── client-patch.test.mjs 客户端补丁注入的规则
411
+ ├── test/ 离线测试(自研 runner,无测试框架)
412
+ │ ├── *.test.mjs 清单不变式、插件页元信息、合并语义、
413
+ │ │ 文本级编辑、所有权判定、启动看护、
414
+ │ │ 补丁与客户端一致性
408
415
  │ ├── verify-reasoning.mjs 思考文本取回的回归测试
409
- │ └── fixtures/
410
- │ └── stream-reasoning.sse 实测抓取的真实流
416
+ │ └── fixtures/stream-reasoning.sse 实测抓取的真实流
411
417
  │
412
418
  └── docs/ 技术文档
413
- ├── plugin-parity-assessment.md 与其他 provider 插件的使用方式差异评估
414
- ├── feasibility-assessment.md 可行性评估与线格式差异矩阵
415
- ├── implementation-plan.md 实施计划与检查点结论
416
- ├── live-verification.md 真机联调实测记录
417
- ├── reference-commandcode-provider.md 第三方 provider 插件参考实现分析
418
- └── market-submission.md 插件市场投稿说明
419
+ └── market-submission.md 插件市场投稿与 npm 发布说明
419
420
  ```
420
421
 
421
422
  > 插件壳只做四件事:**分发**(npm 包,可被插件市场安装)、**声明**(provider 定义写在
@@ -428,13 +429,16 @@ dsh-radeon-cloud-patcher/
428
429
  ## 常用命令
429
430
 
430
431
  ```bash
431
- # 全量测试(合并语义、所有权判定、启动看护、补丁一致性、思考字段取回)
432
+ # 全量测试(清单不变式、插件页元信息、合并语义、所有权判定、启动看护、补丁一致性、思考字段取回)
432
433
  pnpm test
433
434
 
434
- # 只跑启动看护(四种所有权结局)与补丁一致性
435
+ # 只跑启动看护(四种归属结局 + dynamic 跳过)与补丁一致性
435
436
  pnpm test:guard
436
437
  pnpm test:patch
437
438
 
439
+ # 只跑插件页显示名称的契约(locale 文件 + exports 模式 + files 白名单)
440
+ pnpm test:meta
441
+
438
442
  # 校验 provider 定义:结构校验 + llm-pi-ai 真实 schema 校验
439
443
  # 这是判断「字段在当前 DSH 版本里是否仍然有效」的那道校验,建议升级 DSH 后跑一次
440
444
  pnpm validate
@@ -456,7 +460,7 @@ pnpm probe --only Qwen3.8-27B
456
460
 
457
461
  ## 可用模型
458
462
 
459
- > 采集日期:**2026-10-04**
463
+ > 采集日期:**2026-10-16**
460
464
 
461
465
  | 模型 | 上下文 | 图片 | 思考档位 |
462
466
  | --- | --- | --- | --- |
@@ -464,13 +468,16 @@ pnpm probe --only Qwen3.8-27B
464
468
  | DeepSeek-V4-Flash | 1,048,576 | ❌ | off/minimal/low/medium/high/xhigh/max |
465
469
  | DeepSeek-V4-Flash-Vision-Exp | 1,048,576 | ✅ | off/minimal/low/medium/high/xhigh/max |
466
470
  | MiMo-V2.6-Flash | 1,048,576 | ✅ | off/minimal/low/medium/high/xhigh/max |
471
+ | MiniCPM-V-4.7-35B-A3B | 262,144 | ✅ | off/minimal/low/medium/high/xhigh/max |
467
472
  | Qwen3.8-27B | 262,144 | ✅ | off/low/medium/xhigh |
468
473
  | Qwen3.8-Flash-Next | 262,144 | ✅ | off/low/medium/xhigh |
469
474
  | GLM-5.3-Flash | 262,144 | ❌ | low/medium/high |
475
+ | MiniCPM-V-4.7-1B | 262,144 | ✅ | low/medium/high |
470
476
  | MiniCPM5-2B | 131,072 | ❌ | (不返回分离思考,故不提供档位) |
471
477
 
472
478
  `off` 表示发送 `reasoning_effort: "none"` 关闭思考。
473
- **GLM-5.3-Flash 与 Qwen3.8-27B 不支持 `none`**,因此没有关闭档位;其中 Qwen 系在「不传档位」时也会思考(AMD 服务端行为)。
479
+ **GLM-5.3-Flash 与 MiniCPM-V-4.7-1B 不接受 `none`**,因此没有关闭档位;Qwen 系靠 `none` 关闭,而**不传档位时仍会思考**(AMD 服务端行为)。
480
+ `MiniCPM-V-4.7-35B-A3B` 与 `MiniCPM-V-4.7-1B` 同属 MiniCPM 系,但档位表并不相同——**不要按模型名的相近程度推断档位**。
474
481
 
475
482
  > **档位表不在 API 里**(`GET /v1/models` 不返回该信息),只能实测。模型上下架会导致此表过期,
476
483
  > 请用 `pnpm probe` 复测后再更新 `provider/radeon-cloud.yml`。
@@ -504,8 +511,10 @@ DSH 的 `dsh-llm-pi-ai` 会对此抛 `UNSUPPORTED_OPTION`,属于响亮失败
504
511
 
505
512
  ### 3. 部分模型不接受 `developer` 角色
506
513
 
507
- DSH 默认对 OpenAI 兼容端点使用 `developer` 承载系统提示词,8 个模型中有 5 个会直接报
508
- `unknown role: developer`。已通过 `compat.supportsDeveloperRole: false` 关闭。
514
+ DSH 默认对 OpenAI 兼容端点使用 `developer` 承载系统提示词,10 个模型中有 6 个会拒绝,
515
+ 且报错文本分两种:GLM-5.3-Flash、MiniCPM5-2B、MiniCPM-V-4.7-1B 报 `422 unknown role: developer`;
516
+ Qwen 系两个与 MiniCPM-V-4.7-35B-A3B 报 `400 Unexpected message role.`。
517
+ 已通过 `compat.supportsDeveloperRole: false` 关闭,改用 `system` 角色。
509
518
 
510
519
  ### 4. 档位被拒是硬失败
511
520
 
@@ -540,7 +549,7 @@ node ~/.dsh/profiles/<profile>/node_modules/@zhz8888/dsh-radeon-cloud-patcher/sc
540
549
 
541
550
  ```
542
551
  【写入】 ~/.dsh/profiles/desktop/cordis.patch.yml
543
- 新增 providers.radeon-cloud(8 个模型)
552
+ 新增 providers.radeon-cloud(10 个模型)
544
553
  保留的其它 provider:my-corp-proxy, another-one
545
554
  顶层条目 12 个,增减 0 个
546
555
  ```
@@ -559,17 +568,15 @@ node ~/.dsh/profiles/<profile>/node_modules/@zhz8888/dsh-radeon-cloud-patcher/sc
559
568
  ## 兼容性
560
569
 
561
570
  - 目标 DSH 版本:`>=0.2.0-rc.1`,**无上界**(三处声明一致,见[版本闸门](#版本闸门))
562
- - 模型目录与档位表采集日期:**2026-10-04**
563
- - 收录的模型当前均为 `stability: experimental`
571
+ - 收录的模型当前均为 `stability: experimental`——上游接口行为可能变,档位失效时先复测
564
572
  - 仅覆盖 Public Free Model APIs(共享端点);独占端点的基础 URL 每次实例重启都会变化,不在本项目范围内
565
- - 客户端补丁依赖 DSH 模型设置页的 DOM 结构(`aria-label` 中的 provider 路由名 + `rowTag` 类名片段)。
566
- DSH 改版后它会静默失效——「自定义」标签重新出现,功能不受影响
573
+ - 客户端补丁依赖 DSH 的 DOM 结构,DSH 改版后可能静默失效(机制与代价见[归属与删除](#归属与删除))
567
574
 
568
575
  ---
569
576
 
570
577
  ## 许可证
571
578
 
572
- 本项目以 [MIT 许可证](LICENSE) 开源。
579
+ 本项目以 [MIT 许可证](https://github.com/zhz8888/dsh-radeon-cloud-patcher/blob/main/LICENSE) 开源。
573
580
 
574
581
  MIT 许可证允许你自由使用、复制、修改、合并、发布、分发、再许可及销售本项目的副本,
575
582
  唯一义务是在所有副本或实质性部分中保留版权声明与许可声明。
@@ -49,7 +49,7 @@
49
49
  baseURL: https://developer.amd.com.cn/radeon/api/v1
50
50
  displayName: Radeon Cloud
51
51
 
52
- # 默认思考档位。medium 在全部 8 个已收录模型上均被接受。
52
+ # 默认思考档位。medium 在全部 10 个已收录模型上均被接受。
53
53
  reasoning: medium
54
54
 
55
55
  # Radeon 的思考与答案共用同一个 max_tokens 预算,预算不足时 content
@@ -76,7 +76,9 @@
76
76
  # ── 协议兼容开关 ──────────────────────────────────────────────
77
77
  compat:
78
78
  # ★ 关键:DSH 默认对普通 OpenAI 兼容端点发送 developer 角色,
79
- # 而 8 个模型中有 5 个直接拒绝(unknown role: developer)。
79
+ # 而 10 个模型中有 6 个直接拒绝——GLM-5.3-Flash、MiniCPM5-2B、
80
+ # MiniCPM-V-4.7-1B 报 422 unknown role: developer,Qwen 系两个与
81
+ # MiniCPM-V-4.7-35B-A3B 报 400 Unexpected message role。
80
82
  supportsDeveloperRole: false
81
83
  supportsReasoningEffort: true
82
84
  # Radeon 的网关只按白名单转发字段,thinking_budget /
@@ -87,11 +89,14 @@
87
89
  thinkingFormat: openai
88
90
 
89
91
  # ── 模型目录 ──────────────────────────────────────────────────
90
- # 共收录 8 个对话模型。目录接口里还有 MinerU2.5-Pro,但它的
92
+ # 共收录 10 个对话模型。目录接口里还有 MinerU2.5-Pro,但它的
91
93
  # context_length 为 0、tools 为 false,走的是独立的 /v1/ocr
92
94
  # 文档识别端点而非对话接口,故不收录。
95
+ #
96
+ # 下面的分组按「档位表的形状」而非厂商划分——同一个厂商的模型档位
97
+ # 未必相同(MiniCPM 系即是如此),按厂商分组会把读者引向错误预期。
93
98
  models:
94
- # ── DeepSeek 系:7 档全接受,`none` 可关闭思考 ──
99
+ # ── 全档接受:none 可关闭思考 ──
95
100
  - id: DeepSeek-V4.1-Flash
96
101
  name: DeepSeek-V4.1-Flash
97
102
  contextWindow: 1048576
@@ -144,6 +149,21 @@
144
149
  xhigh: xhigh
145
150
  max: max
146
151
 
152
+ # 同一个 MiniCPM 系里两个模型档位形状并不相同:35B-A3B 全档接受,
153
+ # 1B 只接受 low/medium/high。实测确认两者都返回分离的思考内容。
154
+ - id: MiniCPM-V-4.7-35B-A3B
155
+ name: MiniCPM-V-4.7-35B-A3B
156
+ contextWindow: 262144
157
+ input: [text, image]
158
+ reasoningEfforts:
159
+ off: none
160
+ minimal: minimal
161
+ low: low
162
+ medium: medium
163
+ high: high
164
+ xhigh: xhigh
165
+ max: max
166
+
147
167
  # ── Qwen 系:无 minimal/max/high,仅 none/low/medium/xhigh ──
148
168
  # 这两个模型唯一能关闭思考的方式是 none;传入 high 会返回 400。
149
169
  - id: Qwen3.8-27B
@@ -166,8 +186,8 @@
166
186
  medium: medium
167
187
  xhigh: xhigh
168
188
 
169
- # ── GLM 系:不接受 none/minimal/xhigh/max ──
170
- # 不声明 off 键:该模型没有「关闭思考」的档位。
189
+ # ── 仅 low/medium/high:不接受 none/minimal/xhigh/max ──
190
+ # 不声明 off 键:这两个模型没有「关闭思考」的档位。
171
191
  - id: GLM-5.3-Flash
172
192
  name: GLM-5.3-Flash
173
193
  contextWindow: 262144
@@ -177,9 +197,18 @@
177
197
  medium: medium
178
198
  high: high
179
199
 
200
+ - id: MiniCPM-V-4.7-1B
201
+ name: MiniCPM-V-4.7-1B
202
+ contextWindow: 262144
203
+ input: [text, image]
204
+ reasoningEfforts:
205
+ low: low
206
+ medium: medium
207
+ high: high
208
+
180
209
  # MiniCPM5-2B 接受 low/medium/high,但不返回分离的思考内容
181
- # (思考字段恒为空)。给出档位控件会误导用户,
182
- # 故显式声明为非推理模型。
210
+ # (实测 reasoning 恒为空、reasoning_tokens 为 0)。
211
+ # 给出档位控件会误导用户,故显式声明为非推理模型。
183
212
  - id: MiniCPM5-2B
184
213
  name: MiniCPM5-2B
185
214
  contextWindow: 131072
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Radeon Cloud",
4
+ "description": "Unofficial Radeon Cloud provider: full reasoning-effort support, declared in the plugin's own patch layer with no profile writes."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Radeon Cloud 提供方",
4
+ "description": "非官方 Radeon Cloud 提供方:完整支持模型思考档位;provider 定义由插件自带的补丁层声明,不写用户 profile。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@zhz8888/dsh-radeon-cloud-patcher",
3
- "version": "1.2.0",
4
- "description": "为 DeepSeek Harness 接入 AMD Radeon Cloud,完整支持模型思考(reasoning)功能。把 provider 定义按键合并进 profile,由 DSH 自带的 dsh-llm-pi-ai 承载,不修改 DSH 本体,也不自研适配器。",
3
+ "version": "1.3.0",
4
+ "description": "为 DeepSeek Harness 接入 AMD Radeon Cloud,完整支持模型思考(reasoning)功能。provider 定义由插件自带的补丁层声明,交由 DSH 自带的 dsh-llm-pi-ai 承载,不修改 DSH 本体、不写用户 profile,也不自研适配器。",
5
5
  "keywords": [
6
6
  "amd",
7
7
  "deepseek-harness",
@@ -34,6 +34,7 @@
34
34
  "./merge": "./src/merge.js",
35
35
  "./provider": "./provider/radeon-cloud.yml",
36
36
  "./client": "./client/client.js",
37
+ "./locale/*.json": "./locale/*.json",
37
38
  "./package.json": "./package.json"
38
39
  },
39
40
  "dsh": {
@@ -59,14 +60,16 @@
59
60
  "src",
60
61
  "client",
61
62
  "provider",
63
+ "locale",
62
64
  "scripts",
63
65
  "cordis.plugin.patch.yml",
64
66
  "LICENSE",
65
67
  "README.md"
66
68
  ],
67
69
  "scripts": {
68
- "test": "node test/manifest.test.mjs && node test/merge.test.mjs && node test/patch-text.test.mjs && node test/ownership.test.mjs && node test/apply-guard.test.mjs && node test/patch-sync.test.mjs && node test/client-patch.test.mjs && node test/verify-reasoning.mjs",
70
+ "test": "node test/manifest.test.mjs && node test/plugin-meta.test.mjs && node test/merge.test.mjs && node test/patch-text.test.mjs && node test/ownership.test.mjs && node test/apply-guard.test.mjs && node test/patch-sync.test.mjs && node test/client-patch.test.mjs && node test/verify-reasoning.mjs",
69
71
  "test:manifest": "node test/manifest.test.mjs",
72
+ "test:meta": "node test/plugin-meta.test.mjs",
70
73
  "test:merge": "node test/merge.test.mjs && node test/patch-text.test.mjs && node test/ownership.test.mjs",
71
74
  "test:guard": "node test/apply-guard.test.mjs",
72
75
  "test:patch": "node test/patch-sync.test.mjs && node test/client-patch.test.mjs",
@@ -17,7 +17,7 @@ api: openai-completions
17
17
  baseURL: https://developer.amd.com.cn/radeon/api/v1
18
18
  displayName: Radeon Cloud
19
19
 
20
- # 默认思考档位。medium 在全部 8 个已收录模型上均被接受。
20
+ # 默认思考档位。medium 在全部 10 个已收录模型上均被接受。
21
21
  reasoning: medium
22
22
 
23
23
  # Radeon 的思考与答案共用同一个 max_tokens 预算,预算不足时 content
@@ -44,7 +44,9 @@ retryPolicy:
44
44
  # ── 协议兼容开关 ──────────────────────────────────────────────
45
45
  compat:
46
46
  # ★ 关键:DSH 默认对普通 OpenAI 兼容端点发送 developer 角色,
47
- # 而 8 个模型中有 5 个直接拒绝(unknown role: developer)。
47
+ # 而 10 个模型中有 6 个直接拒绝——GLM-5.3-Flash、MiniCPM5-2B、
48
+ # MiniCPM-V-4.7-1B 报 422 unknown role: developer,Qwen 系两个与
49
+ # MiniCPM-V-4.7-35B-A3B 报 400 Unexpected message role。
48
50
  supportsDeveloperRole: false
49
51
  supportsReasoningEffort: true
50
52
  # Radeon 的网关只按白名单转发字段,thinking_budget /
@@ -55,11 +57,14 @@ compat:
55
57
  thinkingFormat: openai
56
58
 
57
59
  # ── 模型目录 ──────────────────────────────────────────────────
58
- # 共收录 8 个对话模型。目录接口里还有 MinerU2.5-Pro,但它的
60
+ # 共收录 10 个对话模型。目录接口里还有 MinerU2.5-Pro,但它的
59
61
  # context_length 为 0、tools 为 false,走的是独立的 /v1/ocr
60
62
  # 文档识别端点而非对话接口,故不收录。
63
+ #
64
+ # 下面的分组按「档位表的形状」而非厂商划分——同一个厂商的模型档位
65
+ # 未必相同(MiniCPM 系即是如此),按厂商分组会把读者引向错误预期。
61
66
  models:
62
- # ── DeepSeek 系:7 档全接受,`none` 可关闭思考 ──
67
+ # ── 全档接受:none 可关闭思考 ──
63
68
  - id: DeepSeek-V4.1-Flash
64
69
  name: DeepSeek-V4.1-Flash
65
70
  contextWindow: 1048576
@@ -112,6 +117,21 @@ models:
112
117
  xhigh: xhigh
113
118
  max: max
114
119
 
120
+ # 同一个 MiniCPM 系里两个模型档位形状并不相同:35B-A3B 全档接受,
121
+ # 1B 只接受 low/medium/high。实测确认两者都返回分离的思考内容。
122
+ - id: MiniCPM-V-4.7-35B-A3B
123
+ name: MiniCPM-V-4.7-35B-A3B
124
+ contextWindow: 262144
125
+ input: [text, image]
126
+ reasoningEfforts:
127
+ off: none
128
+ minimal: minimal
129
+ low: low
130
+ medium: medium
131
+ high: high
132
+ xhigh: xhigh
133
+ max: max
134
+
115
135
  # ── Qwen 系:无 minimal/max/high,仅 none/low/medium/xhigh ──
116
136
  # 这两个模型唯一能关闭思考的方式是 none;传入 high 会返回 400。
117
137
  - id: Qwen3.8-27B
@@ -134,8 +154,8 @@ models:
134
154
  medium: medium
135
155
  xhigh: xhigh
136
156
 
137
- # ── GLM 系:不接受 none/minimal/xhigh/max ──
138
- # 不声明 off 键:该模型没有「关闭思考」的档位。
157
+ # ── 仅 low/medium/high:不接受 none/minimal/xhigh/max ──
158
+ # 不声明 off 键:这两个模型没有「关闭思考」的档位。
139
159
  - id: GLM-5.3-Flash
140
160
  name: GLM-5.3-Flash
141
161
  contextWindow: 262144
@@ -145,9 +165,18 @@ models:
145
165
  medium: medium
146
166
  high: high
147
167
 
168
+ - id: MiniCPM-V-4.7-1B
169
+ name: MiniCPM-V-4.7-1B
170
+ contextWindow: 262144
171
+ input: [text, image]
172
+ reasoningEfforts:
173
+ low: low
174
+ medium: medium
175
+ high: high
176
+
148
177
  # MiniCPM5-2B 接受 low/medium/high,但不返回分离的思考内容
149
- # (思考字段恒为空)。给出档位控件会误导用户,
150
- # 故显式声明为非推理模型。
178
+ # (实测 reasoning 恒为空、reasoning_tokens 为 0)。
179
+ # 给出档位控件会误导用户,故显式声明为非推理模型。
151
180
  - id: MiniCPM5-2B
152
181
  name: MiniCPM5-2B
153
182
  contextWindow: 131072
@@ -9,11 +9,14 @@
9
9
  *
10
10
  * 探测手法:对每个「模型 × 档位」组合发一次 max_tokens 为 1 的极小请求,
11
11
  * 只看 HTTP 状态码,200 即视为接受,不关心回复内容;这样单次探测的产出
12
- * token 成本可忽略。按每分钟 20 次的账户限流节流到约 19 次/分钟。
12
+ * token 成本可忽略。
13
13
  *
14
- * 注意:429 表示触发了限流,不代表该档位被拒绝,遇到时应单独复测确认。
14
+ * 只有 200 才是「接受」;400 与 422 才是「拒绝」。429(限流)与 5xx
15
+ * (上游偶发故障,README 记录过 504 被包装成 500)都不构成结论,本脚本会
16
+ * 自动重测,避免把「测不出来」写成档位表里的一个 ✗——那会让用户在选择器里
17
+ * 少一个本来可用的档位,且全程不产生任何报错。
15
18
  *
16
- * 用法: node scripts/probe-efforts.mjs [--only ModelId] [--tiers a,b,c]
19
+ * 用法: node scripts/probe-efforts.mjs [--only ModelId] [--tiers a,b,c] [--interval 毫秒]
17
20
  */
18
21
  import { execFile } from 'node:child_process'
19
22
  import { promisify } from 'node:util'
@@ -48,8 +51,22 @@ const only = argOf('--only')
48
51
  /** 只探测这些档位,逗号分隔;未指定时用 ALL_TIERS。 */
49
52
  const tiers = (argOf('--tiers') ?? ALL_TIERS.join(',')).split(',')
50
53
 
51
- /** 相邻两次请求之间的最小间隔,取 3100 毫秒约为每分钟 19 次。 */
52
- const MIN_INTERVAL_MS = 3100
54
+ /** 相邻两次请求之间的默认最小间隔。
55
+ *
56
+ * 网关的账户限流是每分钟 20 次(响应头 x-ratelimit-limit-user-rpm)。原先取
57
+ * 3100 毫秒看似合规(≈19.4 次/分),但节流判据是「距上次请求满 3.1 秒」,
58
+ * 第 21 次请求会落在与第 1 次同一个滚动窗口内,实测必踩 429。取 4500 毫秒
59
+ * 把网关这一层躲开;上游自身仍会偶发限流,那由下面的重测兜住。 */
60
+ const DEFAULT_INTERVAL_MS = 4500
61
+ /** 相邻两次请求之间的最小间隔,可用 --interval 覆盖。 */
62
+ const MIN_INTERVAL_MS = Number(argOf('--interval') ?? DEFAULT_INTERVAL_MS)
63
+ /** 命中 429 后的冷却时长:服务端 Retry-After 为 60 秒,多留几秒余量。 */
64
+ const RATE_LIMIT_COOLDOWN_MS = 65000
65
+ /** 命中 5xx 后的重试间隔。上游偶发故障,等几秒再问通常就好了。 */
66
+ const UPSTREAM_RETRY_DELAY_MS = 5000
67
+ /** 单个「模型 × 档位」组合命中可重试错误后最多重测的次数。 */
68
+ const MAX_RETRIES = 2
69
+
53
70
  /** 上一次请求发出的时间戳,用于计算需要等待多久。 */
54
71
  let lastAt = 0
55
72
 
@@ -73,6 +90,30 @@ async function throttle() {
73
90
  * @returns {Promise<{status: number, error?: string}>} HTTP 状态码;取不到时为 -1
74
91
  */
75
92
  async function call(model, effort) {
93
+ for (let attempt = 0; ; attempt += 1) {
94
+ const res = await once(model, effort)
95
+ // 429 是限流,5xx 是上游故障,两者都测不出该档位的真实结论。直接返回会把
96
+ // 「没测到」写成「不接受」,进而从档位表里删掉一个本来可用的档位,所以必须重测。
97
+ const delay = res.status === 429
98
+ ? RATE_LIMIT_COOLDOWN_MS
99
+ : res.status >= 500 ? UPSTREAM_RETRY_DELAY_MS : 0
100
+ if (delay === 0 || attempt >= MAX_RETRIES) return res
101
+ process.stderr.write(
102
+ ` ${model.padEnd(30)} ${effort.padEnd(9)} ${res.status} 非结论,`
103
+ + `${delay / 1000} 秒后重测(第 ${attempt + 1} 次)\n`,
104
+ )
105
+ await new Promise((r) => setTimeout(r, delay))
106
+ }
107
+ }
108
+
109
+ /**
110
+ * 发起一次探测请求,不做限流重试。
111
+ *
112
+ * @param {string} model 模型 id
113
+ * @param {string} effort reasoning_effort 取值
114
+ * @returns {Promise<{status: number, error?: string}>} HTTP 状态码;取不到时为 -1
115
+ */
116
+ async function once(model, effort) {
76
117
  await throttle()
77
118
  // max_tokens 压到 1,让这次请求只为确认档位是否被接受,不产生实际产出。
78
119
  const body = JSON.stringify({
@@ -118,7 +159,8 @@ for (const model of models) {
118
159
  for (const effort of tiers) {
119
160
  const { status } = await call(model, effort)
120
161
  result[model][effort] = status
121
- // 200 为接受;400 与 422 为档位不被接受;其余(429 等)需人工复测。
162
+ // 200 为接受;400 与 422 为档位不被接受;冷却重测后仍非这三者的
163
+ // (例如持续 429、5xx)说明这次没测出结论,须人工复测后手工入表。
122
164
  const mark = status === 200 ? '✓' : status === 400 ? '✗400' : status === 422 ? '✗422' : `?${status}`
123
165
  process.stderr.write(` ${model.padEnd(30)} ${effort.padEnd(9)} ${mark}\n`)
124
166
  }
package/src/ownership.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * home 的 ~/.dsh/cordis.patch.yml、命令行 overlay 都排在 bundle 层之后,
7
7
  * 因此其中任何一条针对 llm-pi-ai 的 config 都会把插件层的定义整份盖掉。
8
8
  *
9
- * 三种结局必须被区分开,否则用户只会看到 provider 无声消失:
9
+ * 四种归属结局必须被区分开,否则用户只会看到 provider 无声消失:
10
10
  *
11
11
  * plugin-layer 之后各层没碰 llm-pi-ai → 插件层的定义生效,一切正常;
12
12
  * materialized 之后的层声明了同 id 定义,且与本插件定义一致 —— 这是设置页
@@ -16,12 +16,15 @@
16
16
  * conflict 之后的层声明了同 id 定义但与本插件不一致 —— 用户自己维护了
17
17
  * 一份同名 provider,插件拒绝接管(这是需求里的「id 冲突」)。
18
18
  *
19
+ * 另有哨兵 dynamic:config 是 !!js 表达式、或本次运行拿不到 profile 目录时,
20
+ * 静态判断不了谁生效,此时只告警并照常加载——「判不了」不等于「失败」。
21
+ *
19
22
  * 与 merge.js 的关系:merge.js 负责「把定义按键合并进 profile」这条补救路径,
20
23
  * 本模块负责「启动时判断谁在管理这个 id」。两者共用同一个 PROVIDER_KEY。
21
24
  */
22
25
  import { PROVIDER_KEY, TARGET_ENTRY_ID, deepEqual } from './merge.js'
23
26
 
24
- /** 所有权判定的四种结局。 */
27
+ /** 所有权判定的四种归属结局,外加「判不了就跳过」的哨兵 dynamic。 */
25
28
  export const OWNERSHIP = {
26
29
  pluginLayer: 'plugin-layer',
27
30
  materialized: 'materialized',