dsh-deepseek-web-login 0.6.4 → 0.6.5

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/CHANGELOG.md CHANGED
@@ -2,6 +2,39 @@
2
2
 
3
3
  本项目遵循大致语义化版本;日期为本地时间。
4
4
 
5
+ ## 0.6.5 — 2026-09-27
6
+
7
+ > 修复 0.6.3 的重试策略**形状**错误:它会让「按本地退避重试」算出 `NaN`,把整轮打成 UNKNOWN 错误。
8
+
9
+ ### 修复
10
+
11
+ - **`providerRetryPolicy` 的返回值改成扁平字段**(0.6.3 包了一层 `backoff`)。dsh-llm 的取法是
12
+ `adapter.providerRetryPolicy(p) ?? resolveRetryPolicy(…)` —— 我们一返回对象,右侧那个"会把 `backoff`
13
+ 展开成扁平字段"的规范化就**不会执行**,而运行期的消费方读的正是扁平字段
14
+ (`policy.initialDelayMs / maxDelayMs / jitterRatio`)。实测 DSH 会话日志里 `llm/retry` 事件的
15
+
16
+ ```
17
+ "policyKey":"[\"normal\",5,[\"EMPTY_RESPONSE\",\"RATE_LIMIT\",…],null,null,null]"
18
+ ```
19
+
20
+ 后三项是 `null` = `undefined` —— 两个后果,第二个是致命的:
21
+
22
+ 1. `providerRetryAfterMs > maxDelayMs` 变成 `X > undefined` = **恒 false** ⇒「要等太久就放弃」失效
23
+ (连封禁一天的解除时间也会被照单等下去);
24
+ 2. 失败**不带** provider 延迟时(例如 `EMPTY_RESPONSE`)走本地退避 `initialDelayMs * 2**n`,
25
+ `undefined` ⇒ **`NaN`** ⇒ DSH 写会话事件时拒收非有限数 ⇒ 整轮以
26
+ `UNKNOWN: session event "llm/retry" carries non-JSON-serializable data` 结束。
27
+
28
+ 实测 2026-09-27 17:33:51:一轮里第一次重试(限流,带 2s)正常成功;第二次重试
29
+ (`EMPTY_RESPONSE`,无 provider 延迟)触发该错误 ⇒ 现象就是"任务突然自己停了"。
30
+
31
+ ### 测试
32
+
33
+ - `check-llm-retry.mjs` 补四条**意图级**断言:策略扁平键齐全且**无 `backoff` 嵌套**、
34
+ **回放 DSH 的 `retryPolicyKey` 不许出现 `null`**、**回放 `localDelay` 必须算出有限正数**、
35
+ 超长解除时间仍须被判「等太久」而放弃。
36
+ - 反向验证 3 步全部如期红:改回嵌套 → 6 条红;`maxDelayMs` 调小到 10s → 2 条;抽掉 `initialDelayMs` → 4 条。
37
+
5
38
  ## 0.6.4 — 2026-09-27
6
39
 
7
40
  > 文档版本:把「封号归因」那一节带到 npm 包页面上(纯文档,**无代码改动**)。
package/lib/index.js CHANGED
@@ -1566,7 +1566,7 @@ const THROTTLE_JITTER_RATIO = .3;
1566
1566
  /**
1567
1567
  * 我们**可能**上报给调用方(`dsh-llm-retry`)的**最大**限流退避。
1568
1568
  *
1569
- * 🔴 它必须 ≤ 适配器声明的 `providerRetryPolicy().backoff.maxDelayMs`(见 `adapter.ts`):
1569
+ * 🔴 它必须 ≤ 适配器声明的 `providerRetryPolicy().maxDelayMs`(**扁平字段**,见 `adapter.ts`):
1570
1570
  * dsh-llm 的重试策略在 normal 模式下,一旦「提供方要的延迟 > maxDelayMs」就**直接放弃重试**
1571
1571
  * (`return next()`),整轮随即以 error 结束 —— 表现就是"任务停在限流上,要用户手点继续"。
1572
1572
  *
@@ -5784,13 +5784,41 @@ function allowsAutoContinue(purpose) {
5784
5784
  * 声明之后:提供方要的延迟 ≤ `maxDelayMs` 会被**照单等待并重试**(同一个打开的轮次里重跑
5785
5785
  * 失败步骤,历史保持一致),任务自己接下去 —— 重发时"自动换号"的检查点还会换上可用账号。
5786
5786
  *
5787
+ * ---
5788
+ *
5789
+ * 🔴🔴 **形状必须是「扁平」的,不能包一层 `backoff`**(0.6.3 就栽在这上面,0.6.5 修):
5790
+ *
5791
+ * dsh-llm 的取法是 `adapter.providerRetryPolicy(provider) ?? resolveRetryPolicy(undefined, …)`
5792
+ * —— 我们一返回对象,右边的 `resolveRetryPolicy` **就不会执行**,也就**没人把
5793
+ * `backoff: {…}` 展开成扁平字段**。而运行期的消费方(`dsh-llm-retry`)读的是扁平字段:
5794
+ *
5795
+ * ```js
5796
+ * retryPolicyKey(policy) → [policy.mode, policy.maxRetries, […], policy.initialDelayMs,
5797
+ * policy.maxDelayMs, policy.jitterRatio]
5798
+ * localDelay(config, …) → config.initialDelayMs / config.maxDelayMs / config.jitterRatio
5799
+ * ```
5800
+ *
5801
+ * 写成嵌套时的实况(DSH 会话日志里 `llm/retry` 事件的 `policyKey`):
5802
+ * `["normal",5,[…],null,null,null]` ← 后三项全是 null,即三个字段 **undefined**
5803
+ * ⇒ 两个后果,第二个是致命的:
5804
+ * ① `providerRetryAfterMs > maxDelayMs` 变成 `2000 > undefined` = false ⇒ **"要不要放弃"这条
5805
+ * 判断彻底失效**(长封禁的解除时间也会被照单等下去);
5806
+ * ② 失败**不带** provider 延迟时走 `localDelay()`,`initialDelayMs * 2**n` = `NaN`
5807
+ * ⇒ `delayMs: NaN` ⇒ DSH 写会话事件时校验拒收(`walkJsonValue` 拒绝非有限数)
5808
+ * ⇒ 整轮以 **`UNKNOWN: session event "llm/retry" carries non-JSON-serializable data`** 死掉。
5809
+ * 实测 2026-09-27 17:33:51(用户以为是"模型自己停了")。
5810
+ *
5811
+ * ⇒ 结论:**要么返回扁平策略(本实现),要么干脆返回 `undefined` 走 dsh-llm 自己的默认策略**
5812
+ * (`resolveRetryPolicy` 会替我们规范化)。半吊子的中间形态最危险。
5813
+ * `tests/check-llm-retry.mjs` 守这条:断言扁平键齐全、无 `backoff` 嵌套、且 `localDelay` 算得出有限值。
5814
+ *
5787
5815
  * 取值理由:
5788
5816
  * - `initialDelayMs` = `FAILOVER_RETRY_MS`:本地退避起点与"能换号"那条路一致;
5789
5817
  * - `maxDelayMs` = `MAX_THROTTLE_RETRY_MS`(117s):**必须 ≥ 我们可能上报的最大退避**,
5790
5818
  * 否则又回到"要等太久 ⇒ 放弃"。`tests/check-llm-retry.mjs` 守这个不变量;
5791
5819
  * - `maxRetries` = 5(与 dsh-llm 默认一致):**有界** —— 避免账号真被封时无限打请求。
5792
5820
  * 想更激进可以调大,或改 `mode: 'always'`(无尝试上限);那会在坏账号上一直重试,默认不开;
5793
- * - `retryableCodes` 与 dsh-llm 默认一致(含 `RATE_LIMIT`)—— 我们只抛这几个码。
5821
+ * - `retryableCodes` 与 dsh-llm 默认一致(含 `RATE_LIMIT`、`EMPTY_RESPONSE`)—— 我们只抛这几个码。
5794
5822
  */
5795
5823
  const RETRY_POLICY = Object.freeze({
5796
5824
  mode: "normal",
@@ -5802,11 +5830,9 @@ const RETRY_POLICY = Object.freeze({
5802
5830
  "TIMEOUT",
5803
5831
  "TRANSPORT"
5804
5832
  ]),
5805
- backoff: Object.freeze({
5806
- initialDelayMs: FAILOVER_RETRY_MS,
5807
- maxDelayMs: MAX_THROTTLE_RETRY_MS,
5808
- jitterRatio: .2
5809
- })
5833
+ initialDelayMs: FAILOVER_RETRY_MS,
5834
+ maxDelayMs: MAX_THROTTLE_RETRY_MS,
5835
+ jitterRatio: .2
5810
5836
  });
5811
5837
  /** 构造 deepseek-web 适配器(鸭子类型满足 LlmAdapter 契约,无需继承)。 */
5812
5838
  function createAdapter(deps) {
@@ -8994,7 +9020,7 @@ async function checkForUpdate(current, fetchImpl) {
8994
9020
  * 兜底常量与 package.json 的一致性由 `tests/check-smoke.mjs` 守着,不会漂。
8995
9021
  */
8996
9022
  /** 与 package.json 保持一致的兜底版本(由测试保证不会漂)。 */
8997
- const FALLBACK_VERSION = "0.6.4";
9023
+ const FALLBACK_VERSION = "0.6.5";
8998
9024
  let cached;
8999
9025
  /** 本插件版本(如 `0.1.26`)。 */
9000
9026
  function pluginVersion() {