dsh-router-core 0.3.21 → 0.4.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.
Files changed (120) hide show
  1. package/README.md +238 -63
  2. package/cordis.patch.yml +22 -0
  3. package/docs/ext.md +28 -5
  4. package/docs/pool-sticky-block.md +158 -0
  5. package/docs/suppliers.md +28 -5
  6. package/lib/client-registry.js +1300 -549
  7. package/lib/client.js +1300 -549
  8. package/lib/ext-test/index.js +47 -0
  9. package/lib/ext-test/locale/en.json +6 -0
  10. package/lib/ext-test/locale/zh.json +6 -0
  11. package/lib/ext-test/package.json +5 -0
  12. package/lib/http-state-C1vb3gW2.js +48 -0
  13. package/lib/index.js +1169 -254
  14. package/lib/suppliers/{nvidia.js → nvidia/index.js} +14 -6
  15. package/lib/suppliers/nvidia/locale/en.json +6 -0
  16. package/lib/suppliers/nvidia/locale/zh.json +6 -0
  17. package/lib/suppliers/nvidia/package.json +5 -0
  18. package/lib/suppliers/{opencode.js → opencode/index.js} +14 -6
  19. package/lib/suppliers/opencode/locale/en.json +6 -0
  20. package/lib/suppliers/opencode/locale/zh.json +6 -0
  21. package/lib/suppliers/opencode/package.json +5 -0
  22. package/lib/suppliers/{openrouter.js → openrouter/index.js} +14 -6
  23. package/lib/suppliers/openrouter/locale/en.json +6 -0
  24. package/lib/suppliers/openrouter/locale/zh.json +6 -0
  25. package/lib/suppliers/openrouter/package.json +5 -0
  26. package/lib/types/ext/contract.d.ts +9 -7
  27. package/lib/types/ext-test/index.d.ts +21 -0
  28. package/lib/types/ext-test/plugin.d.ts +23 -0
  29. package/lib/types/host-version.d.ts +15 -0
  30. package/lib/types/index.d.ts +6 -0
  31. package/lib/types/llm/adapter.d.ts +35 -4
  32. package/lib/types/router/account-pool.d.ts +53 -18
  33. package/lib/types/router/index.d.ts +65 -15
  34. package/lib/types/router/prefix-affinity.d.ts +18 -1
  35. package/lib/types/router/types.d.ts +7 -4
  36. package/lib/types/router/usage-store.d.ts +12 -0
  37. package/lib/types/shared.d.ts +25 -21
  38. package/lib/types/supplier-config.d.ts +15 -3
  39. package/lib/types/suppliers/contract.d.ts +37 -4
  40. package/lib/types/suppliers/http-state.d.ts +23 -0
  41. package/lib/types/suppliers/loader.d.ts +6 -4
  42. package/lib/types/suppliers/nvidia/index.d.ts +18 -0
  43. package/lib/types/suppliers/nvidia/plugin.d.ts +13 -0
  44. package/lib/types/suppliers/opencode/index.d.ts +18 -0
  45. package/lib/types/suppliers/opencode/plugin.d.ts +7 -0
  46. package/lib/types/suppliers/openrouter/index.d.ts +18 -0
  47. package/lib/types/suppliers/openrouter/plugin.d.ts +11 -0
  48. package/lib/types/suppliers/probe.d.ts +160 -0
  49. package/lib/types/suppliers/registry.d.ts +3 -2
  50. package/package.json +50 -16
  51. package/src/client/CombosTab.tsx +8 -53
  52. package/src/client/ExtDetail.tsx +32 -196
  53. package/src/client/ExtTab.tsx +25 -42
  54. package/src/client/ExtTestPanel.tsx +392 -0
  55. package/src/client/LastHitDock.tsx +141 -0
  56. package/src/client/RouterComponentsSection.tsx +241 -0
  57. package/src/client/RouterView.tsx +12 -5
  58. package/src/client/StatsTab.tsx +8 -6
  59. package/src/client/SupplierDetail.tsx +2 -34
  60. package/src/client/comp-button-scope.test.ts +58 -0
  61. package/src/client/comp-panel-layout.test.ts +58 -0
  62. package/src/client/env.d.ts +38 -1
  63. package/src/client/ext-panels.test.ts +44 -0
  64. package/src/client/ext-panels.ts +49 -0
  65. package/src/client/index.tsx +86 -0
  66. package/src/client/last-hit-store.ts +89 -0
  67. package/src/client/model-choice.test.ts +41 -0
  68. package/src/client/model-choice.ts +37 -0
  69. package/src/client/model-hint-copy.test.ts +7 -2
  70. package/src/client/model-hint-copy.ts +2 -2
  71. package/src/client/router-components.test.ts +114 -0
  72. package/src/client/router-components.ts +104 -0
  73. package/src/client/router.css +2592 -1635
  74. package/src/client/supplier-visibility.test.ts +57 -0
  75. package/src/client/supplier-visibility.ts +63 -0
  76. package/src/ext/contract.ts +9 -7
  77. package/src/ext-test/ext-test.test.ts +60 -0
  78. package/src/ext-test/index.ts +35 -0
  79. package/src/ext-test/locale/en.json +6 -0
  80. package/src/ext-test/locale/zh.json +6 -0
  81. package/src/ext-test/plugin.ts +34 -0
  82. package/src/ext-test/row.json +5 -0
  83. package/src/host-version.test.ts +61 -0
  84. package/src/host-version.ts +101 -0
  85. package/src/index.ts +165 -68
  86. package/src/llm/adapter.test.ts +175 -4
  87. package/src/llm/adapter.ts +109 -16
  88. package/src/router/account-pool.test.ts +189 -83
  89. package/src/router/account-pool.ts +120 -31
  90. package/src/router/disconnect-leak.test.ts +7 -12
  91. package/src/router/first-byte-budget.test.ts +160 -0
  92. package/src/router/index.ts +276 -62
  93. package/src/router/models-cache.test.ts +54 -9
  94. package/src/router/precise-dispatch.test.ts +6 -6
  95. package/src/router/prefix-affinity.ts +22 -1
  96. package/src/router/supplier-switch.test.ts +220 -0
  97. package/src/router/types.ts +7 -5
  98. package/src/router/usage-store.ts +16 -0
  99. package/src/router/usage.test.ts +1 -1
  100. package/src/shared.ts +25 -24
  101. package/src/supplier-config.ts +33 -13
  102. package/src/suppliers/contract.ts +56 -4
  103. package/src/suppliers/loader.test.ts +38 -4
  104. package/src/suppliers/loader.ts +33 -18
  105. package/src/suppliers/nvidia/index.ts +27 -0
  106. package/src/suppliers/nvidia/locale/en.json +6 -0
  107. package/src/suppliers/nvidia/locale/zh.json +6 -0
  108. package/src/suppliers/nvidia/row.json +5 -0
  109. package/src/suppliers/opencode/index.ts +27 -0
  110. package/src/suppliers/opencode/locale/en.json +6 -0
  111. package/src/suppliers/opencode/locale/zh.json +6 -0
  112. package/src/suppliers/opencode/row.json +5 -0
  113. package/src/suppliers/openrouter/index.ts +27 -0
  114. package/src/suppliers/openrouter/locale/en.json +6 -0
  115. package/src/suppliers/openrouter/locale/zh.json +6 -0
  116. package/src/suppliers/openrouter/row.json +5 -0
  117. package/src/suppliers/probe.test.ts +266 -0
  118. package/src/suppliers/probe.ts +475 -0
  119. package/src/suppliers/registry.ts +103 -23
  120. package/src/types.d.ts +2 -1
@@ -0,0 +1,11 @@
1
+ import type { SupplierEnv, SupplierModule } from '../contract.ts';
2
+ export declare const id = "openrouter";
3
+ export declare const name = "OpenRouter";
4
+ export declare const priority = 10;
5
+ /** 面板图标。 */
6
+ export declare const icon = "data:image/png;base64,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";
7
+ /**
8
+ * 「添加 API key」弹窗提示(OpenRouter API key 获取页)。
9
+ */
10
+ export declare const apiKeyHint = "\u586B\u5165 OpenRouter API key\uFF08https://openrouter.ai/settings/keys\uFF09\uFF0C\u53EF\u8D77\u540D\u5B57\u533A\u5206\u591A\u4E2A key\u3002";
11
+ export default function factory(env: SupplierEnv): SupplierModule;
@@ -0,0 +1,160 @@
1
+ import type { LoadedSupplier } from './loader.ts';
2
+ /**
3
+ * 体检只有**一档,全自动**。
4
+ *
5
+ * 前提由使用者保证:连接池里已经有一个可用的 token。所以「每个功能到底通不通」这个问题
6
+ * 是可以被回答的 —— 于是能跑的就都跑掉:
7
+ * - 没有副作用的成员直接实跑;
8
+ * - 有副作用但可以用**无害探针输入**跑通代码路径的也实跑(`removeLink` 传不存在的
9
+ * uid、`completeLogin` 传无效回调 URL、`generateLoginUrl` 直接调);
10
+ * - 需要真实账号/真实额度的也实跑(`chatOnce` 走 `router.testModel` 真发一次、
11
+ * `checkinNow` 对连接池里的真实连接签到);
12
+ * - 会破坏体检前提的(`dispose` 卸载自己、`addApiKey` 写凭证…)**不进清单** ——
13
+ * 见 `PROBE_EXCLUDED_MEMBERS`,逐个写了不测的理由。
14
+ *
15
+ * 报告的每个成员都标 `executed`:体检的价值全在「哪些是验过的、哪些只是看了一眼」。
16
+ */
17
+ export type ProbeExecuted = 'ran' | 'no';
18
+ /** 这个成员是不是有副作用的(`skip` = 需要特别处理:要么探针实跑,要么只报存在性)。 */
19
+ export type ProbeKind = 'safe' | 'skip';
20
+ /** 一个契约成员的体检条目。 */
21
+ export interface ProbeMember {
22
+ /** 契约里的成员名(与 `SupplierModule` 逐字一致)。 */
23
+ key: string;
24
+ /** 面板显示名。 */
25
+ label: string;
26
+ /** 必填成员 / 可选(差异化)成员。 */
27
+ required: boolean;
28
+ probe: ProbeKind;
29
+ /** 不自动执行的原因(`probe: 'skip'` 时必填,面板要显示给用户看)。 */
30
+ skipReason?: string;
31
+ }
32
+ /**
33
+ * 结果里一条成员的最终状态。
34
+ *
35
+ * `unverified` 与 `fail` 必须分开:**没条件验**(没有 token、没有已启用模型、连接池
36
+ * 为空)不是插件的毛病。压成 fail 会让「用户还没放 token」被出厂结论判成不合格 ——
37
+ * 体检最忌讳把「没测到」说成「测出问题」。
38
+ */
39
+ export type ProbeState = 'ok' | 'fail' | 'absent' | 'unverified';
40
+ export interface ProbeResult {
41
+ key: string;
42
+ label: string;
43
+ required: boolean;
44
+ present: boolean;
45
+ state: ProbeState;
46
+ /** 到底跑没跑。`no` 时 `detail` 写清为什么。 */
47
+ executed: ProbeExecuted;
48
+ /** 摘要/失败原因;`executed: 'no'` 时这里是「为什么没跑」。 */
49
+ detail?: string;
50
+ }
51
+ /** 体检的外部输入:模型启用状态 + 跑一次真实 `chatOnce` 的入口。 */
52
+ export interface ProbeInput {
53
+ /** 该供应商当前的模型启用状态(调用方用 `router.modelsOf` 取,走核心缓存)。 */
54
+ models?: ReadonlyArray<{
55
+ id: string;
56
+ enabled: boolean;
57
+ }>;
58
+ /**
59
+ * 跑一次真实调用(`router.testModel`:账号遍历 + 冷却 + 首字节预算都生效)。
60
+ * 调用方决定用哪个模型 / 哪个连接。取不到模型时可以不提供 —— 报告里会写明
61
+ * 「无法验证」而不是假装通过。
62
+ */
63
+ runChatOnce?: (model: string) => Promise<{
64
+ ok: boolean;
65
+ detail: string;
66
+ }>;
67
+ /**
68
+ * **真跑一遍「全部禁用 → 全部启用」并还原**。
69
+ *
70
+ * 这不是「只报可用性」—— 它就是体检里能测出「插件在 listModels 里过滤已禁用模型」
71
+ * 越权的那条路径:核心把全部模型标成停用,再拉一次模型列表,**被藏起来的就露馅**
72
+ * (插件以为该藏,核心却必须还能看见它们,否则面板的「已禁用」列表会空、用户再也
73
+ * 看不到自己禁用过什么)。
74
+ *
75
+ * 由调用方实现,**必须自己还原**(体检不该在用户配置上留痕)。
76
+ */
77
+ runBulkToggleRoundTrip?: () => Promise<{
78
+ ok: boolean;
79
+ detail: string;
80
+ }>;
81
+ }
82
+ /**
83
+ * **核心侧**能力:不属于插件契约(插件不实现它们,是核心代劳),所以不在上面的
84
+ * 成员表里 —— 但体检要看它们的状态。模型启用/禁用就是这一类。
85
+ */
86
+ export interface ProbeCoreReport {
87
+ models: {
88
+ /** 模型总数;`listModels` 失败时为 null(= 拿不到,不代表 0 个)。 */
89
+ total: number | null;
90
+ enabled: number;
91
+ disabled: number;
92
+ /** 拿不到总数时的原因。 */
93
+ note?: string;
94
+ };
95
+ /** 核心代劳的操作:**只报可用性,一律不自动执行**(改了就是替用户改配置)。 */
96
+ /**
97
+ * 核心代劳的能力区。**「全部启用 / 全部禁用」是实跑的**(并还原):它既是面板上
98
+ * 真实存在的操作,也是体检唯一能验出「插件私自过滤已禁用模型」的办法。
99
+ */
100
+ operations: Array<{
101
+ key: string;
102
+ label: string;
103
+ ran: boolean;
104
+ ok?: boolean;
105
+ detail?: string;
106
+ }>;
107
+ }
108
+ export interface ProbeReport {
109
+ supplier: string;
110
+ name: string;
111
+ /** 出厂结论:`fail` = 必填成员缺失/实跑失败;`warn` = 可选能力缺或没实跑;`pass` = 全绿。 */
112
+ members: ProbeResult[];
113
+ core: ProbeCoreReport;
114
+ summary: {
115
+ total: number;
116
+ implemented: number;
117
+ ok: number;
118
+ fail: number;
119
+ absent: number;
120
+ /** 实现了但没验(任何输入都会毁掉体检前提)。 */
121
+ unverified: number;
122
+ /** 实际实跑过的成员数(`executed !== 'no'`)。 */
123
+ ran: number;
124
+ /** 核心区「全部启用/全部禁用」实跑的结果(抓插件越权的那条路)。 */
125
+ bulk: 'ok' | 'fail' | 'unverified';
126
+ };
127
+ }
128
+ /**
129
+ * 契约成员清单(体检的唯一事实来源)。
130
+ *
131
+ * `required: false` = 差异化能力,按存在性暴露端点/UI;`probe: 'skip'` 的理由都写在这里,
132
+ * 面板会把 `skipReason` 原样显示 —— 用户能知道「有这个能力,只是体检不替你调」。
133
+ */
134
+ export declare const SUPPLIER_CONTRACT_MEMBERS: readonly ProbeMember[];
135
+ /**
136
+ * **故意不列进体检清单的成员**(不是「不跑」,是**不在报告里出现**):
137
+ *
138
+ * - `completeLogin`:需要一个真实回调 URL 才跑得通,体检拿不到 —— 跑了也只是验
139
+ * 「无效 URL 会不会抛错」,那不是功能是否可用。
140
+ * - `addApiKey`:需要一个真实 API key,自动造一个只会往凭证库写垃圾。
141
+ * - `removeLink`:用不存在的 uid 去试,它当然返回 false —— 那是**正常响应**,
142
+ * 判成失败是体检自己错判功能有问题(踩过)。真要验它只能删一个真连接。
143
+ * - `pollLogin`:只读一个布尔标记,验它通不通没有意义。
144
+ * - `generateLoginUrl`:可能直接触发设备码 / OAuth 登录流,会在上游留下待处理的会话。
145
+ *
146
+ * 保留在清单里、逐条占行、只为说「没验」,是在用篇幅淹掉真问题。
147
+ * 但**契约里加了新成员,体检要能报出来** —— `probe.test.ts` 有一条判据解析
148
+ * `contract.ts` 断言「除了这份有意排除的名单之外必须全覆盖」。
149
+ */
150
+ export declare const PROBE_EXCLUDED_MEMBERS: Record<string, string>;
151
+ /**
152
+ * 体检一个已装载的供应商。
153
+ *
154
+ * 只跑 `probe: 'safe'` 的成员;`skip` 的只报存在性。绝不代替用户调有副作用的成员。
155
+ *
156
+ * @param models - 该供应商当前的模型启用状态(由调用方用 `router.modelsOf` 取,
157
+ * 走核心缓存不额外打上游)。取不到就传 undefined —— 报告里会如实写「拿不到」,
158
+ * 不能拿 0 冒充「一个模型都没有」。
159
+ */
160
+ export declare function probeSupplier(loaded: LoadedSupplier, input?: ProbeInput): Promise<ProbeReport>;
@@ -3,17 +3,18 @@
3
3
  *
4
4
  * 通用能力(dsh-router 核心,所有供应商自动可用,js 无需实现):
5
5
  * GET /suppliers/:id/models models + alias(listModels 合并启用状态)
6
+ * POST /suppliers/:id/probe 契约体检:逐成员报「实现/可用/不执行」
7
+ * PATCH /suppliers/:id/enabled {enabled} —— 供应商开关(关掉不参与路由)
6
8
  * POST /suppliers/:id/models/toggle {id, enabled}
7
9
  * POST /suppliers/:id/models/add {id}
8
10
  * POST /suppliers/:id/models/remove {id}
9
11
  * POST /suppliers/:id/models/bulk {enabled}
10
12
  * POST /suppliers/:id/alias {alias}
11
13
  * GET /suppliers/:id/pool/order + POST {uids}
12
- * GET /suppliers/:id/pool/strategy + POST {strategy}
13
14
  *
14
15
  * 差异化能力(供应商 js 实现,按存在性注册;未实现返回 404):
15
16
  * POST /suppliers/:id/models/fetch 拉取上游模型
16
- * POST /suppliers/:id/models/test {id}
17
+ * POST /suppliers/:id/models/test {id, uid?}(uid = 只测这个连接)
17
18
  * POST /suppliers/:id/login 生成登录链接
18
19
  * POST /suppliers/:id/login/callback {callbackUrl}
19
20
  * POST /suppliers/:id/links/remove {uid}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-router-core",
3
- "version": "0.3.21",
3
+ "version": "0.4.1",
4
4
  "description": "DSH plugin: a simplified 9router (AI routing gateway). Adds a 路由系统 (Routing System) sidebar entry beside 记忆系统; the panel shows suppliers, account pool, combos, and endpoint/API keys.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -18,6 +18,30 @@
18
18
  "types": "./lib/types/index.d.ts",
19
19
  "default": "./lib/index.js"
20
20
  },
21
+ "./suppliers/opencode": {
22
+ "types": "./lib/types/suppliers/opencode/index.d.ts",
23
+ "default": "./lib/suppliers/opencode/index.js"
24
+ },
25
+ "./suppliers/opencode/package.json": "./lib/suppliers/opencode/package.json",
26
+ "./suppliers/opencode/locale/*.json": "./lib/suppliers/opencode/locale/*.json",
27
+ "./suppliers/openrouter": {
28
+ "types": "./lib/types/suppliers/openrouter/index.d.ts",
29
+ "default": "./lib/suppliers/openrouter/index.js"
30
+ },
31
+ "./suppliers/openrouter/package.json": "./lib/suppliers/openrouter/package.json",
32
+ "./suppliers/openrouter/locale/*.json": "./lib/suppliers/openrouter/locale/*.json",
33
+ "./suppliers/nvidia": {
34
+ "types": "./lib/types/suppliers/nvidia/index.d.ts",
35
+ "default": "./lib/suppliers/nvidia/index.js"
36
+ },
37
+ "./suppliers/nvidia/package.json": "./lib/suppliers/nvidia/package.json",
38
+ "./suppliers/nvidia/locale/*.json": "./lib/suppliers/nvidia/locale/*.json",
39
+ "./ext-test": {
40
+ "types": "./lib/types/ext-test/index.d.ts",
41
+ "default": "./lib/ext-test/index.js"
42
+ },
43
+ "./ext-test/package.json": "./lib/ext-test/package.json",
44
+ "./ext-test/locale/*.json": "./lib/ext-test/locale/*.json",
21
45
  "./client": "./lib/client.js",
22
46
  "./src/*": "./src/*",
23
47
  "./package.json": "./package.json"
@@ -35,7 +59,8 @@
35
59
  "react-dom",
36
60
  "cordis",
37
61
  "@deepseek-ai/dsh-client-ui-slots",
38
- "@deepseek-ai/dsh-client-locale"
62
+ "@deepseek-ai/dsh-client-locale",
63
+ "@deepseek-ai/dsh-client-ui-primitives"
39
64
  ],
40
65
  "platform": "web"
41
66
  }
@@ -44,7 +69,13 @@
44
69
  "lib/index.js",
45
70
  "lib/client.js",
46
71
  "lib/client-registry.js",
47
- "lib/suppliers/*.js",
72
+ "lib/*.js",
73
+ "lib/suppliers/*/index.js",
74
+ "lib/suppliers/*/package.json",
75
+ "lib/suppliers/*/locale/*.json",
76
+ "lib/ext-test/index.js",
77
+ "lib/ext-test/package.json",
78
+ "lib/ext-test/locale/*.json",
48
79
  "lib/types/**/*.d.ts",
49
80
  "src",
50
81
  "cordis.patch.yml",
@@ -53,7 +84,7 @@
53
84
  "LICENSE"
54
85
  ],
55
86
  "scripts": {
56
- "build": "node scripts/check-css.mjs && node scripts/check-publish.mjs && rm -rf lib && tsc -p tsconfig.build.json && tsdown && npm run build:client && node scripts/check-publish.mjs",
87
+ "build": "node scripts/check-css.mjs && node scripts/check-publish.mjs --source && rm -rf lib && tsc -p tsconfig.build.json && tsdown && node scripts/copy-row-meta.mjs && npm run build:client && node scripts/check-publish.mjs",
57
88
  "build:client": "DSH_CLIENT_ID=dsh-router-core DSH_CLIENT_FILE=client.js vite build -c client/vite.config.ts && DSH_CLIENT_ID=dsh-external/dsh-router DSH_CLIENT_FILE=client-registry.js vite build -c client/vite.config.ts",
58
89
  "typecheck": "tsc --noEmit",
59
90
  "test": "node --test \"src/**/*.test.ts\"",
@@ -65,13 +96,14 @@
65
96
  },
66
97
  "license": "MIT",
67
98
  "dependencies": {
68
- "@deepseek-ai/schemastery": "^3.18.2"
99
+ "@deepseek-ai/schemastery": "^3.18.4"
69
100
  },
70
101
  "peerDependencies": {
71
102
  "@deepseek-ai/cordis": "^4.0.2",
72
- "@deepseek-ai/dsh-client-locale": "^0.1.5-rc.1",
73
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.1",
74
- "@deepseek-ai/dsh-llm": "^0.1.5-rc.1",
103
+ "@deepseek-ai/dsh-client-locale": "^0.1.5-rc.2",
104
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.2",
105
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.2",
106
+ "@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
75
107
  "@deepseek-ai/dsh-settings": "^0.1.5-rc.2",
76
108
  "cordis": "^4.0.0-rc.7",
77
109
  "react": "^18.2.0",
@@ -86,19 +118,21 @@
86
118
  },
87
119
  "@deepseek-ai/dsh-client-ui-slots": {
88
120
  "optional": true
121
+ },
122
+ "@deepseek-ai/dsh-client-ui-primitives": {
123
+ "optional": true
89
124
  }
90
125
  },
91
126
  "devDependencies": {
92
- "@deepseek-ai/dsh-llm": "0.1.5-rc.1",
93
- "@types/node": "^26.4.1",
94
- "@types/react": "~19.2.18",
95
- "@types/react-dom": "~19.2.7",
96
- "cordis": "4.0.0-rc.9",
97
- "react": "^19.2.8",
98
- "react-dom": "19.2.8",
127
+ "@types/node": "^26.6.2",
128
+ "@types/react": "~19.3.0",
129
+ "@types/react-dom": "~19.3.0",
130
+ "cordis": "4.0.0-rc.10",
131
+ "react": "^19.3.0",
132
+ "react-dom": "19.3.0",
99
133
  "tsdown": "^0.23.0",
100
134
  "typescript": "^7.0.2",
101
- "vite": "8.2.2",
135
+ "vite": "8.3.0",
102
136
  "vite-plugin-css-injected-by-js": "^5.0.2"
103
137
  },
104
138
  "publishConfig": {
@@ -1,14 +1,13 @@
1
1
  /**
2
2
  * 组合 — 布局交互贴近 9router combos 页。
3
3
  * 组合 = 命名的一组模型(完整名 alias/id),请求 model 传组合名时,
4
- * 按策略(回退/轮询)命中其中一个。可创建/编辑/删除。
4
+ * 作为降级链按序尝试命中其中一个。可创建/编辑/删除。
5
5
  */
6
6
  import { useEffect, useRef, useState } from 'react'
7
7
  import {
8
8
  ROUTER_API_BASE,
9
9
  type RouterCombo,
10
10
  type RouterComboAlias,
11
- type RouterComboStrategy,
12
11
  type RouterComboSupplierGroup,
13
12
  } from '../shared.ts'
14
13
  import { Modal } from './Modal.tsx'
@@ -44,7 +43,6 @@ interface CombosTabProps {
44
43
 
45
44
  interface ComboForm {
46
45
  name: string
47
- strategy: RouterComboStrategy
48
46
  models: string[]
49
47
  }
50
48
 
@@ -71,7 +69,7 @@ export function CombosTab({ combos, aliases, onRefresh }: CombosTabProps): JSX.E
71
69
  }
72
70
  const [showForm, setShowForm] = useState(false)
73
71
  const [editing, setEditing] = useState<RouterCombo | null>(null)
74
- const [form, setForm] = useState<ComboForm>({ name: '', strategy: 'fallback', models: [] })
72
+ const [form, setForm] = useState<ComboForm>({ name: '', models: [] })
75
73
  const [formError, setFormError] = useState('')
76
74
  const [saving, setSaving] = useState(false)
77
75
  const [deleteTarget, setDeleteTarget] = useState<RouterCombo | null>(null)
@@ -115,7 +113,6 @@ export function CombosTab({ combos, aliases, onRefresh }: CombosTabProps): JSX.E
115
113
  const openCreate = (): void => {
116
114
  setForm({
117
115
  name: '',
118
- strategy: 'fallback',
119
116
  models: [],
120
117
  })
121
118
  setModelQuery('')
@@ -128,7 +125,6 @@ export function CombosTab({ combos, aliases, onRefresh }: CombosTabProps): JSX.E
128
125
  const openEdit = (combo: RouterCombo): void => {
129
126
  setForm({
130
127
  name: combo.name,
131
- strategy: combo.strategy,
132
128
  models: [...combo.models],
133
129
  })
134
130
  setModelQuery('')
@@ -191,8 +187,8 @@ export function CombosTab({ combos, aliases, onRefresh }: CombosTabProps): JSX.E
191
187
  method: 'POST',
192
188
  headers: { 'Content-Type': 'application/json' },
193
189
  body: JSON.stringify(isEdit
194
- ? { id: editing!.id, name: form.name.trim(), strategy: form.strategy, models: form.models }
195
- : { name: form.name.trim(), strategy: form.strategy, models: form.models }),
190
+ ? { id: editing!.id, name: form.name.trim(), models: form.models }
191
+ : { name: form.name.trim(), models: form.models }),
196
192
  cache: 'no-store',
197
193
  })
198
194
  const data = await response.json() as { ok: boolean; error?: string }
@@ -237,24 +233,6 @@ export function CombosTab({ combos, aliases, onRefresh }: CombosTabProps): JSX.E
237
233
  }
238
234
  }
239
235
 
240
- const updateStrategy = async (id: string, strategy: RouterComboStrategy): Promise<void> => {
241
- const combo = combos.find(c => c.id === id)
242
- if (!combo) return
243
- const response = await fetch(`${ROUTER_API_BASE}/combos/update`, {
244
- method: 'POST',
245
- headers: { 'Content-Type': 'application/json' },
246
- body: JSON.stringify({ id, name: combo.name, strategy, models: combo.models }),
247
- cache: 'no-store',
248
- })
249
- const data = await response.json() as { ok: boolean; error?: string }
250
- if (data.ok) {
251
- onRefresh()
252
- showToast(`策略已切换为${strategy === 'round-robin' ? '轮询' : '回退'}`)
253
- } else {
254
- showToast(data.error ?? '更新失败')
255
- }
256
- }
257
-
258
236
  return (
259
237
  <div className="dshr-tabBody">
260
238
  {/* Header */}
@@ -262,15 +240,14 @@ export function CombosTab({ combos, aliases, onRefresh }: CombosTabProps): JSX.E
262
240
  <div className="dshr-cardHead">
263
241
  <span className="dshr-cardIcon"><Icon d={I.layers} size={16} /></span>
264
242
  <div className="dshr-cardTitle">组合</div>
265
- <span className="dshr-muted dshr-comboIntroDesc">请求传组合名,在一组模型中按策略命中</span>
243
+ <span className="dshr-muted dshr-comboIntroDesc">请求传组合名,在一组模型中按序降级命中</span>
266
244
  <button type="button" className="dshr-primaryButton dshr-cardAction" onClick={openCreate}>
267
245
  <Icon d={I.add} size={14} />
268
246
  创建组合
269
247
  </button>
270
248
  </div>
271
249
  <div className="dshr-comboIntroBody">
272
- <span className="dshr-comboHeaderKey">回退</span> 按顺序尝试,前面失败时用下一个
273
- <span className="dshr-comboHeaderKey dshr-comboHeaderKey-2">轮询</span> 在模型间轮转分配请求
250
+ <span className="dshr-comboHeaderKey">降级链</span> 按顺序尝试,前面失败时用下一个
274
251
  </div>
275
252
  </section>
276
253
 
@@ -294,15 +271,6 @@ export function CombosTab({ combos, aliases, onRefresh }: CombosTabProps): JSX.E
294
271
  <code className="dshr-comboName">{combo.name}</code>
295
272
  <span className="dshr-muted dshr-cardMeta">{combo.models.length} 模型</span>
296
273
  <div className="dshr-comboOps">
297
- <select
298
- className="dshr-input dshr-comboStrategySelect"
299
- value={combo.strategy}
300
- title="组合策略"
301
- onChange={(e) => void updateStrategy(combo.id, e.target.value as RouterComboStrategy)}
302
- >
303
- <option value="fallback">回退</option>
304
- <option value="round-robin">轮询</option>
305
- </select>
306
274
  <button type="button" className="dshr-iconBtn dshr-iconBtn-sm" title="复制组合名" onClick={() => void copyName(combo.name, combo.id)}>
307
275
  <Icon d={copied === combo.id ? I.check : I.copy} size={15} />
308
276
  </button>
@@ -333,9 +301,7 @@ export function CombosTab({ combos, aliases, onRefresh }: CombosTabProps): JSX.E
333
301
  )}
334
302
  </div>
335
303
  <div className="dshr-muted dshr-reason">
336
- {combo.strategy === 'fallback'
337
- ? '按此顺序尝试模型;当前模型不可用时换下一个。'
338
- : '请求在模型间轮转分配。'}
304
+ 按此顺序尝试模型;当前模型不可用时换下一个。
339
305
  </div>
340
306
  </div>
341
307
  </section>
@@ -349,7 +315,7 @@ export function CombosTab({ combos, aliases, onRefresh }: CombosTabProps): JSX.E
349
315
  <div className="dshr-modalForm">
350
316
  <div className="dshr-comboFormSplit">
351
317
  <div className="dshr-comboFormDivider" aria-hidden="true" />
352
- {/* 左:组合名 + 策略 + 已选模型 */}
318
+ {/* 左:组合名 + 已选模型 */}
353
319
  <div className="dshr-comboFormLeft">
354
320
  <div>
355
321
  <p className="dshr-formLabel">组合名</p>
@@ -362,17 +328,6 @@ export function CombosTab({ combos, aliases, onRefresh }: CombosTabProps): JSX.E
362
328
  />
363
329
  <p className="dshr-formHint">只能含字母、数字、-、_ 和 .</p>
364
330
  </div>
365
- <div>
366
- <p className="dshr-formLabel">策略</p>
367
- <select
368
- className="dshr-input"
369
- value={form.strategy}
370
- onChange={(e) => patchForm({ strategy: e.target.value as RouterComboStrategy })}
371
- >
372
- <option value="fallback">回退 — 按顺序尝试,失败换下一个</option>
373
- <option value="round-robin">轮询 — 在模型间轮转分配</option>
374
- </select>
375
- </div>
376
331
  <div>
377
332
  <p className="dshr-formLabel">模型(已选 {form.models.length})</p>
378
333
  {form.models.length === 0 ? (