dsh-router-core 0.3.13 → 0.3.15

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/docs/suppliers.md CHANGED
@@ -14,7 +14,7 @@
14
14
  | | 谁负责 |
15
15
  |---|---|
16
16
  | 组合回退、按策略选中模型 | 核心 |
17
- | **账号池**:选号、冷却、禁用、遍历回退 | 核心 |
17
+ | **账号池**:选号、冷却、遍历回退 | 核心 |
18
18
  | 响应写入(状态行、SSE 头、JSON body) | 核心 |
19
19
  | 模型启用/禁用、自定义模型、别名 | 核心 |
20
20
  | 凭证存储(`env.credentials`) | 核心 |
@@ -75,7 +75,7 @@ js 报告**此刻**观察到的账户状况,而不是把上游原始响应丢给
75
75
  | `ok` | 正常 |
76
76
  | `rate_limit` | 限流(429 类) |
77
77
  | `quota` | 额度/权益不足 |
78
- | `session_dead` | 凭证彻底失效,必须重新登录才能恢复 |
78
+ | `session_dead` | 凭证失效/凭证被上游拒(403 类,含风控误伤) |
79
79
  | `unavailable` | 上游不可用(404 / 服务下线) |
80
80
  | `transport` | 网络/连接层失败(没拿到 HTTP 状态) |
81
81
  | `unknown` | 说不清是什么错 |
@@ -86,11 +86,11 @@ js 报告**此刻**观察到的账户状况,而不是把上游原始响应丢给
86
86
  都报一次,于是一个都选不出来(表现为「组合越用越没号可用」)。所以「这个模型
87
87
  不属于我」一定要报 `no_such_model`,核心才知道该换供应商而不是换账号。
88
88
 
89
- **js 只说「现在怎么了」,不说「该怎么办」。** 冷却多久、是否禁用、要不要换号
89
+ **js 只说「现在怎么了」,不说「该怎么办」。** 冷却多久、要不要换号
90
90
  都是核心的策略(见下),js 不该自己记冷却表——那样每个插件都会长出一份
91
91
  互相不一致的实现。
92
92
 
93
- 核心在 `status()` 之上叠加 `cooling` / `disabled` / `err_count` / `until` / `reason`
93
+ 核心在 `status()` 之上叠加 `cooling` / `err_count` / `until` / `reason`
94
94
  再给面板,所以 js 不用(也不该)填这些字段。`accounts` 为空数组 = 无账号供应商。
95
95
  注意 `err_count` 现在是**限流退避等级**(反复被限流的号等级递增),不是
96
96
  「连续失败次数」——面板按等级展示,等级在成功后清零。
@@ -101,7 +101,7 @@ js 只要知道这一条:**报 `rate_limit`/`transport`/`unknown` 只影响「
101
101
  模型」**,不会牵连该号上的其它模型。一个连接服务多个模型时,模型 A 被限流不该
102
102
  让模型 B 也不可用。
103
103
 
104
- 例外是 `session_dead`:凭证失效是连接级问题,该号**所有模型**都停用。
104
+ 例外是 `session_dead`:凭证失效是连接级问题,该号**所有模型**都停用(连接级冷却,到期自愈)。
105
105
 
106
106
  ## `chatOnce(uid, req)`:一次只服务一个账号
107
107
 
@@ -124,7 +124,7 @@ type ChatOnceResult =
124
124
  |---|---|
125
125
  | `rate_limit` | **指数退避**:首次 2s,每次翻倍,上限 5 分钟;成功后等级清零 |
126
126
  | `quota` | 冷却 10 分钟 |
127
- | `session_dead` | **永久禁用该号**(所有模型都停用,需重新登录才能恢复) |
127
+ | `session_dead` | **连接级冷却 30 分钟**(该号所有模型都停用,到期自愈;没有永久禁用——403 分不清凭证死活与风控误伤) |
128
128
  | `unavailable` / `transport` / `unknown` | 瞬时短冷却 30s(**每次都冷**,不攒次数) |
129
129
  | `no_such_model` | **不惩罚账号**,核心直接跳过整个供应商换下一个 |
130
130
 
@@ -137,7 +137,7 @@ type ChatOnceResult =
137
137
  「请求参数非法」「上游临时不可用」)建议归 `rate_limit`——瞬时/请求类的错,
138
138
  短冷换号比长冷划算。
139
139
 
140
- 冷却/禁用/退避等级都是**内存态**,重启归零(与凭证、积分的持久化不同)。
140
+ 冷却/退避等级都是**内存态**,重启归零(与凭证、积分的持久化不同)。
141
141
  重启后所有号都会重新被选中一次,所以**上游确实挂了的号会各撞一次**,属预期。
142
142
  要跨重启保留,届时给账号池加一个落盘 state 文件即可,接口不用变。
143
143
 
@@ -274,7 +274,7 @@ export const id = 'echo'
274
274
  export const name = 'Echo'
275
275
  export const priority = 10
276
276
 
277
- // 只报「现在状态」:冷却/禁用/退避等级由核心叠加,积分由核心持久化
277
+ // 只报「现在状态」:冷却/退避等级由核心叠加,积分由核心持久化
278
278
  // credits: -1 = 不支持/没拉到(别写 0,0 会被当成「用完了」存下来)
279
279
  const accounts = [{ uid: 'echo-1', nickname: 'Echo 1', credits: -1, state: 'ok' }]
280
280
 
@@ -116,10 +116,6 @@ window.__ModuleLoader__.load({
116
116
  return `剩余 ${Math.ceil(minutes / 60)} 小时`;
117
117
  }
118
118
  function statusBadge(account) {
119
- if (account.disabled) return {
120
- text: "已禁用",
121
- cls: "dshr-badge-danger"
122
- };
123
119
  if (account.cooling) return {
124
120
  text: "冷却中",
125
121
  cls: "dshr-badge-warn"
@@ -768,7 +764,7 @@ window.__ModuleLoader__.load({
768
764
  className: "dshr-linkList",
769
765
  children: orderedAccounts.map((account, index) => {
770
766
  const badge = statusBadge(account);
771
- const coolingText = account.cooling || account.disabled ? `${account.reason ?? ""}${untilLabel(account) !== "" ? ` · ${untilLabel(account)}` : ""}` : "";
767
+ const coolingText = account.cooling ? `${account.reason ?? ""}${untilLabel(account) !== "" ? ` · ${untilLabel(account)}` : ""}` : "";
772
768
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
773
769
  className: `dshr-linkRow${dragIndex === index ? " dshr-linkRowDragging" : ""}`,
774
770
  draggable: renameUid !== account.uid,
@@ -3585,7 +3581,7 @@ window.__ModuleLoader__.load({
3585
3581
  className: "dshr-supplierGrid",
3586
3582
  children: group.map((supplier) => {
3587
3583
  const supplierAccounts = accounts.filter((a) => a.supplier === supplier.id);
3588
- const healthy = supplierAccounts.filter((a) => !a.cooling && !a.disabled).length;
3584
+ const healthy = supplierAccounts.filter((a) => !a.cooling).length;
3589
3585
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3590
3586
  className: "dshr-supplierCard",
3591
3587
  onClick: () => setDetailSupplier(supplier),
@@ -4115,6 +4111,60 @@ window.__ModuleLoader__.load({
4115
4111
  };
4116
4112
  }
4117
4113
  //#endregion
4114
+ //#region src/client/model-hint-copy.ts
4115
+ /**
4116
+ * 设置-模型 → Router 卡片:把宿主那句走不通的提示换成正确说法。
4117
+ *
4118
+ * 为什么不能直接改源头:Router 的卡片走宿主的 "unknown" 布局(宿主只认得
4119
+ * llm-deepseek / llm-pi-ai 两套表单),那句
4120
+ * 「其余字段在 settings.yaml 中,请直接编辑对应段。 (llm-dsh-router)」
4121
+ * 属于宿主 `settings.models` 的 locale 字典,而字典注册对同一个 (ns, locale)
4122
+ * 只允许一个占位者(重复注册直接抛),插件覆写不了,契约也没给可覆写字段。
4123
+ * 所以沿用本插件既有的认领手法(同 settings-nav-icon.ts):按文案指纹认出宿主
4124
+ * 渲染的那一段,就地改写。
4125
+ *
4126
+ * 只写 `nodeValue`、不增删节点(节点归 React,摘掉会让它的引用落空)。React 重渲染
4127
+ * 覆盖回来时观察器会再改一次;已是目标文案就早退,不会自激。
4128
+ *
4129
+ * 升级路径:宿主若给该提示长出可覆写字段(或 Router 有了专属布局),删掉本文件。
4130
+ */
4131
+ /** 指纹:语言无关(不受中英文切换影响),且只出现在 Router 卡片上。 */
4132
+ var FINGERPRINT = "(llm-dsh-router)";
4133
+ /** 正确说法:对齐 README「组合即模型」。 */
4134
+ var ROUTER_MODEL_HINT_COPY = "组合即模型 · 建好的组合会自动出现在 DSH 模型目录中,选中组合名即可直接使用。";
4135
+ /**
4136
+ * 这段文本要不要改写。
4137
+ * @param text 段落当前文本
4138
+ * @returns 需要改写时给出目标文案,否则 undefined
4139
+ */
4140
+ function routerHintRewrite(text) {
4141
+ if (!text.includes(FINGERPRINT)) return void 0;
4142
+ if (text === "组合即模型 · 建好的组合会自动出现在 DSH 模型目录中,选中组合名即可直接使用。") return void 0;
4143
+ return ROUTER_MODEL_HINT_COPY;
4144
+ }
4145
+ /**
4146
+ * 持续把 Router 卡片里的宿主提示改写成 {@link ROUTER_MODEL_HINT_COPY}。
4147
+ * @returns 清理函数:断开观察
4148
+ */
4149
+ function rewriteRouterModelHint() {
4150
+ const sync = () => {
4151
+ for (const el of document.querySelectorAll("p[class*=\"advancedHint\"]")) {
4152
+ const next = routerHintRewrite(el.textContent ?? "");
4153
+ const node = el.firstChild;
4154
+ if (next === void 0 || node?.nodeType !== Node.TEXT_NODE) continue;
4155
+ node.nodeValue = next;
4156
+ }
4157
+ };
4158
+ sync();
4159
+ const observer = new MutationObserver(sync);
4160
+ observer.observe(document.body, {
4161
+ childList: true,
4162
+ subtree: true,
4163
+ characterData: true
4164
+ });
4165
+ return () => observer.disconnect();
4166
+ }
4167
+ //#endregion
4118
4168
  //#region src/client/index.tsx
4119
4169
  /**
4120
4170
  * Client half of dsh-router.
@@ -4157,6 +4207,7 @@ window.__ModuleLoader__.load({
4157
4207
  }
4158
4208
  ctx.effect(() => disposeSettings, "dsh-router: settings section");
4159
4209
  ctx.effect(() => registerSettingsNavIcon(SECTION_LABEL), "dsh-router: settings nav icon");
4210
+ ctx.effect(() => rewriteRouterModelHint(), "dsh-router: models provider hint copy");
4160
4211
  }
4161
4212
  //#endregion
4162
4213
  exports.apply = apply;
package/lib/client.js CHANGED
@@ -116,10 +116,6 @@ window.__ModuleLoader__.load({
116
116
  return `剩余 ${Math.ceil(minutes / 60)} 小时`;
117
117
  }
118
118
  function statusBadge(account) {
119
- if (account.disabled) return {
120
- text: "已禁用",
121
- cls: "dshr-badge-danger"
122
- };
123
119
  if (account.cooling) return {
124
120
  text: "冷却中",
125
121
  cls: "dshr-badge-warn"
@@ -768,7 +764,7 @@ window.__ModuleLoader__.load({
768
764
  className: "dshr-linkList",
769
765
  children: orderedAccounts.map((account, index) => {
770
766
  const badge = statusBadge(account);
771
- const coolingText = account.cooling || account.disabled ? `${account.reason ?? ""}${untilLabel(account) !== "" ? ` · ${untilLabel(account)}` : ""}` : "";
767
+ const coolingText = account.cooling ? `${account.reason ?? ""}${untilLabel(account) !== "" ? ` · ${untilLabel(account)}` : ""}` : "";
772
768
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
773
769
  className: `dshr-linkRow${dragIndex === index ? " dshr-linkRowDragging" : ""}`,
774
770
  draggable: renameUid !== account.uid,
@@ -3585,7 +3581,7 @@ window.__ModuleLoader__.load({
3585
3581
  className: "dshr-supplierGrid",
3586
3582
  children: group.map((supplier) => {
3587
3583
  const supplierAccounts = accounts.filter((a) => a.supplier === supplier.id);
3588
- const healthy = supplierAccounts.filter((a) => !a.cooling && !a.disabled).length;
3584
+ const healthy = supplierAccounts.filter((a) => !a.cooling).length;
3589
3585
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3590
3586
  className: "dshr-supplierCard",
3591
3587
  onClick: () => setDetailSupplier(supplier),
@@ -4115,6 +4111,60 @@ window.__ModuleLoader__.load({
4115
4111
  };
4116
4112
  }
4117
4113
  //#endregion
4114
+ //#region src/client/model-hint-copy.ts
4115
+ /**
4116
+ * 设置-模型 → Router 卡片:把宿主那句走不通的提示换成正确说法。
4117
+ *
4118
+ * 为什么不能直接改源头:Router 的卡片走宿主的 "unknown" 布局(宿主只认得
4119
+ * llm-deepseek / llm-pi-ai 两套表单),那句
4120
+ * 「其余字段在 settings.yaml 中,请直接编辑对应段。 (llm-dsh-router)」
4121
+ * 属于宿主 `settings.models` 的 locale 字典,而字典注册对同一个 (ns, locale)
4122
+ * 只允许一个占位者(重复注册直接抛),插件覆写不了,契约也没给可覆写字段。
4123
+ * 所以沿用本插件既有的认领手法(同 settings-nav-icon.ts):按文案指纹认出宿主
4124
+ * 渲染的那一段,就地改写。
4125
+ *
4126
+ * 只写 `nodeValue`、不增删节点(节点归 React,摘掉会让它的引用落空)。React 重渲染
4127
+ * 覆盖回来时观察器会再改一次;已是目标文案就早退,不会自激。
4128
+ *
4129
+ * 升级路径:宿主若给该提示长出可覆写字段(或 Router 有了专属布局),删掉本文件。
4130
+ */
4131
+ /** 指纹:语言无关(不受中英文切换影响),且只出现在 Router 卡片上。 */
4132
+ var FINGERPRINT = "(llm-dsh-router)";
4133
+ /** 正确说法:对齐 README「组合即模型」。 */
4134
+ var ROUTER_MODEL_HINT_COPY = "组合即模型 · 建好的组合会自动出现在 DSH 模型目录中,选中组合名即可直接使用。";
4135
+ /**
4136
+ * 这段文本要不要改写。
4137
+ * @param text 段落当前文本
4138
+ * @returns 需要改写时给出目标文案,否则 undefined
4139
+ */
4140
+ function routerHintRewrite(text) {
4141
+ if (!text.includes(FINGERPRINT)) return void 0;
4142
+ if (text === "组合即模型 · 建好的组合会自动出现在 DSH 模型目录中,选中组合名即可直接使用。") return void 0;
4143
+ return ROUTER_MODEL_HINT_COPY;
4144
+ }
4145
+ /**
4146
+ * 持续把 Router 卡片里的宿主提示改写成 {@link ROUTER_MODEL_HINT_COPY}。
4147
+ * @returns 清理函数:断开观察
4148
+ */
4149
+ function rewriteRouterModelHint() {
4150
+ const sync = () => {
4151
+ for (const el of document.querySelectorAll("p[class*=\"advancedHint\"]")) {
4152
+ const next = routerHintRewrite(el.textContent ?? "");
4153
+ const node = el.firstChild;
4154
+ if (next === void 0 || node?.nodeType !== Node.TEXT_NODE) continue;
4155
+ node.nodeValue = next;
4156
+ }
4157
+ };
4158
+ sync();
4159
+ const observer = new MutationObserver(sync);
4160
+ observer.observe(document.body, {
4161
+ childList: true,
4162
+ subtree: true,
4163
+ characterData: true
4164
+ });
4165
+ return () => observer.disconnect();
4166
+ }
4167
+ //#endregion
4118
4168
  //#region src/client/index.tsx
4119
4169
  /**
4120
4170
  * Client half of dsh-router.
@@ -4157,6 +4207,7 @@ window.__ModuleLoader__.load({
4157
4207
  }
4158
4208
  ctx.effect(() => disposeSettings, "dsh-router: settings section");
4159
4209
  ctx.effect(() => registerSettingsNavIcon(SECTION_LABEL), "dsh-router: settings nav icon");
4210
+ ctx.effect(() => rewriteRouterModelHint(), "dsh-router: models provider hint copy");
4160
4211
  }
4161
4212
  //#endregion
4162
4213
  exports.apply = apply;