dsh-connect-trae 4.6.3 → 4.6.4

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,51 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.6.4 (2026-10-28)
4
+
5
+ > **缓存命中恒为 0% 不是插件丢了数字,是上游没有分配缓存池。** Trae 的
6
+ > `metadata` 事件里 `prompt_cache_pool_record` 非空才代表这次请求挂了前缀
7
+ > 缓存池;实测 CN/AI 双区、4 个账号、7 个模型、4 种 function,该字段
8
+ > **100% 为 `null`**。没有池,`cache_read_input_tokens` 只能是 0 —— 0% 是
9
+ > 正确读数。本版把这个事实变成可观测的,用户不必再怀疑插件吞了数据。
10
+
11
+ ### 修复
12
+
13
+ - **上游的缓存池事实与管线名不再被丢弃**(#67)。
14
+ - **现象**:无论简单还是复杂提问,缓存命中恒为 0%,十个会话一致;
15
+ 同时 tps 在 60–80 之间浮动。
16
+ - **根因**:不是插件。issue #10 的转发修复仍在(`lib/index.js` 含
17
+ `cached_tokens`,同版本下 `workbuddy`/`dsh-opencode-go` 命中显示正常),
18
+ 是上游 `llm_utils_chat` 改由 `llm_raw_chat_v2` 管线、火山方舟 `ep-…`
19
+ 端点承载后,不再为这些请求分配前缀缓存池。当初产出 5248/9216 的
20
+ `scripts/probe-cache-convention.mjs` 原样重跑,现在三轮全是 0。
21
+ - **修法**:`sse.ts` 新增 `diagnostic` 事件类型,解码 `metadata`
22
+ (`prompt_cache_pool_record`)与 `timing_cost`(`name` 管线名、
23
+ `provider_model_name`);`solo-bridge.ts` 通过可选 `log` 上报
24
+ `promptCachePoolRecord` / `pipeline` / `providerModelName`;
25
+ `index.ts` 四个 `TraeSoloBridge` 构造点接入 `ctx.logger.warn`。
26
+ 诊断事件不进答案文本、不改 usage 映射,**对外行为不变**。
27
+ - **为什么区分 `null` 与「字段不存在」**:`null` 是上游明确表示「没挂池」,
28
+ 字段缺失只是没这条信息;两者混为一谈就又回到「看不出是谁的问题」。
29
+
30
+ ### 修复(顺带守住一个回归)
31
+
32
+ - **`metadata` 不能被 peek 当成「上游已应答」**。它现在是被认识的事件类型,
33
+ 若据此停止 peek,紧跟其后的拒绝(如 `1005` 未订阅)会退化成截断的 200 流
34
+ —— 正是 issue #19 的故障形态。`openTraeSource` 显式把 `diagnostic` 排除在
35
+ 「已应答」之外。
36
+
37
+ ### 测试
38
+
39
+ - `tests/sse.spec.ts`:`metadata` / `timing_cost` 解码为 `diagnostic`,
40
+ 保留 `prompt_cache_pool_record: null`,且区分字段缺失。
41
+ - `tests/solo-bridge.spec.ts`:诊断事实上报(含 `null` 原样保留、事件不泄漏进
42
+ 答案);`metadata` 之后紧跟的 `1005` 仍转成 402 `hard_credit`。
43
+
44
+ ### 说明
45
+
46
+ - 诊断细节与完整证据链见 `docs/ISSUE67_DIAGNOSIS.md`(不随 npm 包发布)。
47
+ - 上游若开始分配缓存池,`cached_tokens` 会自动恢复显示,插件侧无需再改。
48
+
3
49
  ## 4.6.3 (2026-10-27)
4
50
 
5
51
  > **国际版美国区账号不会再被当成新加坡区。** 它的凭据明明写着
package/lib/index.d.ts CHANGED
@@ -3426,6 +3426,29 @@ type TraeStreamEvent = {
3426
3426
  */
3427
3427
  cacheReadTokens?: number;
3428
3428
  cacheWriteTokens?: number;
3429
+ } | {
3430
+ /**
3431
+ * The upstream's own account of how the request was served: `metadata`
3432
+ * (acceptance, plus `prompt_cache_pool_record`) and `timing_cost` (the
3433
+ * pipeline and the backing provider model).
3434
+ *
3435
+ * These events were previously folded into `unknown` and dropped. That
3436
+ * mattered for issue #67: `prompt_cache_pool_record` is the ONLY upstream
3437
+ * signal that says whether a prompt-cache pool was even attached to the
3438
+ * request, so with it dropped a "0% cache" report could not be told apart
3439
+ * from a plugin that lost the numbers. See docs/ISSUE67_DIAGNOSIS.md.
3440
+ */
3441
+ type: 'diagnostic';
3442
+ /**
3443
+ * `metadata.prompt_cache_pool_record`. `null` is a real, observed value
3444
+ * (100% of measured requests on 2026-10-28) and means the upstream attached
3445
+ * no cache pool; it is kept distinct from "the field was absent".
3446
+ */
3447
+ promptCachePoolRecord?: unknown;
3448
+ /** `timing_cost.name`, e.g. `llm_raw_chat_v2`. */
3449
+ pipeline?: string;
3450
+ /** `timing_cost.provider_model_name` — the model that actually answered. */
3451
+ providerModelName?: string;
3429
3452
  } | {
3430
3453
  type: 'done';
3431
3454
  finishReason: string;
@@ -3569,7 +3592,21 @@ declare class TraeSoloBridge implements TraeUpstreamClient {
3569
3592
  private readonly upstream;
3570
3593
  private readonly catalog?;
3571
3594
  private readonly wireResolver?;
3572
- constructor(upstream: TraeUpstreamClient, catalog?: Pick<TraeCatalog, "current"> | undefined, wireResolver?: TraeWireResolver | undefined);
3595
+ /**
3596
+ * Optional sink for the upstream's own diagnostics (`metadata` /
3597
+ * `timing_cost`). Issue #67: without it, `prompt_cache_pool_record: null`
3598
+ * was invisible, so "the upstream attached no cache pool" and "the plugin
3599
+ * lost the numbers" looked identical to a user reading a 0% cache rate.
3600
+ */
3601
+ private readonly log?;
3602
+ constructor(upstream: TraeUpstreamClient, catalog?: Pick<TraeCatalog, "current"> | undefined, wireResolver?: TraeWireResolver | undefined,
3603
+ /**
3604
+ * Optional sink for the upstream's own diagnostics (`metadata` /
3605
+ * `timing_cost`). Issue #67: without it, `prompt_cache_pool_record: null`
3606
+ * was invisible, so "the upstream attached no cache pool" and "the plugin
3607
+ * lost the numbers" looked identical to a user reading a 0% cache rate.
3608
+ */
3609
+ log?: ((message: string, detail?: unknown) => void) | undefined);
3573
3610
  chatStream(bodyJson: string, signal?: AbortSignal): Promise<TraeChatResult>;
3574
3611
  }
3575
3612
  //#endregion
package/lib/index.js CHANGED
@@ -5581,6 +5581,12 @@ function decodeTraeEvent(event) {
5581
5581
  ...typeof record["cache_read_input_tokens"] === "number" ? { cacheReadTokens: record["cache_read_input_tokens"] } : {},
5582
5582
  ...typeof record["cache_creation_input_tokens"] === "number" ? { cacheWriteTokens: record["cache_creation_input_tokens"] } : {}
5583
5583
  };
5584
+ if (event.event === "metadata" || event.event === "timing_cost") return {
5585
+ type: "diagnostic",
5586
+ ...record["prompt_cache_pool_record"] === void 0 ? {} : { promptCachePoolRecord: record["prompt_cache_pool_record"] },
5587
+ ...typeof record["name"] === "string" ? { pipeline: record["name"] } : {},
5588
+ ...typeof record["provider_model_name"] === "string" ? { providerModelName: record["provider_model_name"] } : {}
5589
+ };
5584
5590
  if (event.event === "done" || typeof record["finish_reason"] === "string" && record["response"] === void 0) return {
5585
5591
  type: "done",
5586
5592
  finishReason: typeof record["finish_reason"] === "string" ? record["finish_reason"] : "stop"
@@ -5747,7 +5753,7 @@ function bridgeTraeSoloStream(response, model) {
5747
5753
  return bridgeTraeSource(source, model);
5748
5754
  }
5749
5755
  /** {@link bridgeTraeSoloStream} over an already-opened byte stream. */
5750
- function bridgeTraeSource(source, model) {
5756
+ function bridgeTraeSource(source, model, log) {
5751
5757
  const id = `chatcmpl-${randomUUID().replaceAll("-", "").slice(0, 24)}`;
5752
5758
  const created = Math.floor(Date.now() / 1e3);
5753
5759
  const decoder = new TextDecoder();
@@ -5780,6 +5786,15 @@ function bridgeTraeSource(source, model) {
5780
5786
  if (failure !== void 0) upstreamError = new Error(describeTraeFailure(failure));
5781
5787
  return;
5782
5788
  }
5789
+ if (decoded.type === "diagnostic") {
5790
+ if (decoded.promptCachePoolRecord !== void 0 || decoded.pipeline !== void 0) log?.("dsh-connect-trae: upstream request diagnostics", {
5791
+ model,
5792
+ promptCachePoolRecord: decoded.promptCachePoolRecord,
5793
+ pipeline: decoded.pipeline,
5794
+ providerModelName: decoded.providerModelName
5795
+ });
5796
+ return;
5797
+ }
5783
5798
  if (decoded.type === "delta") {
5784
5799
  const delta = {};
5785
5800
  if (decoded.text !== "") delta["content"] = decoded.text;
@@ -5932,7 +5947,7 @@ async function openTraeSource(response) {
5932
5947
  message: describeTraeFailure(failure)
5933
5948
  };
5934
5949
  }
5935
- if (decoded.type !== "unknown") answered = true;
5950
+ if (decoded.type !== "unknown" && decoded.type !== "diagnostic") answered = true;
5936
5951
  }
5937
5952
  if (answered) break;
5938
5953
  }
@@ -5956,12 +5971,12 @@ async function openTraeSource(response) {
5956
5971
  * The sibling {@link bridgeTraeSoloStream} stays synchronous and always answers
5957
5972
  * 200; it is the primitive, and this is the policy the bridge class uses.
5958
5973
  */
5959
- async function bridgeTraeSoloStreamOrFail(response, model) {
5974
+ async function bridgeTraeSoloStreamOrFail(response, model, log) {
5960
5975
  const opened = await openTraeSource(response);
5961
5976
  if (!opened.ok) return opened;
5962
5977
  return {
5963
5978
  ok: true,
5964
- response: bridgeTraeSource(opened.source, model)
5979
+ response: bridgeTraeSource(opened.source, model, log)
5965
5980
  };
5966
5981
  }
5967
5982
  /**
@@ -6002,10 +6017,12 @@ var TraeSoloBridge = class {
6002
6017
  upstream;
6003
6018
  catalog;
6004
6019
  wireResolver;
6005
- constructor(upstream, catalog, wireResolver) {
6020
+ log;
6021
+ constructor(upstream, catalog, wireResolver, log) {
6006
6022
  this.upstream = upstream;
6007
6023
  this.catalog = catalog;
6008
6024
  this.wireResolver = wireResolver;
6025
+ this.log = log;
6009
6026
  }
6010
6027
  async chatStream(bodyJson, signal) {
6011
6028
  let model = "glm-5.2";
@@ -6093,7 +6110,7 @@ var TraeSoloBridge = class {
6093
6110
  }
6094
6111
  const result = await this.upstream.chatStream(prepared, signal);
6095
6112
  if (!result.ok) return result;
6096
- return await bridgeTraeSoloStreamOrFail(result.response, model);
6113
+ return await bridgeTraeSoloStreamOrFail(result.response, model, this.log);
6097
6114
  }
6098
6115
  };
6099
6116
  //#endregion
@@ -11189,7 +11206,7 @@ function apply(ctx, config) {
11189
11206
  route,
11190
11207
  log: (message, detail) => ctx.logger.warn(message, detail)
11191
11208
  });
11192
- const delegating = new TraeDelegatingUpstreamClient(new TraeSoloBridge(client, catalog, wireResolver));
11209
+ const delegating = new TraeDelegatingUpstreamClient(new TraeSoloBridge(client, catalog, wireResolver, (message, detail) => ctx.logger.warn(message, detail)));
11193
11210
  routeClients[provider] = {
11194
11211
  provider,
11195
11212
  route,
@@ -11230,7 +11247,7 @@ function apply(ctx, config) {
11230
11247
  runtime: resolved.runtime
11231
11248
  }
11232
11249
  }),
11233
- solo: new TraeSoloBridge(primary.client, catalog, wireResolver),
11250
+ solo: new TraeSoloBridge(primary.client, catalog, wireResolver, (message, detail) => ctx.logger.warn(message, detail)),
11234
11251
  endpoint: `${REGION_GATEWAYS.cn.chat}/api/ide/v2/llm_raw_chat`,
11235
11252
  edition: resolved.identity.edition,
11236
11253
  identity: {
@@ -11627,7 +11644,7 @@ function apply(ctx, config) {
11627
11644
  identity: () => stack.identity(),
11628
11645
  route: stack.routeOf(providerOfPanel(panel)),
11629
11646
  log: (message, detail) => ctx.logger.warn(message, detail)
11630
- }), stack.catalog, stack.wireResolver)
11647
+ }), stack.catalog, stack.wireResolver, (message, detail) => ctx.logger.warn(message, detail))
11631
11648
  };
11632
11649
  }
11633
11650
  /**
@@ -11709,7 +11726,7 @@ function apply(ctx, config) {
11709
11726
  identity: () => stack.identity(),
11710
11727
  route: stack.routeOf(providerOfPanel(panel)),
11711
11728
  log: (message, detail) => ctx.logger.warn(message, detail)
11712
- }), stack.catalog, stack.wireResolver);
11729
+ }), stack.catalog, stack.wireResolver, (message, detail) => ctx.logger.warn(message, detail));
11713
11730
  const chat = (body, signal) => bridge.chatStream(body, signal);
11714
11731
  bridges.set(accountId, chat);
11715
11732
  return chat;
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": "4.6.3",
5
+ "version": "4.6.4",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",