dsh-connect-trae 2.7.0 → 2.7.2

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
@@ -1,5 +1,53 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.7.2 (2026-10-05)
4
+
5
+ > **补完 #23 的确定性**:2.7.1 修好了 Max 档随机丢失,但**在 max 相等时仍保留了先到者**,于是 **dev 基础窗口**还会随货架顺序漂移。报告人 JiewiW 在 Windows 复测时发现并给了修法(`glm-5.2`:`chat_v3` 的 dev=116000 与 `solo_agent_remote` 的 dev=200000 都带 max=1000000,谁先到就取谁)。
6
+
7
+ ### Bug Fixes
8
+
9
+ - **择强规则改为逐级比较到底(字典序),不再在「max 相等」处停止**(`preferStrongerRow`):
10
+ - ① 带 Max 的行优先 → ② Max 更大者胜 → ③ **max 相等(或都无 Max)时比 dev,取更宽者** → ④ 完全相等才保留先到者。
11
+ - **为什么第 ③ 步不是多余的**:2.7.1 在 max 相等时直接保留先到者,导致**未配置预算的模型**其默认窗口仍是一次抛硬币——本机 CN 30 个模型里有 9 个如此(`glm-5.2` 就是其一)。配了 1M 预算时最终仍恒为 1M,所以用户不易察觉;但「默认窗口」决定 DSH 的显示与压缩基准,正是 #23 要消除的同一类缺陷。
12
+ - **实测(本机 CN,连刷 8 次)**:修复后**整目录指纹 8/8 完全一致**(含每个模型的 dev 与 max,30 个模型);`glm-5.2` 稳定为 `dev=200000 / max=1000000`。修复前同一份数据的两种货架顺序分别得到 dev=116000 与 dev=200000。
13
+
14
+ ### Tests
15
+
16
+ - **全仓 417 → 419**:新增 2 例(max 相等时按 dev 取宽、且与顺序无关;带 Max 的行仍胜过 dev 更宽的无 Max 行)。同时把原有的「6 种货架排列」用例的**数据改成「max 相等但 dev 不等」**——正是报告人指出的形态,断言全部排列落到同一个 dev;这条用例此前只覆盖了 dev 相同的情形,所以没能发现该问题。
17
+ - **变异验证 2 次全部被抓**:把规则退回「max 相等即保留先到者」→ 2 例失败;去掉「带 Max 优先于 dev 更宽」→ 2 例失败。
18
+
19
+ ### 致谢
20
+
21
+ 报告人 **JiewiW** 在 Windows 上完成了三项预期验证,并额外指出 `preferStrongerRow` 在 max 相等时的残留不确定性,连修法(「在 max 相等时继续落到 dev 取更宽」)一并给出——本版照此实现。
22
+
23
+ ## 2.7.1 (2026-10-05)
24
+
25
+ > **修复「Max 档随机丢失、同一模型上下文窗口在多次刷新间漂移」**([#23](https://github.com/dingminhua/dsh-connect-trae/issues/23),报告人 JiewiW)。
26
+ >
27
+ > 这是 **2.6.0 引入/放大的缺陷**:那一版把发现侧从 2 个货架扩到 7 个,却保留了「先到先得」去重——而同一个模型在多个货架里的**容量并不相同**,货架顺序又**每次调用都不同**。结果同一模型这次是 `116000`、下次是 `256000`、再下次才带 1M Max,用户配置的 1M 预算被随机忽略。
28
+
29
+ ### Bug Fixes
30
+
31
+ - **跨货架去重改为「保留容量最强的行」**(`TraeSoloRemoteCatalogClient.fetchModels`):
32
+ - **根因**:remote `/models` 按 function 分组,同一模型 id 出现在多个组里,各组报告不同容量。实测(本机 CN 凭证,2026-10-05)同一次响应里 `Doubao-Seed-2.1-Pro` 的各组值:`builder_v3` → `116000`/无 Max;`solo_work_remote` / `solo_work_lite` → `256000`/无 Max;`chat_v3` / `solo_agent` / `solo_agent_remote` → `256000`/**`1000000`**(`max_mode: true`)。
33
+ - **为什么旧写法必错**:原来的去重是 `seen` Set 的「第一次遇到就锁定」,而我当时的代码注释还写着「保持网关自己的组顺序稳定」——**那个假设是错的**:连打 5 次实测得到 **5 种不同的组顺序**。于是容量的发布值取决于哪一组恰好先回来。`applyContextBudgets` 只能在 `maxContextWindow` 存在时升窗,所以丢失该字段会让配置了 1M 的模型被静默压到 256K 或更低,DSH 因此在远早于用户设定的阈值处反复压缩。
34
+ - **修法**:按 ① 带 Max 档优先 ② 都有 Max 取更大 ③ 都无 Max 取更宽的 dev 窗口 的顺序择强;平手保留先到者(模型顺序仍由网关决定)。同一 id 的各行在 `multimodal` / `reasoning` / 积分倍率上完全一致(同日实测逐字段核对),因此按容量择强不会丢失能力信息。
35
+ - **实测对比**(本机 CN,连打 6 次实时目录,同一预算配置):
36
+ | | 修复前 | 修复后 |
37
+ | --- | --- | --- |
38
+ | `Doubao-Seed-2.1-Pro` 的取值 | `116000/无`、`256000/1000000`、`256000/无` **三种漂移** | 6/6 稳定 **`256000/1000000`** |
39
+ | 整目录带 Max 的模型数 | 11 → 26 **随机** | 6/6 稳定 **26** |
40
+
41
+ ### Tests
42
+
43
+ - **全仓 413 → 417**:新增 4 例(`preferStrongerRow` 的三条择强分支 + 平手保留先到;以及**同一组数据在全部 6 种货架排列下产出完全相同的目录**——这条直接锁住了「结果不得依赖网关顺序」)。
44
+ - **变异验证 3 次全部被抓**:改回「先到先得」→ 1 例失败;去掉 Max 档优先 → 2 例失败;把平手规则从 `>` 改成 `>=`(后到者胜)→ 1 例失败。
45
+ - 其中第 3 次最初**没被抓到**:我原来的平手用例只覆盖了「都无 Max」的情况,没覆盖「两个相等的 Max」。补齐用例后才抓住——这本身说明那条断言当时的覆盖是假的。
46
+
47
+ ### 致谢
48
+
49
+ 报告人 **JiewiW** 给出的定位(跨组容量不一致 + 组顺序不稳定)与实测数据表完全正确,并附了可直接套用的补丁思路;本版按其方向实现,并补上了「结果与顺序无关」的回归测试。
50
+
3
51
  ## 2.7.0 (2026-10-02)
4
52
 
5
53
  > **新增「一键测试启用模型」**:给每个已启用的模型发一条最小消息,看哪些**真的能应答**。
package/lib/index.js CHANGED
@@ -2851,6 +2851,40 @@ const TRAE_REMOTE_DIRECTORY_FUNCTIONS = {
2851
2851
  ]
2852
2852
  };
2853
2853
  /**
2854
+ * Of two rows for the SAME model id, the one that advertises the stronger
2855
+ * capacity (issue #23).
2856
+ *
2857
+ * The gateway groups its directory by function and reports a model's capacity
2858
+ * per group, with the group order varying between calls. Keeping the strongest
2859
+ * row makes the published window deterministic. The comparison is
2860
+ * LEXICOGRAPHIC and must be applied all the way down, or ordering leaks back in
2861
+ * at the next field:
2862
+ *
2863
+ * 1. a row with a Max tier beats one without (`maxContextWindow` is only set
2864
+ * when the group reported `max_mode: true` with a positive max);
2865
+ * 2. two Max rows: the larger max wins;
2866
+ * 3. max equal (or absent on both): the WIDER dev window wins;
2867
+ * 4. both equal: keep the incumbent, so the model order stays the gateway's.
2868
+ *
2869
+ * Step 3 is not an afterthought. With equal `max` the first version of this
2870
+ * function stopped and kept the incumbent, which left the dev window decided by
2871
+ * arrival order — measured 2026-10-05: `glm-5.2` came back as `dev=116000` when
2872
+ * `chat_v3` won and `dev=200000` when `solo_agent_remote` did, both with
2873
+ * `max=1000000` (9 of 30 CN models behaved that way, reported by JiewiW on
2874
+ * Windows). A budgeted model still resolved to 1M either way, but the DEFAULT
2875
+ * window of an unbudgeted model — what DSH shows and compresses against — was
2876
+ * a coin flip, which is the same defect this function exists to remove.
2877
+ */
2878
+ function preferStrongerRow(candidate, incumbent) {
2879
+ if (incumbent === void 0) return candidate;
2880
+ const candidateMax = candidate.maxContextWindow;
2881
+ const incumbentMax = incumbent.maxContextWindow;
2882
+ if (candidateMax !== void 0 && incumbentMax === void 0) return candidate;
2883
+ if (candidateMax === void 0 && incumbentMax !== void 0) return incumbent;
2884
+ if (candidateMax !== void 0 && incumbentMax !== void 0 && candidateMax !== incumbentMax) return candidateMax > incumbentMax ? candidate : incumbent;
2885
+ return (candidate.contextWindow ?? 0) > (incumbent.contextWindow ?? 0) ? candidate : incumbent;
2886
+ }
2887
+ /**
2854
2888
  * Region-scoped request dressing. The CN portal is `solo.trae.cn` with the
2855
2889
  * CN locale headers; the international directory lives on the shared
2856
2890
  * `coresg-normal.trae.ai` gateway and was verified (2026-09-15) with the
@@ -2907,14 +2941,13 @@ var TraeSoloRemoteCatalogClient = class {
2907
2941
  });
2908
2942
  if (!response.ok) throw new Error(`SOLO remote models returned HTTP ${response.status}`);
2909
2943
  const groups = (await response.json()).data?.list ?? [];
2910
- const seen = /* @__PURE__ */ new Set();
2911
- const models = [];
2944
+ const byId = /* @__PURE__ */ new Map();
2912
2945
  for (const group of groups) for (const raw of group.models ?? []) {
2913
2946
  const model = parseTraeRemoteModel(raw);
2914
- if (model === void 0 || seen.has(model.id)) continue;
2915
- seen.add(model.id);
2916
- models.push(model);
2947
+ if (model === void 0) continue;
2948
+ byId.set(model.id, preferStrongerRow(model, byId.get(model.id)));
2917
2949
  }
2950
+ const models = [...byId.values()];
2918
2951
  if (models.length === 0) throw new Error("SOLO remote models response contained no models");
2919
2952
  return models;
2920
2953
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-connect-trae",
3
3
  "displayName": "DSH Connect Trae",
4
4
  "description": "把本机登录的 Trae 模型接入 DeepSeek Harness:国内版与国际版双供应商并行,提供用量/积分概览与每日签到领取。",
5
- "version": "2.7.0",
5
+ "version": "2.7.2",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",