dsh-token-total 1.1.2 → 1.1.3
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 +1 -1
- package/lib/client.js +34 -19
- package/package.json +1 -1
- package/src/client/StatsLineEnhanced.tsx +55 -33
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ DSH(DeepSeek Harness)Web 插件:**Token 用量统计看板** + **单会话
|
|
|
43
43
|
- **Token 胶囊**显示的是**整棵会话树**(主智能体 + 全部子智能体)的合计,点开弹窗分为「主智能体 / 子智能体 / 会话合计」三行,并给出子智能体会话数。
|
|
44
44
|
- 子智能体是独立持久化的会话,通过日志头的 `parentSession` 串成树;插件按需重放这些日志汇总(同一套 `(turn, step)` 去重规则,且跳过 fork 继承来的事件前缀,避免与父会话重复计数)。
|
|
45
45
|
- **会话 Token 明细始终开启**:未命中缓存、缓存读 / 写、输出分别列出;底部胶囊的缓存命中率始终显示两位小数。
|
|
46
|
-
-
|
|
46
|
+
- **流式吞吐速度始终开启**:输出过程中显示当前这一步的实时解码速度——按已到达的流式增量实测(每步开始约 0.2 秒内出首个读数);一旦没有步骤在流(一步刚结算、工具执行中、步间空档),就交回本轮会话的加权平均解码速度(Σ 输出 token ÷ Σ 解码耗时,与会话「用量」页里的平均速率同口径),而不是停在最后那次短窗口读数上——那正是底部 57 tok/s 与请求记录 156 token/秒看起来互相矛盾的原因。
|
|
47
47
|
- 不再提供底部增强功能的开关;旧版本保存的关闭设置不再影响显示。
|
|
48
48
|
|
|
49
49
|
### 会话「用量」页
|
package/lib/client.js
CHANGED
|
@@ -31661,14 +31661,14 @@ window.__ModuleLoader__.load({
|
|
|
31661
31661
|
* cannot see.
|
|
31662
31662
|
*
|
|
31663
31663
|
* 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
|
-
*
|
|
31664
|
+
* figure with a live estimate of what is happening now, and hands the slot back
|
|
31665
|
+
* to the session's decode average when nothing is streaming. While a step
|
|
31666
|
+
* streams the live rate is the only reading that can match the pace of the
|
|
31667
|
+
* answer on screen; once the step settles it becomes a figure from a window
|
|
31668
|
+
* nobody is watching any more, so the pill returns to the same quotient the
|
|
31669
|
+
* usage view quotes (ΣoutputTokens / ΣdecodeMs) instead of freezing on the last
|
|
31670
|
+
* measurement. That subscription lives in its own child component, so stream
|
|
31671
|
+
* deltas never re-render the pills. The live
|
|
31672
31672
|
* figure itself is measured by a bounded sliding window over observed token
|
|
31673
31673
|
* growth (stats-line-core's StreamingRateSampler): the earlier cumulative
|
|
31674
31674
|
* quotient charged a whole step's text to a window that had just opened, so a
|
|
@@ -31754,14 +31754,23 @@ window.__ModuleLoader__.load({
|
|
|
31754
31754
|
* The gauge pill's always-on streaming speed slot.
|
|
31755
31755
|
*
|
|
31756
31756
|
* 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
|
-
*
|
|
31757
|
+
* this child, not the surrounding pills. Two figures share the slot, chosen by
|
|
31758
|
+
* whether a step is streaming right now:
|
|
31759
|
+
*
|
|
31760
|
+
* - while one is (`partial != null`), the measured rate of THAT stream — a
|
|
31761
|
+
* bounded sliding window over observed token growth (stats-line-core's
|
|
31762
|
+
* StreamingRateSampler), the only reading that can track the pace of the
|
|
31763
|
+
* answer currently on screen;
|
|
31764
|
+
* - otherwise the session's own decode average, ΣoutputTokens / ΣdecodeMs —
|
|
31765
|
+
* the same quotient `decodeRateOf` quotes in the usage view. An idle composer
|
|
31766
|
+
* must not freeze on whatever the last window happened to measure: a settled
|
|
31767
|
+
* thirty-step turn kept showing its final second-long reading (57 tok/s)
|
|
31768
|
+
* beside the usage view's 156 tok/s for the same work, and the two read as a
|
|
31769
|
+
* contradiction rather than as two scopes of one number.
|
|
31770
|
+
*
|
|
31771
|
+
* The measured reading carries the step it was taken in, so a new step cannot
|
|
31772
|
+
* inherit the previous one's figure before its own window publishes; nothing is
|
|
31773
|
+
* held across the gap — the gap is exactly when the average applies.
|
|
31765
31774
|
*
|
|
31766
31775
|
* One window covers a whole step, across every output kind the accumulator
|
|
31767
31776
|
* carries — reasoning, answer text, and Tool-call arguments — because the
|
|
@@ -31772,7 +31781,8 @@ window.__ModuleLoader__.load({
|
|
|
31772
31781
|
* state, so no render reads a clock or mutates the window: a render React starts
|
|
31773
31782
|
* and then discards must not decide what the next committed one displays. The
|
|
31774
31783
|
* rate itself comes from stats-line-core's StreamingRateSampler — a bounded
|
|
31775
|
-
* window over observed token growth, smoothed, and held while nothing arrives
|
|
31784
|
+
* window over observed token growth, smoothed, and held while nothing arrives
|
|
31785
|
+
* inside the step it belongs to.
|
|
31776
31786
|
*/
|
|
31777
31787
|
function LiveSpeed({ useChat, scale, fallback, t }) {
|
|
31778
31788
|
const partial = useChat((s) => s.legacy.partial);
|
|
@@ -31788,13 +31798,17 @@ window.__ModuleLoader__.load({
|
|
|
31788
31798
|
const scaleRef = (0, react.useRef)(scale);
|
|
31789
31799
|
const [beat, setBeat] = (0, react.useState)(0);
|
|
31790
31800
|
(0, react.useLayoutEffect)(() => {
|
|
31801
|
+
if (key === null) return;
|
|
31791
31802
|
if (scaleRef.current !== scale) {
|
|
31792
31803
|
scaleRef.current = scale;
|
|
31793
31804
|
rate.reset();
|
|
31794
31805
|
}
|
|
31795
31806
|
rate.observe(tokens, Date.now());
|
|
31796
31807
|
const next = rate.ratePerSecond();
|
|
31797
|
-
if (next !== null) setReading((previous) => previous === next ? previous :
|
|
31808
|
+
if (next !== null) setReading((previous) => previous !== null && previous.key === key && previous.value === next ? previous : {
|
|
31809
|
+
key,
|
|
31810
|
+
value: next
|
|
31811
|
+
});
|
|
31798
31812
|
}, [
|
|
31799
31813
|
tokens,
|
|
31800
31814
|
key,
|
|
@@ -31812,7 +31826,8 @@ window.__ModuleLoader__.load({
|
|
|
31812
31826
|
clearInterval(handle);
|
|
31813
31827
|
};
|
|
31814
31828
|
}, [streaming]);
|
|
31815
|
-
|
|
31829
|
+
const measured = reading !== null && reading.key === key ? reading.value : null;
|
|
31830
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SpeedFigure, { text: measured === null ? fallback : t("stats.tokensPerSecond", { throughput: formatTokensPerSecond(measured) }) });
|
|
31816
31831
|
}
|
|
31817
31832
|
function TimePill({ stats, t, dialog, useChat, scale, dock }) {
|
|
31818
31833
|
const { open, setOpen, rootRef, panelRef, pos } = useStatDialog(dialog);
|
package/package.json
CHANGED
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
* cannot see.
|
|
32
32
|
*
|
|
33
33
|
* Streaming throughput replaces the gauge pill's speed
|
|
34
|
-
* figure with a live estimate of what is happening now
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* subscription lives in
|
|
41
|
-
*
|
|
34
|
+
* figure with a live estimate of what is happening now, and hands the slot back
|
|
35
|
+
* to the session's decode average when nothing is streaming. While a step
|
|
36
|
+
* streams the live rate is the only reading that can match the pace of the
|
|
37
|
+
* answer on screen; once the step settles it becomes a figure from a window
|
|
38
|
+
* nobody is watching any more, so the pill returns to the same quotient the
|
|
39
|
+
* usage view quotes (ΣoutputTokens / ΣdecodeMs) instead of freezing on the last
|
|
40
|
+
* measurement. That subscription lives in its own child component, so stream
|
|
41
|
+
* deltas never re-render the pills. The live
|
|
42
42
|
* figure itself is measured by a bounded sliding window over observed token
|
|
43
43
|
* growth (stats-line-core's StreamingRateSampler): the earlier cumulative
|
|
44
44
|
* quotient charged a whole step's text to a window that had just opened, so a
|
|
@@ -181,14 +181,23 @@ const LIVE_SPEED_REFRESH_MS = 1_000
|
|
|
181
181
|
* The gauge pill's always-on streaming speed slot.
|
|
182
182
|
*
|
|
183
183
|
* It subscribes to `legacy.partial` itself so per-frame deltas re-render only
|
|
184
|
-
* this child, not the surrounding pills.
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
184
|
+
* this child, not the surrounding pills. Two figures share the slot, chosen by
|
|
185
|
+
* whether a step is streaming right now:
|
|
186
|
+
*
|
|
187
|
+
* - while one is (`partial != null`), the measured rate of THAT stream — a
|
|
188
|
+
* bounded sliding window over observed token growth (stats-line-core's
|
|
189
|
+
* StreamingRateSampler), the only reading that can track the pace of the
|
|
190
|
+
* answer currently on screen;
|
|
191
|
+
* - otherwise the session's own decode average, ΣoutputTokens / ΣdecodeMs —
|
|
192
|
+
* the same quotient `decodeRateOf` quotes in the usage view. An idle composer
|
|
193
|
+
* must not freeze on whatever the last window happened to measure: a settled
|
|
194
|
+
* thirty-step turn kept showing its final second-long reading (57 tok/s)
|
|
195
|
+
* beside the usage view's 156 tok/s for the same work, and the two read as a
|
|
196
|
+
* contradiction rather than as two scopes of one number.
|
|
197
|
+
*
|
|
198
|
+
* The measured reading carries the step it was taken in, so a new step cannot
|
|
199
|
+
* inherit the previous one's figure before its own window publishes; nothing is
|
|
200
|
+
* held across the gap — the gap is exactly when the average applies.
|
|
192
201
|
*
|
|
193
202
|
* One window covers a whole step, across every output kind the accumulator
|
|
194
203
|
* carries — reasoning, answer text, and Tool-call arguments — because the
|
|
@@ -199,13 +208,16 @@ const LIVE_SPEED_REFRESH_MS = 1_000
|
|
|
199
208
|
* state, so no render reads a clock or mutates the window: a render React starts
|
|
200
209
|
* and then discards must not decide what the next committed one displays. The
|
|
201
210
|
* rate itself comes from stats-line-core's StreamingRateSampler — a bounded
|
|
202
|
-
* window over observed token growth, smoothed, and held while nothing arrives
|
|
211
|
+
* window over observed token growth, smoothed, and held while nothing arrives
|
|
212
|
+
* inside the step it belongs to.
|
|
203
213
|
*/
|
|
204
214
|
function LiveSpeed({ useChat, scale, fallback, t }: {
|
|
205
215
|
useChat: ChatSnapshotSelectorHook
|
|
206
216
|
/** Correction measured from this session's settled steps (1 = raw prior). */
|
|
207
217
|
scale: number
|
|
208
|
-
/** The
|
|
218
|
+
/** The session's own decode average (ΣoutputTokens / ΣdecodeMs): what the
|
|
219
|
+
* slot shows whenever no step is streaming, and before the streaming step's
|
|
220
|
+
* window has published its first reading. */
|
|
209
221
|
fallback: string | null
|
|
210
222
|
t: T
|
|
211
223
|
}) {
|
|
@@ -223,18 +235,22 @@ function LiveSpeed({ useChat, scale, fallback, t }: {
|
|
|
223
235
|
sampler.current = { key, rate: new StreamingRateSampler() }
|
|
224
236
|
}
|
|
225
237
|
const rate = sampler.current.rate
|
|
226
|
-
// The
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
const [reading, setReading] = useState<number | null>(null)
|
|
238
|
+
// The figure last measured, tagged with the step it came from. The tag is
|
|
239
|
+
// what retires it: a reading taken in 3:4 never shows during 3:5, and with
|
|
240
|
+
// no step streaming (`key === null`) no tag can match at all, so the slot
|
|
241
|
+
// falls through to the session average on its own — no timer, no clearing
|
|
242
|
+
// effect, and no stale figure left standing after a turn settles.
|
|
243
|
+
const [reading, setReading] = useState<{ key: string; value: number } | null>(null)
|
|
233
244
|
const scaleRef = useRef(scale)
|
|
234
245
|
// Every delta re-runs this (its dependencies move), and the beat re-runs it once
|
|
235
246
|
// a second so a step that has gone quiet still advances its window.
|
|
236
247
|
const [beat, setBeat] = useState(0)
|
|
237
248
|
useLayoutEffect(() => {
|
|
249
|
+
// Nothing is streaming: the slot shows the session average, and there is no
|
|
250
|
+
// window to advance. Skipping the observation also keeps a settled step's
|
|
251
|
+
// sampler from being fed a zero estimate, which would re-anchor it against
|
|
252
|
+
// text that is already counted.
|
|
253
|
+
if (key === null) return
|
|
238
254
|
// A correction that moved re-prices the whole estimate, so growth already
|
|
239
255
|
// counted under the old one must not be measured against the new one: that
|
|
240
256
|
// would read as a burst the stream never produced.
|
|
@@ -244,11 +260,13 @@ function LiveSpeed({ useChat, scale, fallback, t }: {
|
|
|
244
260
|
}
|
|
245
261
|
rate.observe(tokens, Date.now())
|
|
246
262
|
// A null here means the fresh window has not measured anything yet (a step
|
|
247
|
-
// that just opened, or one that re-anchored); the
|
|
248
|
-
//
|
|
263
|
+
// that just opened, or one that re-anchored); the average stays up until
|
|
264
|
+
// this step's own window publishes something.
|
|
249
265
|
const next = rate.ratePerSecond()
|
|
250
266
|
if (next !== null) {
|
|
251
|
-
setReading(previous => (previous ===
|
|
267
|
+
setReading(previous => (previous !== null && previous.key === key && previous.value === next
|
|
268
|
+
? previous
|
|
269
|
+
: { key, value: next }))
|
|
252
270
|
}
|
|
253
271
|
}, [tokens, key, beat, rate, scale])
|
|
254
272
|
// Keyed on "a step is streaming", not on the step identity: switching steps
|
|
@@ -259,9 +277,12 @@ function LiveSpeed({ useChat, scale, fallback, t }: {
|
|
|
259
277
|
const handle = setInterval(() => { setBeat(Date.now()) }, LIVE_SPEED_REFRESH_MS)
|
|
260
278
|
return () => { clearInterval(handle) }
|
|
261
279
|
}, [streaming])
|
|
262
|
-
|
|
280
|
+
// The tag test is the whole selection rule: a reading only shows while the
|
|
281
|
+
// step that produced it is the step streaming now.
|
|
282
|
+
const measured = reading !== null && reading.key === key ? reading.value : null
|
|
283
|
+
return <SpeedFigure text={measured === null
|
|
263
284
|
? fallback
|
|
264
|
-
: t('stats.tokensPerSecond', { throughput: formatTokensPerSecond(
|
|
285
|
+
: t('stats.tokensPerSecond', { throughput: formatTokensPerSecond(measured) })} />
|
|
265
286
|
}
|
|
266
287
|
|
|
267
288
|
function TimePill({ stats, t, dialog, useChat, scale, dock }: {
|
|
@@ -278,9 +299,10 @@ function TimePill({ stats, t, dialog, useChat, scale, dock }: {
|
|
|
278
299
|
}) {
|
|
279
300
|
const { open, setOpen, rootRef, panelRef, pos } = useStatDialog(dialog)
|
|
280
301
|
const counts = t('stats.counts', { turns: stats.turns, steps: stats.steps })
|
|
281
|
-
// The
|
|
282
|
-
// gets the durable session reading rather than a value that
|
|
283
|
-
// second while a step streams
|
|
302
|
+
// The session figure now serves both jobs: the pill's accessible name (a
|
|
303
|
+
// screen reader gets the durable session reading rather than a value that
|
|
304
|
+
// changes every second while a step streams) and the visible figure whenever
|
|
305
|
+
// no step is streaming.
|
|
284
306
|
const officialTps = stats.decodeMs > 0
|
|
285
307
|
? t('stats.tokensPerSecond', {
|
|
286
308
|
throughput: formatTokensPerSecond(stats.decodeTokens / (stats.decodeMs / 1_000)),
|