dsh-connect-trae 2.0.4 → 2.0.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 +13 -0
- package/lib/index.d.ts +15 -0
- package/lib/index.js +23 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.5 (2026-09-22)
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
- **修复「缓存统计一直是 0」**(issue #10:用户反馈「我测试了一下一直0缓存」,并猜测是接口原因或 Trae 本身不缓存):
|
|
8
|
+
- **根因在插件自己这一层**:Trae 上游**确实有前缀缓存、也确实在报**(实测逐字节相同的长 prompt 连发,第二次起 `cache_read_input_tokens` 从 0 升到 5248、9216),但插件解码 `token_usage` 事件时**只搬了 4 个计数器**(`prompt_tokens` / `completion_tokens` / `total_tokens` / `reasoning_tokens`),`cache_read_input_tokens` 与 `cache_creation_input_tokens` 被直接丢弃;`TraeStreamEvent.usage` 在**类型层就无法表达**这两个值,转发层自然也无从搬运。
|
|
9
|
+
- **不是「放行」就够,必须做字段名映射**:`dsh-llm-pi-ai` 只在 `usage.cacheRead > 0` 时写入 `cacheReadTokens`,而 pi-ai 的 `parseChunkUsage` 只识别 `prompt_tokens_details.cached_tokens` / `prompt_cache_hit_tokens` / `cached_tokens` 三种拼写——Trae 的原生字段名不在其中,原样透传依然读不到。现在映射为规范的 `prompt_tokens_details.cached_tokens` / `.cache_write_tokens`。
|
|
10
|
+
- **先确认了语义再动手**:pi-ai 会做 `input = prompt_tokens - cacheRead - cacheWrite`,该减法仅在「`prompt_tokens` 包含缓存量」(OpenAI 口径)时成立。实测同一份 prompt 的 `prompt_tokens` 恒为 9224 而 `cache_read` 从 5248 涨到 9216,若为 Anthropic 的排除口径则同一输入的实际规模会在 14472 → 18440 之间跳变,不可能——据此确认 Trae 用包含口径。端到端复核账目守恒:`8 + 9216 + 0 = 9224`,与 `prompt_tokens` 完全相等,无重复扣减。
|
|
11
|
+
- **`src/raw-chat.ts` 存在同一类缺口**(Raw Chat 通道将来启用时会踩同一个坑):`RawChatDelta.usage` 补上缓存字段,并同时接受 OpenAI 嵌套拼写与 DeepSeek/Kimi 的顶层拼写。两个通道均保证「上游没报缓存时 wire 形状不变」,不产生空 `prompt_tokens_details`。
|
|
12
|
+
- 新增 4 个回归测试,其中 bridge 用例直接以真机捕获的 `token_usage` 事件原文作为输入。
|
|
13
|
+
- 取证与复现脚本:`docs/ISSUE10_DIAGNOSIS.md`、`scripts/probe-cache-convention.mjs`。
|
|
14
|
+
- **说明**:修复只保证「上游报了缓存就如实上报」。新会话/新前缀的第一轮必然 `cache_read=0`(冷启动),第二轮起才有值;实测 Trae 当前只报读不报写(`cache_creation_input_tokens` 恒为 0)。
|
|
15
|
+
|
|
3
16
|
## 2.0.4 (2026-09-16)
|
|
4
17
|
|
|
5
18
|
### Fixes
|
package/lib/index.d.ts
CHANGED
|
@@ -760,6 +760,10 @@ type RawChatDelta = {
|
|
|
760
760
|
inputTokens?: number;
|
|
761
761
|
outputTokens?: number;
|
|
762
762
|
totalTokens?: number;
|
|
763
|
+
/** Cache-read tokens (`prompt_tokens_details.cached_tokens` / `cached_tokens`). */
|
|
764
|
+
cacheReadTokens?: number;
|
|
765
|
+
/** Cache-write tokens (`prompt_tokens_details.cache_write_tokens`). */
|
|
766
|
+
cacheWriteTokens?: number;
|
|
763
767
|
} | {
|
|
764
768
|
type: 'done';
|
|
765
769
|
finishReason: string;
|
|
@@ -1018,6 +1022,17 @@ type TraeStreamEvent = {
|
|
|
1018
1022
|
outputTokens?: number;
|
|
1019
1023
|
totalTokens?: number;
|
|
1020
1024
|
reasoningTokens?: number;
|
|
1025
|
+
/**
|
|
1026
|
+
* Trae's prompt-cache accounting (`cache_read_input_tokens` /
|
|
1027
|
+
* `cache_creation_input_tokens` on the `token_usage` event). Verified
|
|
1028
|
+
* 2026-09-22 (docs/ISSUE10_DIAGNOSIS.md): a byte-identical 9224-token
|
|
1029
|
+
* prompt reports `prompt_tokens: 9224` while `cache_read_input_tokens`
|
|
1030
|
+
* rises to 9216, so these are a *subset* of `prompt_tokens` — the OpenAI
|
|
1031
|
+
* convention, not Anthropic's exclusive one. Forwarding them as
|
|
1032
|
+
* `cached_tokens` is therefore arithmetically correct.
|
|
1033
|
+
*/
|
|
1034
|
+
cacheReadTokens?: number;
|
|
1035
|
+
cacheWriteTokens?: number;
|
|
1021
1036
|
} | {
|
|
1022
1037
|
type: 'done';
|
|
1023
1038
|
finishReason: string;
|
package/lib/index.js
CHANGED
|
@@ -2166,7 +2166,9 @@ function decodeTraeEvent(event) {
|
|
|
2166
2166
|
...typeof record["prompt_tokens"] === "number" ? { inputTokens: record["prompt_tokens"] } : {},
|
|
2167
2167
|
...typeof record["completion_tokens"] === "number" ? { outputTokens: record["completion_tokens"] } : {},
|
|
2168
2168
|
...typeof record["total_tokens"] === "number" ? { totalTokens: record["total_tokens"] } : {},
|
|
2169
|
-
...typeof record["reasoning_tokens"] === "number" ? { reasoningTokens: record["reasoning_tokens"] } : {}
|
|
2169
|
+
...typeof record["reasoning_tokens"] === "number" ? { reasoningTokens: record["reasoning_tokens"] } : {},
|
|
2170
|
+
...typeof record["cache_read_input_tokens"] === "number" ? { cacheReadTokens: record["cache_read_input_tokens"] } : {},
|
|
2171
|
+
...typeof record["cache_creation_input_tokens"] === "number" ? { cacheWriteTokens: record["cache_creation_input_tokens"] } : {}
|
|
2170
2172
|
};
|
|
2171
2173
|
if (event.event === "done" || typeof record["finish_reason"] === "string" && record["response"] === void 0) return {
|
|
2172
2174
|
type: "done",
|
|
@@ -2253,12 +2255,20 @@ function bridgeTraeSoloStream(response, model) {
|
|
|
2253
2255
|
delta["tool_calls"] = toolCalls;
|
|
2254
2256
|
}
|
|
2255
2257
|
if (Object.keys(delta).length > 0) controller.enqueue(chunk(delta));
|
|
2256
|
-
} else if (decoded.type === "usage")
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2258
|
+
} else if (decoded.type === "usage") {
|
|
2259
|
+
const cacheRead = decoded.cacheReadTokens;
|
|
2260
|
+
const cacheWrite = decoded.cacheWriteTokens;
|
|
2261
|
+
const details = {
|
|
2262
|
+
...cacheRead === void 0 ? {} : { cached_tokens: cacheRead },
|
|
2263
|
+
...cacheWrite === void 0 ? {} : { cache_write_tokens: cacheWrite }
|
|
2264
|
+
};
|
|
2265
|
+
usage = {
|
|
2266
|
+
...decoded.inputTokens === void 0 ? {} : { prompt_tokens: decoded.inputTokens },
|
|
2267
|
+
...decoded.outputTokens === void 0 ? {} : { completion_tokens: decoded.outputTokens },
|
|
2268
|
+
...decoded.totalTokens === void 0 ? {} : { total_tokens: decoded.totalTokens },
|
|
2269
|
+
...Object.keys(details).length === 0 ? {} : { prompt_tokens_details: details }
|
|
2270
|
+
};
|
|
2271
|
+
} else if (decoded.type === "done") {
|
|
2262
2272
|
upstreamEnded = true;
|
|
2263
2273
|
if (!emittedFinishReason) {
|
|
2264
2274
|
emittedFinishReason = true;
|
|
@@ -2549,11 +2559,16 @@ function decodeRawChatChunk(value) {
|
|
|
2549
2559
|
}
|
|
2550
2560
|
if (typeof record["usage"] === "object" && record["usage"] !== null) {
|
|
2551
2561
|
const usage = record["usage"];
|
|
2562
|
+
const details = typeof usage["prompt_tokens_details"] === "object" && usage["prompt_tokens_details"] !== null ? usage["prompt_tokens_details"] : {};
|
|
2563
|
+
const cacheRead = details["cached_tokens"] ?? usage["prompt_cache_hit_tokens"] ?? usage["cached_tokens"];
|
|
2564
|
+
const cacheWrite = details["cache_write_tokens"];
|
|
2552
2565
|
result.push({
|
|
2553
2566
|
type: "usage",
|
|
2554
2567
|
...typeof usage["prompt_tokens"] === "number" ? { inputTokens: usage["prompt_tokens"] } : {},
|
|
2555
2568
|
...typeof usage["completion_tokens"] === "number" ? { outputTokens: usage["completion_tokens"] } : {},
|
|
2556
|
-
...typeof usage["total_tokens"] === "number" ? { totalTokens: usage["total_tokens"] } : {}
|
|
2569
|
+
...typeof usage["total_tokens"] === "number" ? { totalTokens: usage["total_tokens"] } : {},
|
|
2570
|
+
...typeof cacheRead === "number" ? { cacheReadTokens: cacheRead } : {},
|
|
2571
|
+
...typeof cacheWrite === "number" ? { cacheWriteTokens: cacheWrite } : {}
|
|
2557
2572
|
});
|
|
2558
2573
|
}
|
|
2559
2574
|
return result.length === 0 ? [{
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-connect-trae",
|
|
3
3
|
"displayName": "DSH Connect Trae",
|
|
4
4
|
"description": "Connect locally signed-in Trae models to DeepSeek Harness, plus a read-only usage/credits summary.",
|
|
5
|
-
"version": "2.0.
|
|
5
|
+
"version": "2.0.5",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|