dsh-router-core 0.4.7 → 0.4.9

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.
Files changed (62) hide show
  1. package/README.md +14 -70
  2. package/cordis.patch.yml +2 -7
  3. package/docs/ext.md +3 -17
  4. package/docs/suppliers.md +1 -1
  5. package/lib/client-registry.js +137 -590
  6. package/lib/client.js +137 -590
  7. package/lib/ext-prompt/index.js +20 -15
  8. package/lib/index.js +0 -419
  9. package/lib/types/ext/contract.d.ts +1 -1
  10. package/lib/types/ext-prompt/content.d.ts +11 -0
  11. package/lib/types/ext-prompt/index.d.ts +7 -9
  12. package/lib/types/ext-prompt/mount.d.ts +2 -2
  13. package/lib/types/ext-prompt/plugin.d.ts +3 -5
  14. package/lib/types/router/types.d.ts +11 -0
  15. package/lib/types/shared.d.ts +2 -0
  16. package/lib/types/suppliers/registry.d.ts +0 -1
  17. package/package.json +1 -10
  18. package/src/client/CombosTab.tsx +29 -8
  19. package/src/client/ExtDetail.tsx +1 -13
  20. package/src/client/SupplierDetail.tsx +11 -5
  21. package/src/client/group-controls.test.ts +1 -1
  22. package/src/client/group-controls.ts +1 -1
  23. package/src/client/index.tsx +0 -17
  24. package/src/client/model-label.test.ts +33 -0
  25. package/src/client/model-label.ts +48 -0
  26. package/src/client/router-components.test.ts +1 -1
  27. package/src/client/router.css +24 -412
  28. package/src/ext/contract.ts +1 -1
  29. package/src/ext-prompt/content.ts +17 -12
  30. package/src/ext-prompt/ext-prompt.test.ts +3 -3
  31. package/src/ext-prompt/index.ts +7 -9
  32. package/src/ext-prompt/mount.ts +2 -2
  33. package/src/ext-prompt/plugin.ts +3 -5
  34. package/src/router/index.ts +2 -3
  35. package/src/router/models-cache.test.ts +21 -6
  36. package/src/router/supplier-switch.test.ts +1 -1
  37. package/src/router/types.ts +11 -0
  38. package/src/shared.ts +2 -0
  39. package/src/suppliers/registry.ts +0 -71
  40. package/lib/contract-D4BWZ7Xe.js +0 -13
  41. package/lib/ext-test/index.js +0 -36
  42. package/lib/ext-test/locale/en.json +0 -6
  43. package/lib/ext-test/locale/zh.json +0 -6
  44. package/lib/ext-test/package.json +0 -5
  45. package/lib/types/ext-test/index.d.ts +0 -21
  46. package/lib/types/ext-test/plugin.d.ts +0 -23
  47. package/lib/types/suppliers/probe.d.ts +0 -160
  48. package/src/client/ExtTestPanel.tsx +0 -392
  49. package/src/client/comp-button-scope.test.ts +0 -58
  50. package/src/client/comp-panel-layout.test.ts +0 -58
  51. package/src/client/ext-panels.test.ts +0 -44
  52. package/src/client/ext-panels.ts +0 -49
  53. package/src/client/model-choice.test.ts +0 -41
  54. package/src/client/model-choice.ts +0 -37
  55. package/src/ext-test/ext-test.test.ts +0 -60
  56. package/src/ext-test/index.ts +0 -35
  57. package/src/ext-test/locale/en.json +0 -6
  58. package/src/ext-test/locale/zh.json +0 -6
  59. package/src/ext-test/plugin.ts +0 -34
  60. package/src/ext-test/row.json +0 -5
  61. package/src/suppliers/probe.test.ts +0 -266
  62. package/src/suppliers/probe.ts +0 -475
package/README.md CHANGED
@@ -119,7 +119,7 @@ team 测试就是多个会话交错发请求。dsh-router 原生适配这个场
119
119
  第二处入口是**设置 → 插件 → dsh-router-core 的详情页**,那里分两层:
120
120
 
121
121
  - 宿主的原生**「包含的组件」**列 core 自己 patch 里声明的**行**:core 本体、三个
122
- 内置供应商、插件自检这个内置扩展。每行一个宿主管的开关,关一行 = loader 不
122
+ 内置供应商、分层提示词这个内置扩展。每行一个宿主管的开关,关一行 = loader 不
123
123
  import 它 = 它压根没注册。
124
124
  - 原生列表**下面**是本插件自绘的**「路由组件」**一节,列**外部供应商插件**与
125
125
  **扩展插件**,两组都带开关。
@@ -165,7 +165,8 @@ team 测试就是多个会话交错发请求。dsh-router 原生适配这个场
165
165
  - **可用模型** — 模型列表,逐个启用/禁用 + 自定义模型(通用能力,持久化到
166
166
  `data/supplier-config.json`,`/v1/models` 与 chat 只接受启用的模型);单个模型可
167
167
  「测试」,走真实对话路径并按账号池依次回退,所以能分清是这个账号额度没了还是
168
- 该模型真的不支持;
168
+ 该模型真的不支持。行上显示**上游的可读名**(如 `Qwen3.8-Max`),上游 key
169
+ (`qmodel_38max`)跟在小号淡字里 —— 请求与组合用的始终是 key;
169
170
  - **组合** — fallback 链(免费优先),可自定义。**组合即模型**:建好的组合会**自动带出**
170
171
  为 DSH 模型目录里的 `router` provider 选项(设置 → 模型直接选组合名即可用),请求
171
172
  按组合策略命中其中一个供应商模型;
@@ -291,55 +292,6 @@ Anthropic 的工具调用(非流式 `tool_use` 与流式 `content_block_start` +
291
292
  把它开回来。开关 UI 在**设置 → 插件 → dsh-router-core 详情页**下面的「路由组件」一节
292
293
  (「供应商」组 + 「扩展」组)。
293
294
 
294
- ## 插件自检(内置扩展 `dsh-router-ext-test`)
295
-
296
- 随核心分发的内置扩展,提供两块自检面板。它**不挂任何监听、不改写任何命令** ——
297
- 与 RTK 那类扩展的区别只在于它只提供面板。
298
-
299
- 1. **连接测试**:选**供应商 / 模型 / 连接**,跑一次真实的访问测试。
300
- 2. **契约体检**:选一个**外部供应商插件**,逐个契约成员报告「实现了吗 / 实跑通了吗 /
301
- 为什么没跑」。
302
-
303
- 它与三个内置供应商走同一套管道:是 `cordis.patch.yml` 里的一个**行**(子路径模块
304
- `dsh-router-core/ext-test`),因此在插件页原生「包含的组件」里占一行、开关由宿主管,
305
- 且**默认关闭**。行关闭时 loader 根本不 import 这个模块,扩展就不在 `router.ext` 表里 ——
306
- 「打开那一行才出现这张卡片」是天然的,不需要额外的状态位。
307
-
308
- 面板入口**只有一处**:设置 → 路由 → 扩展 → 点开「插件自检」。插件页那一行不挂详情页,
309
- 它只负责开关(理由见 [扩展插件](#扩展插件routerext) 一节)。
310
-
311
- 测试走核心已有的 `POST /suppliers/:id/models/test`,与面板「供应商」详情里的「测试」
312
- 按钮**同一条真实路径**(账号遍历 + chatOnce)。区别只在可以**钉死连接**:留空则由账号池
313
- 任选可用号(与那个按钮同义);指定连接则只测它、**失败不回退到别的号** —— 否则
314
- 「测这个连接」测出来的是另一个号的结果,诊断就成了假话。
315
-
316
- 面板的模型下拉**只列可用模型**:那个端点给的是全部模型(含用户在供应商详情里停用的),
317
- 把停用的也列出来,用户会挑一个自己明明关掉的模型去测。列表为空时还区分「一个模型都
318
- 没有」(该去拉取)与「全被停用」(该去开启)—— 共用一句「没有模型」会把用户往错的方向指。
319
-
320
- ### 契约体检:报告,不是代替你操作
321
-
322
- `POST /router/api/suppliers/:id/probe` 逐个成员给状态:`ok`(实现了且实跑通)/
323
- `fail`(必填成员缺失,或实跑抛错)/ `absent`(可选成员没实现)/ `skipped`(实现了但**故意
324
- 不跑**)。
325
-
326
- **有副作用的一律不自动执行**,只标出它实现了并说明为什么:
327
-
328
- | 成员 | 为什么不给自动跑 |
329
- | --- | --- |
330
- | `dispose` | 调用它就是把这个供应商卸载掉 |
331
- | `addApiKey` / `removeLink` | 写/删凭证 |
332
- | `generateLoginUrl` | 可能直接触发设备码 / OAuth 登录流 |
333
- | `completeLogin` | 需要一个真实回调 URL,空调没有意义 |
334
- | `checkinNow` | 替这个连接真的去签到 |
335
- | `chatOnce` | 会真发一次请求(消耗额度)——用上面的「跑一次访问测试」单独测 |
336
-
337
- 体检是给眼睛看的,不是给手用的:要触发上面这些,去供应商详情里点对应的按钮。
338
-
339
- 清单与契约**必须一起长**:`src/suppliers/probe.ts` 的成员表有一条判据直接解析
340
- `contract.ts` 源码里的 `SupplierModule` 成员名并断言全覆盖(编译期抓不到这种「两份
341
- 清单」),所以契约新增成员而体检漏检会当场红。
342
-
343
295
  ## 扩展插件(`router.ext`)
344
296
 
345
297
  > 完整契约、注册方式、自检与降级约定见 [`docs/ext.md`](docs/ext.md)。
@@ -372,17 +324,6 @@ Anthropic 的工具调用(非流式 `tool_use` 与流式 `content_block_start` +
372
324
  (`PATCH /router/api/ext`)。开启时会**自检**:`getState().ready === false` 的拒绝开启
373
325
  (API 409),那一面的开关禁用并显示原因。
374
326
 
375
- ### 扩展可以自带详情面板
376
-
377
- 扩展详情页原本是一张写死的只读页(名字、id、状态、一段说明),扩展自己想放的交互
378
- (要选的模型、要跑的测试、要看的诊断)没地方放。客户端因此有一张按**扩展 id** 索引的
379
- 面板注册表(`src/client/ext-panels.ts` 的 `registerExtPanel`):登记了就用你的组件渲染
380
- 整个内容区,没登记才落回通用只读页。`插件自检` 就是这么用的。
381
-
382
- ⚠️ 这张注册表目前只在 dsh-router 自己的 client bundle 内,**外部插件包还引不到** ——
383
- `dsh-router-core/client` 是整个入口闭包、不导出它。要让外部插件也能自带面板需要加一个
384
- 真正的导出路径,详见 [`docs/ext.md`](docs/ext.md)。
385
-
386
327
  ### 内置扩展不在自绘节里重复列
387
328
 
388
329
  随核心分发的扩展(`source: 'builtin'`)已经是插件页原生「包含的组件」里的一行、自带
@@ -405,16 +346,17 @@ Anthropic 的工具调用(非流式 `tool_use` 与流式 `content_block_start` +
405
346
 
406
347
  ### 两层,各管一件事
407
348
 
408
- **第一层:准则(内置 13 条)**
349
+ **第一层:准则(内置 12 条)**
409
350
 
410
- 插件自带一套协作准则(`[准则 v5]`),按语义拆成 13 条,**每条独立开关、独立可改**:
351
+ 插件自带一套协作准则(`[准则 v5]`),按语义拆成 12 条,**每条独立开关、独立可改**:
411
352
 
412
353
  | 分类 | 管什么 |
413
354
  | --- | --- |
414
355
  | 身份气质 / 视野 / 懒人梯子 | 怎么定位自己、动手前看什么、找最省的解法 |
415
- | 领地 / 全局收口 / 可判定 | 工作边界、收尾复查、什么叫"做完了" |
356
+ | 领地 / 全局收口 | 工作边界、收尾复查 |
357
+ | 文档 | 只写权威内容(结论/约束/怎么用),不写流水账 |
416
358
  | 修根因 / 取舍 / 底线 | 改共享函数而不是补单点、砍边角与升级路径、不可逆禁令 |
417
- | 结构 / 绝不偷懒 / 交付 | 先结构后代码、校验与安全、交付口径 |
359
+ | 结构 / 交付 | 先结构后代码、交付口径 |
418
360
  | 执行顺序 | 一次任务的固定流程 |
419
361
 
420
362
  **还能自己加条目**:面板上「新增」写一条自己的准则,与内置的并列、同样能开关。
@@ -460,7 +402,7 @@ Anthropic 的工具调用(非流式 `tool_use` 与流式 `content_block_start` +
460
402
 
461
403
  ### 两条纪律
462
404
 
463
- - **自己那段永不进段落清单**。准则已经有总开关 + 14 条分类开关,再列一条段开关
405
+ - **自己那段永不进段落清单**。准则已经有总开关 + 逐条分类开关,再列一条段开关
464
406
  就是同一件事的两个开关,状态必然漂移;过滤时它也恒放行。
465
407
  - **落盘只存关闭项**(`data.sections[name] = false`),开回来就删键。宿主段随
466
408
  版本增删,存全表就得每次升级对账;只记"用户主动关了谁"让新段自然落回默认。
@@ -479,14 +421,13 @@ Anthropic 的工具调用(非流式 `tool_use` 与流式 `content_block_start` +
479
421
  │ │ ├─ StatsTab 概览:用量看板(周期按钮组 + 汇总卡 + 折线趋势 + Top 榜 + 最近请求)
480
422
  │ │ ├─ SupplierDetail 供应商详情:链接池 + 加链接 + 可用模型 + 模型测试
481
423
  │ │ ├─ CombosTab / EndpointTab
482
- │ │ └─ ExtTab → ExtDetail 扩展列表(只列已启用)与详情(优先渲染扩展自带的面板)
424
+ │ │ └─ ExtTab → ExtDetail 扩展列表(只列已启用)与详情(子开关 + 未就绪原因)
483
425
  │ └─ LastHitDock 「路由」徽章(composer.dock 座位;按会话显示最近命中)
484
426
  ├─ 设置 → 插件(官方 plugins.detail.section 座位)
485
427
  │ ├─ (原生「包含的组件」) core patch 里声明的行:core 本体 + 内置供应商 + 内置扩展
486
428
  │ │ 宿主管的开关,关一行 = loader 不 import 它
487
429
  │ └─ RouterComponentsSection dsh-router-core 详情页自绘的「路由组件」一节
488
430
  │ (外部供应商 + 扩展,两组都带开关)
489
- │ └─ ExtTestPanel 插件自检扩展的详情面板(按 ext id 登记到 ext-panels.ts)
490
431
  └─ fetch /router/api/* (同源,无 CORS)
491
432
  └─ host 半(src/index.ts)
492
433
  ├─ /v1/models + /v1/chat/completions (OpenAI 兼容, KeysStore 鉴权)
@@ -509,7 +450,7 @@ Anthropic 的工具调用(非流式 `tool_use` 与流式 `content_block_start` +
509
450
  └─ 外部插件供应商(经 router.suppliers service 注册)
510
451
  ↑ 内置三个是 core 自己 patch 里的**行**(子路径模块),与外部插件同一条通道
511
452
  └─ 扩展表 router.ext(core provide 空表,谁都能往里 append)
512
- ├─ 内置:插件自检(行 dsh-router-core/ext-test,默认关闭)
453
+ ├─ 内置:分层提示词(行 dsh-router-core/ext-prompt,默认关闭)
513
454
  └─ 外部:dsh-router-ext-rtk 等独立 bundle 经同一个 service 注册
514
455
  ```
515
456
 
@@ -529,6 +470,9 @@ Anthropic 的工具调用(非流式 `tool_use` 与流式 `content_block_start` +
529
470
  2. **用户目录**:`~/.dsh/profiles/<profile>/suppliers/*.js`,**压过**同名内置行。
530
471
  - **模型统一策略**:插件不内置、不缓存模型;`listModels` 每次从上游拉取,
531
472
  缓存由核心按 60s TTL 统一管(`/suppliers/:id/models`),`/v1/models` 保持实时。
473
+ 条目是 `{ id, name?, context_length? }`:**`id` 是上游 key**(组合、路由、请求都用它),
474
+ **`name` 是上游的可读名**(只用于显示)。有的上游 key 是内部标识(Qoder 的
475
+ `qmodel_38max`),不把名字带上来,面板上就只有一串看不懂的字母。
532
476
  - **凭证存储**:SQLite 单库 `{authDir}/credentials.sqlite`(表 `credentials(supplier, uid, data)`,
533
477
  凭证为供应商不透明 JSON blob)。
534
478
  - **/v1/\* 鉴权**:由 `KeysStore.requireApiKey` 控制。关闭 → 不鉴权;
package/cordis.patch.yml CHANGED
@@ -30,13 +30,8 @@
30
30
  name: 'dsh-router-core/suppliers/openrouter'
31
31
  - id: dsh-router-nvidia
32
32
  name: 'dsh-router-core/suppliers/nvidia'
33
- # 连接自检扩展。**默认关闭**:行关闭时 loader 根本不 import 它,扩展就不在
34
- # `router.ext` 表里 —— 所以「打开才出现这张卡片」是天然的,不需要额外状态位。
35
- - id: dsh-router-ext-test
36
- name: 'dsh-router-core/ext-test'
37
- disabled: true
38
- # 分层提示词扩展。**默认关闭**(与 ext-test 同款):行关闭时 loader 根本不
39
- # import 它,扩展就不在 `router.ext` 表里 —— 「打开才出现这张卡片」是天然的。
33
+ # 分层提示词扩展。**默认关闭**:行关闭时 loader 根本不 import 它,扩展就不在
34
+ # `router.ext` 表里 —— 「打开才出现这张卡片」是天然的。
40
35
  #
41
36
  # ⚠️ 这里来回改过一次(2026-09-29):先改成默认开启(理由是"准则是基础设施"),
42
37
  # 随后又改回默认关闭 —— **决定权在用户**。判据见 ext-prompt.test.ts 的
package/docs/ext.md CHANGED
@@ -12,20 +12,6 @@ bash 命令。装好后出现在「设置 → 路由 → 扩展」,**开关不
12
12
  数据目录是 `<profile>/data/`,**不跟进程 cwd 跑**——从任何目录启动 `dsh web`
13
13
  读到的都是同一份配置(见 `src/data-dir.ts`)。
14
14
 
15
- ## 扩展详情页可以自带内容
16
-
17
- 扩展详情页默认是一张**写死的只读页**(名字、id、状态、一段说明)。扩展若想在详情里放
18
- 自己的交互(要选的模型、要跑的测试、要看的诊断),可以按**扩展 id** 登记一个面板组件
19
- (`src/client/ext-panels.ts` 的 `registerExtPanel`):登记后 `ExtDetail` 用你的组件渲染
20
- 整个内容区,没登记才落回那张通用只读页。组件**不收 props** —— 面板的渲染点只有详情页
21
- 一处。核心自带的 `插件自检` 就是这么做的。
22
-
23
- ⚠️ **目前这张注册表在 dsh-router 自己的 client bundle 内部,外部插件包还引不到它**:
24
- `dsh-router-core/client` 是整个 client 入口(一个注册座位的 CJS 闭包),并不导出
25
- `registerExtPanel`。要让外部插件也能自带面板,需要给它加一个真正的导出路径
26
- (如 `./client/panels`)。这是已知的升级路径,不是现在就能用的写法 —— 照着现有 exports
27
- 去 `import { registerExtPanel } from 'dsh-router-core/client'` 会拿到 undefined。
28
-
29
15
  参考实现:[dsh-router-ext-rtk](https://github.com/CARVIN94/dsh-router-ext-rtk)
30
16
  (把命令改写成 `rtk <cmd>` 压缩输出)。
31
17
 
@@ -68,9 +54,9 @@ export function createPromptExt(deps): RouterExt {
68
54
  (隐藏会让用户以为它们不存在),重新打开总开关就恢复原样。
69
55
  3. **`control.id` 是持久化键**,改名会让用户已保存的选择丢失 —— 同 `ExtId`。
70
56
 
71
- ⚠️ **不适用于外部插件包(暂时)**:与下面的 `registerExtPanel` 一样,
72
- `controls` 的读写经 `dsh-router-core/client` 才能到达外部 bundle,而该入口不导出
73
- 这些能力。要让外部扩展用上,需要给它加一条真正的导出路径。这是已知的升级路径。
57
+ ⚠️ **不适用于外部插件包(暂时)**:`controls` 的读写经 `dsh-router-core/client`
58
+ 才能到达外部 bundle,而该入口不导出这些能力。要让外部扩展用上,需要给它加一条
59
+ 真正的导出路径。这是已知的升级路径。
74
60
 
75
61
  ## 分工:管理归核心,执行归插件
76
62
 
package/docs/suppliers.md CHANGED
@@ -42,7 +42,7 @@ export const priority = 10 // 数字越小越优先(免费优先)
42
42
  | 方法 | 说明 |
43
43
  |---|---|
44
44
  | `status()` | 报账号**现在状态**:`{ id, name, accounts: [{ uid, nickname, credits, state }] }` |
45
- | `listModels(force?)` | `[{ id, context_length? }]`;`force=true` 时强制刷新来源(「获取模型」按钮调用) |
45
+ | `listModels(force?)` | `[{ id, name?, context_length? }]`;`force=true` 时强制刷新来源(「获取模型」按钮调用)。`id` = 上游 key(组合/路由/请求都用它),`name` = 上游的可读名,**只用于显示**——上游 key 是内部标识时(Qoder 的 `qmodel_38max`)不报 name,面板上就只有一串看不懂的字母 |
46
46
  | `getAlias()` | 默认前缀 = 供应商 id(模型全名 = alias/id,别名留空即用 id) |
47
47
  | `chatOnce(uid, req)` | **对单个账号调一次上游**,见下 |
48
48
  | `dispose()` | 停止时清理 |