dsh-token-total 1.1.2 → 1.1.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/README.md +5 -1
- package/lib/client.js +73 -26
- package/lib/index.js +166 -180
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/client/session-usage-locales.d.ts +2 -0
- package/lib/types/session-tree.d.ts +16 -0
- package/lib/types/session-usage.d.ts +5 -33
- package/lib/types/wire.d.ts +16 -0
- package/package.json +1 -1
- package/src/client/SessionUsageView.tsx +6 -1
- package/src/client/StatsLineEnhanced.tsx +69 -38
- package/src/client/locales.ts +8 -0
- package/src/client/session-usage-core.ts +1 -0
- package/src/client/session-usage-locales.ts +3 -0
- package/src/session-detail.ts +7 -36
- package/src/session-tree.ts +146 -0
- package/src/session-usage.ts +32 -181
- package/src/wire.ts +18 -0
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ DSH(DeepSeek Harness)Web 插件:**Token 用量统计看板** + **单会话
|
|
|
15
15
|
### 统计看板
|
|
16
16
|
|
|
17
17
|
入口在左侧边栏的插件区(与原生「插件」页并列的独立页面),点开即为看板,分上中下三部分。
|
|
18
|
+

|
|
18
19
|
|
|
19
20
|
#### 上部
|
|
20
21
|
|
|
@@ -40,15 +41,18 @@ DSH(DeepSeek Harness)Web 插件:**Token 用量统计看板** + **单会话
|
|
|
40
41
|
|
|
41
42
|
会话输入框下方原生的「用时与速度 / Token 用量」两枚胶囊被接管并增强:
|
|
42
43
|
|
|
44
|
+

|
|
45
|
+
|
|
43
46
|
- **Token 胶囊**显示的是**整棵会话树**(主智能体 + 全部子智能体)的合计,点开弹窗分为「主智能体 / 子智能体 / 会话合计」三行,并给出子智能体会话数。
|
|
44
47
|
- 子智能体是独立持久化的会话,通过日志头的 `parentSession` 串成树;插件按需重放这些日志汇总(同一套 `(turn, step)` 去重规则,且跳过 fork 继承来的事件前缀,避免与父会话重复计数)。
|
|
45
48
|
- **会话 Token 明细始终开启**:未命中缓存、缓存读 / 写、输出分别列出;底部胶囊的缓存命中率始终显示两位小数。
|
|
46
|
-
-
|
|
49
|
+
- **流式吞吐速度始终开启**:输出过程中显示当前这一步的实时解码速度——按已到达的流式增量实测(每步开始约 0.2 秒内出首个读数);一旦没有步骤在流(一步刚结算、工具执行中、步间空档),就交回本轮会话的加权平均解码速度(Σ 输出 token ÷ Σ 解码耗时,与会话「用量」页里的平均速率同口径),而不是停在最后那次短窗口读数上——那正是底部 57 tok/s 与请求记录 156 token/秒看起来互相矛盾的原因。
|
|
47
50
|
- 不再提供底部增强功能的开关;旧版本保存的关闭设置不再影响显示。
|
|
48
51
|
|
|
49
52
|
### 会话「用量」页
|
|
50
53
|
|
|
51
54
|
在每个会话原生的「对话 / 轨迹」旁新增 **用量** tab;沿用会话切换与视图导航,不另造顶部栏或输入框。
|
|
55
|
+

|
|
52
56
|
|
|
53
57
|
- **两张实时卡**(形制参考 [magpie](https://github.com/yetone/magpie))并排位于子代理/模型明细之下、请求记录之上,回答「提示词里现在有什么」与「整场会话到底花了什么」两个不同的问题:
|
|
54
58
|
- 左卡「**当前上下文**」:`已用 / 模型窗口 tokens` 大字附占用百分比,标题行右侧是当前供应商 · 模型;下方一条**整条即模型窗口**的堆叠条——有颜色的部分是已用的七类(按类别占比分色),尾部是还没用上的余量,所以条本身就是占用率;再下面按统一图例列出系统提示词、用户消息、技能注入、工具结果、工具定义、注入内容、助手消息各自的占比与估算 token(输出不占窗口,故不列)。鼠标悬浮可读精确 token 数与占上下文比例;模型窗口未知时只写已用数。
|
package/lib/client.js
CHANGED
|
@@ -31531,7 +31531,8 @@ window.__ModuleLoader__.load({
|
|
|
31531
31531
|
self,
|
|
31532
31532
|
subagents: tree?.subagents ?? { ...ZERO },
|
|
31533
31533
|
total: tree === null ? self : combinedTotals(tree, self),
|
|
31534
|
-
subagentSessions: tree?.subagentSessions ?? 0
|
|
31534
|
+
subagentSessions: tree?.subagentSessions ?? 0,
|
|
31535
|
+
...tree?.coverage ? { coverage: tree.coverage } : {}
|
|
31535
31536
|
};
|
|
31536
31537
|
}
|
|
31537
31538
|
function hasAnyTokens(totals) {
|
|
@@ -31661,14 +31662,14 @@ window.__ModuleLoader__.load({
|
|
|
31661
31662
|
* cannot see.
|
|
31662
31663
|
*
|
|
31663
31664
|
* Streaming throughput replaces the gauge pill's speed
|
|
31664
|
-
* figure with a live estimate of what is happening now
|
|
31665
|
-
*
|
|
31666
|
-
*
|
|
31667
|
-
*
|
|
31668
|
-
*
|
|
31669
|
-
*
|
|
31670
|
-
* subscription lives in
|
|
31671
|
-
*
|
|
31665
|
+
* figure with a live estimate of what is happening now, and hands the slot back
|
|
31666
|
+
* to the session's decode average when nothing is streaming. While a step
|
|
31667
|
+
* streams the live rate is the only reading that can match the pace of the
|
|
31668
|
+
* answer on screen; once the step settles it becomes a figure from a window
|
|
31669
|
+
* nobody is watching any more, so the pill returns to the same quotient the
|
|
31670
|
+
* usage view quotes (ΣoutputTokens / ΣdecodeMs) instead of freezing on the last
|
|
31671
|
+
* measurement. That subscription lives in its own child component, so stream
|
|
31672
|
+
* deltas never re-render the pills. The live
|
|
31672
31673
|
* figure itself is measured by a bounded sliding window over observed token
|
|
31673
31674
|
* growth (stats-line-core's StreamingRateSampler): the earlier cumulative
|
|
31674
31675
|
* quotient charged a whole step's text to a window that had just opened, so a
|
|
@@ -31754,14 +31755,23 @@ window.__ModuleLoader__.load({
|
|
|
31754
31755
|
* The gauge pill's always-on streaming speed slot.
|
|
31755
31756
|
*
|
|
31756
31757
|
* It subscribes to `legacy.partial` itself so per-frame deltas re-render only
|
|
31757
|
-
* this child, not the surrounding pills.
|
|
31758
|
-
*
|
|
31759
|
-
*
|
|
31760
|
-
*
|
|
31761
|
-
*
|
|
31762
|
-
*
|
|
31763
|
-
*
|
|
31764
|
-
*
|
|
31758
|
+
* this child, not the surrounding pills. Two figures share the slot, chosen by
|
|
31759
|
+
* whether a step is streaming right now:
|
|
31760
|
+
*
|
|
31761
|
+
* - while one is (`partial != null`), the measured rate of THAT stream — a
|
|
31762
|
+
* bounded sliding window over observed token growth (stats-line-core's
|
|
31763
|
+
* StreamingRateSampler), the only reading that can track the pace of the
|
|
31764
|
+
* answer currently on screen;
|
|
31765
|
+
* - otherwise the session's own decode average, ΣoutputTokens / ΣdecodeMs —
|
|
31766
|
+
* the same quotient `decodeRateOf` quotes in the usage view. An idle composer
|
|
31767
|
+
* must not freeze on whatever the last window happened to measure: a settled
|
|
31768
|
+
* thirty-step turn kept showing its final second-long reading (57 tok/s)
|
|
31769
|
+
* beside the usage view's 156 tok/s for the same work, and the two read as a
|
|
31770
|
+
* contradiction rather than as two scopes of one number.
|
|
31771
|
+
*
|
|
31772
|
+
* The measured reading carries the step it was taken in, so a new step cannot
|
|
31773
|
+
* inherit the previous one's figure before its own window publishes; nothing is
|
|
31774
|
+
* held across the gap — the gap is exactly when the average applies.
|
|
31765
31775
|
*
|
|
31766
31776
|
* One window covers a whole step, across every output kind the accumulator
|
|
31767
31777
|
* carries — reasoning, answer text, and Tool-call arguments — because the
|
|
@@ -31772,7 +31782,8 @@ window.__ModuleLoader__.load({
|
|
|
31772
31782
|
* state, so no render reads a clock or mutates the window: a render React starts
|
|
31773
31783
|
* and then discards must not decide what the next committed one displays. The
|
|
31774
31784
|
* rate itself comes from stats-line-core's StreamingRateSampler — a bounded
|
|
31775
|
-
* window over observed token growth, smoothed, and held while nothing arrives
|
|
31785
|
+
* window over observed token growth, smoothed, and held while nothing arrives
|
|
31786
|
+
* inside the step it belongs to.
|
|
31776
31787
|
*/
|
|
31777
31788
|
function LiveSpeed({ useChat, scale, fallback, t }) {
|
|
31778
31789
|
const partial = useChat((s) => s.legacy.partial);
|
|
@@ -31788,13 +31799,17 @@ window.__ModuleLoader__.load({
|
|
|
31788
31799
|
const scaleRef = (0, react.useRef)(scale);
|
|
31789
31800
|
const [beat, setBeat] = (0, react.useState)(0);
|
|
31790
31801
|
(0, react.useLayoutEffect)(() => {
|
|
31802
|
+
if (key === null) return;
|
|
31791
31803
|
if (scaleRef.current !== scale) {
|
|
31792
31804
|
scaleRef.current = scale;
|
|
31793
31805
|
rate.reset();
|
|
31794
31806
|
}
|
|
31795
31807
|
rate.observe(tokens, Date.now());
|
|
31796
31808
|
const next = rate.ratePerSecond();
|
|
31797
|
-
if (next !== null) setReading((previous) => previous === next ? previous :
|
|
31809
|
+
if (next !== null) setReading((previous) => previous !== null && previous.key === key && previous.value === next ? previous : {
|
|
31810
|
+
key,
|
|
31811
|
+
value: next
|
|
31812
|
+
});
|
|
31798
31813
|
}, [
|
|
31799
31814
|
tokens,
|
|
31800
31815
|
key,
|
|
@@ -31812,7 +31827,8 @@ window.__ModuleLoader__.load({
|
|
|
31812
31827
|
clearInterval(handle);
|
|
31813
31828
|
};
|
|
31814
31829
|
}, [streaming]);
|
|
31815
|
-
|
|
31830
|
+
const measured = reading !== null && reading.key === key ? reading.value : null;
|
|
31831
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SpeedFigure, { text: measured === null ? fallback : t("stats.tokensPerSecond", { throughput: formatTokensPerSecond(measured) }) });
|
|
31816
31832
|
}
|
|
31817
31833
|
function TimePill({ stats, t, dialog, useChat, scale, dock }) {
|
|
31818
31834
|
const { open, setOpen, rootRef, panelRef, pos } = useStatDialog(dialog);
|
|
@@ -31886,9 +31902,11 @@ window.__ModuleLoader__.load({
|
|
|
31886
31902
|
}
|
|
31887
31903
|
function UsagePill({ usage, tree, t, dialog, dock }) {
|
|
31888
31904
|
const { open, setOpen, rootRef, panelRef, pos } = useStatDialog(dialog);
|
|
31889
|
-
const
|
|
31905
|
+
const display = sessionUsageForDisplay(usage, tree?.tree ?? null);
|
|
31906
|
+
const billed = display.total;
|
|
31890
31907
|
const total = billedTotal(billed);
|
|
31891
|
-
const
|
|
31908
|
+
const incomplete = display.coverage?.complete === false;
|
|
31909
|
+
const totalText = (incomplete ? "≥ " : "") + t("message.turnUsage.count", { count: formatTokensCompact(total) });
|
|
31892
31910
|
const cacheHit = cacheHitPercent(billed);
|
|
31893
31911
|
const cacheHitText = cacheHit !== null ? t("stats.cacheHit", { percent: cacheHit }) : null;
|
|
31894
31912
|
const exactCount = (value) => t("message.turnUsage.count", { count: formatTokens(value) });
|
|
@@ -31901,7 +31919,8 @@ window.__ModuleLoader__.load({
|
|
|
31901
31919
|
className: StatsLineEnhanced_module_default.pill,
|
|
31902
31920
|
"aria-haspopup": "dialog",
|
|
31903
31921
|
"aria-expanded": open,
|
|
31904
|
-
"aria-label": cacheHitText === null ? totalText : totalText + " · " + cacheHitText,
|
|
31922
|
+
"aria-label": (incomplete ? t("stats.partial") + " · " : "") + (cacheHitText === null ? totalText : totalText + " · " + cacheHitText),
|
|
31923
|
+
title: incomplete ? t("stats.incomplete", { count: display.coverage.unreadableSessions.length }) : void 0,
|
|
31905
31924
|
onClick: () => {
|
|
31906
31925
|
setOpen(!open);
|
|
31907
31926
|
},
|
|
@@ -31928,13 +31947,18 @@ window.__ModuleLoader__.load({
|
|
|
31928
31947
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DatabaseIcon, {}), t("stats.dialog.usageTitle")]
|
|
31929
31948
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
31930
31949
|
className: StatsLineEnhanced_module_default.dialogTitleValue,
|
|
31931
|
-
children: exactCount(total)
|
|
31950
|
+
children: (incomplete ? "≥ " : "") + exactCount(total)
|
|
31932
31951
|
})]
|
|
31933
31952
|
}),
|
|
31934
31953
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
31935
31954
|
className: StatsLineEnhanced_module_default.dialogTitleRule,
|
|
31936
31955
|
"aria-hidden": true
|
|
31937
31956
|
}),
|
|
31957
|
+
incomplete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
31958
|
+
role: "status",
|
|
31959
|
+
"data-session-usage-partial": true,
|
|
31960
|
+
children: t("stats.incomplete", { count: display.coverage.unreadableSessions.length })
|
|
31961
|
+
}),
|
|
31938
31962
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
|
|
31939
31963
|
className: StatsLineEnhanced_module_default.dialogDetails,
|
|
31940
31964
|
"data-session-stats-usage": true,
|
|
@@ -31967,7 +31991,8 @@ window.__ModuleLoader__.load({
|
|
|
31967
31991
|
const stats = (0, react.useMemo)(() => projected ?? deriveStats(settledNodes ?? []), [projected, settledNodes]);
|
|
31968
31992
|
const tokenScale = (0, react.useMemo)(() => measuredTokenScale(settledNodes ?? []) ?? 1, [settledNodes]);
|
|
31969
31993
|
const tree = useSessionUsage(sessionId, usageSignature(usage, stats.steps));
|
|
31970
|
-
const
|
|
31994
|
+
const display = sessionUsageForDisplay(usage, tree.tree);
|
|
31995
|
+
const hasTokens = hasAnyTokens(display.total) || display.coverage?.complete === false;
|
|
31971
31996
|
if (stats.steps === 0 && !hasTokens) return null;
|
|
31972
31997
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
31973
31998
|
ref: markDockContainer,
|
|
@@ -32033,7 +32058,8 @@ window.__ModuleLoader__.load({
|
|
|
32033
32058
|
const StatsUsageEntry = (0, react.memo)(function StatsUsageEntry({ useProjection, t, sessionId }) {
|
|
32034
32059
|
const usage = useProjection("tokenUsage");
|
|
32035
32060
|
const tree = useSessionUsage(sessionId, usageSignature(usage, usage === void 0 ? 0 : 1));
|
|
32036
|
-
|
|
32061
|
+
const display = sessionUsageForDisplay(usage, tree.tree);
|
|
32062
|
+
if (!(hasAnyTokens(display.total) || display.coverage?.complete === false)) return null;
|
|
32037
32063
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsagePill, {
|
|
32038
32064
|
usage,
|
|
32039
32065
|
tree,
|
|
@@ -32405,6 +32431,8 @@ window.__ModuleLoader__.load({
|
|
|
32405
32431
|
"stats.cacheMiss": "Cache miss",
|
|
32406
32432
|
"stats.dialog.title": "Time & speed",
|
|
32407
32433
|
"stats.dialog.usageTitle": "Token usage",
|
|
32434
|
+
"stats.partial": "Partial usage",
|
|
32435
|
+
"stats.incomplete": "{count} subagent session(s) could not be read; totals are incomplete.",
|
|
32408
32436
|
"stats.dialog.llmTime": "LLM time",
|
|
32409
32437
|
"stats.dialog.toolTime": "Tool time",
|
|
32410
32438
|
"stats.dialog.ttft": "Mean TTFT",
|
|
@@ -32493,6 +32521,8 @@ window.__ModuleLoader__.load({
|
|
|
32493
32521
|
"stats.cacheMiss": "未命中缓存",
|
|
32494
32522
|
"stats.dialog.title": "用时与速度",
|
|
32495
32523
|
"stats.dialog.usageTitle": "Token 用量",
|
|
32524
|
+
"stats.partial": "部分用量",
|
|
32525
|
+
"stats.incomplete": "有 {count} 个子会话暂不可读取,总计尚不完整。",
|
|
32496
32526
|
"stats.dialog.llmTime": "LLM 用时",
|
|
32497
32527
|
"stats.dialog.toolTime": "工具用时",
|
|
32498
32528
|
"stats.dialog.ttft": "首 token 平均",
|
|
@@ -32581,6 +32611,8 @@ window.__ModuleLoader__.load({
|
|
|
32581
32611
|
"stats.cacheMiss": "未命中快取",
|
|
32582
32612
|
"stats.dialog.title": "用時與速度",
|
|
32583
32613
|
"stats.dialog.usageTitle": "Token 用量",
|
|
32614
|
+
"stats.partial": "部分用量",
|
|
32615
|
+
"stats.incomplete": "有 {count} 個子會話暫不可讀取,總計尚不完整。",
|
|
32584
32616
|
"stats.dialog.llmTime": "LLM 用時",
|
|
32585
32617
|
"stats.dialog.toolTime": "工具用時",
|
|
32586
32618
|
"stats.dialog.ttft": "首 token 平均",
|
|
@@ -33829,6 +33861,15 @@ window.__ModuleLoader__.load({
|
|
|
33829
33861
|
className: SessionUsageView_module_default.scope,
|
|
33830
33862
|
children: t("scope")
|
|
33831
33863
|
}),
|
|
33864
|
+
detail.coverage?.complete === false && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
33865
|
+
className: SessionUsageView_module_default.error,
|
|
33866
|
+
role: "status",
|
|
33867
|
+
"data-session-usage-partial": true,
|
|
33868
|
+
children: [t("partialCoverage", { count: String(detail.coverage.unreadableSessions.length) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
33869
|
+
title: detail.coverage.unreadableSessions.map((issue) => issue.sessionId).join("\n"),
|
|
33870
|
+
children: t("partialTotals")
|
|
33871
|
+
})]
|
|
33872
|
+
}),
|
|
33832
33873
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metrics, {
|
|
33833
33874
|
detail,
|
|
33834
33875
|
t
|
|
@@ -33957,6 +33998,8 @@ window.__ModuleLoader__.load({
|
|
|
33957
33998
|
noModels: "暂无模型用量",
|
|
33958
33999
|
scope: "本会话及所有子代理 · 历史日志累计",
|
|
33959
34000
|
note: "统计包含本会话与全部后代的独立请求、重试和压缩调用,不重复计算继承历史。首 token 与速度仅使用已记录的时间信息,缺失时显示 —。",
|
|
34001
|
+
partialCoverage: "有 {count} 个子会话暂不可读取。",
|
|
34002
|
+
partialTotals: "当前仅显示已读取用量,未将缺失用量视为零。",
|
|
33960
34003
|
failedLoad: "用量读取失败,已保留上次结果。",
|
|
33961
34004
|
retry: "重新读取",
|
|
33962
34005
|
completed: "已完成",
|
|
@@ -34065,6 +34108,8 @@ window.__ModuleLoader__.load({
|
|
|
34065
34108
|
noModels: "No model usage yet",
|
|
34066
34109
|
scope: "This session and all subagents · Historical log totals",
|
|
34067
34110
|
note: "Includes independent requests, retries and compaction calls from this session and all descendants, without counting inherited history twice. Timing uses recorded timestamps only; missing values appear as —.",
|
|
34111
|
+
partialCoverage: "{count} subagent session(s) could not be read.",
|
|
34112
|
+
partialTotals: "Only readable usage is shown; missing usage is not treated as zero.",
|
|
34068
34113
|
failedLoad: "Could not read usage. The last successful result is preserved.",
|
|
34069
34114
|
retry: "Retry",
|
|
34070
34115
|
completed: "Completed",
|
|
@@ -34173,6 +34218,8 @@ window.__ModuleLoader__.load({
|
|
|
34173
34218
|
noModels: "暫無模型用量",
|
|
34174
34219
|
scope: "本會話及所有子代理 · 歷史日誌累計",
|
|
34175
34220
|
note: "統計包含本會話與全部後代的獨立請求、重試和壓縮呼叫,不重複計算繼承歷史。首 token 與速度僅使用已記錄的時間資訊,缺失時顯示 —。",
|
|
34221
|
+
partialCoverage: "有 {count} 個子會話暫不可讀取。",
|
|
34222
|
+
partialTotals: "目前僅顯示已讀取用量,未將缺失用量視為零。",
|
|
34176
34223
|
failedLoad: "用量讀取失敗,已保留上次結果。",
|
|
34177
34224
|
retry: "重新讀取",
|
|
34178
34225
|
completed: "已完成",
|