dsh-all-usage 1.0.6 → 1.0.8
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 +69 -0
- package/README.md +28 -2
- package/lib/index.js +88 -7
- package/package.json +2 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `dsh-all-usage` are documented here.
|
|
4
|
+
|
|
5
|
+
## [1.0.8] - 2026-08-23
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added a restart read-avoidance cursor: on startup the plugin uses the persisted log revision (via `sessionPersistence.listSnapshots()`, a header-line + stat read) as a per-session change signal. Sessions whose log is unchanged since the last ledger write are applied straight from the durable ledger without reading their full event log; only changed or new sessions are read (incrementally).
|
|
10
|
+
- Stored the log revision in each durable ledger record (`lastRevision`); existing rows without it are re-read once and backfilled, so behavior is backward compatible.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Eliminated the per-session full-event read at restart for unchanged persisted sessions, so DSH restarts no longer re-read every session's history from scratch.
|
|
15
|
+
|
|
16
|
+
## [1.0.7] - 2026-08-23
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Added an incremental ledger cursor: baseline rescans reuse the durable ledger as a per-session cursor, so unchanged sessions seed from the ledger instead of re-folding every historical event, and changed sessions fold only the new tail.
|
|
21
|
+
- Added an all-zero usage guard: zero-token usage replays no longer overwrite already-recorded turn/step usage, while pure cache-read requests (billable cache hits with no input/output) are still counted.
|
|
22
|
+
- Added a structured, machine-readable token semantics declaration (`tokenSemantics.semantics`) to the `/api/all-usage` snapshot: input is fresh (excludes cache), cache and reasoning are separate buckets.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Reduced startup scan cost on long usage history: sessions whose events are unchanged since the last ledger write are no longer re-folded or rewritten.
|
|
27
|
+
- Prevented a live retry of an already-recorded step from double-counting after a fresh-instance seed.
|
|
28
|
+
|
|
29
|
+
## [1.0.6] - 2026-08-23
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- Added a durable per-session usage ledger written at the awaited `session/flush` boundary.
|
|
34
|
+
- Preserved successfully flushed turns and token usage after conversation logs are deleted.
|
|
35
|
+
- Added automatic rebuilds from ledger records after session disposal or reconciliation detects missing logs.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- Prevented stale baseline snapshots from overwriting newer flushed ledger records.
|
|
40
|
+
- Normalized duplicate ledger keys during hydration and guarded ledger writes during plugin disposal.
|
|
41
|
+
- Prevented inactive Cordis timer contexts from escalating into fatal DSH load failures.
|
|
42
|
+
- Replaced invalid hyphenated storage unit names with valid underscore names.
|
|
43
|
+
|
|
44
|
+
## [1.0.5] - 2026-08-20
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
|
|
48
|
+
- Added custom start/end date ranges across all available historical daily data.
|
|
49
|
+
- Kept the heatmap as a fixed latest-53-week view while allowing older range aggregation and CSV export.
|
|
50
|
+
- Added distinct session counts for bounded ranges.
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
- Added baseline retry with backoff after transient session registry failures.
|
|
55
|
+
- Stopped in-flight scans and stale polling responses after plugin disposal or refresh races.
|
|
56
|
+
|
|
57
|
+
## [1.0.4] - 2026-08-19
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- Hardened loopback, same-origin, method, and process-token checks for dashboard API routes.
|
|
62
|
+
- Allowed same-origin browser balance GET requests that omit `Origin` while retaining token protection.
|
|
63
|
+
- Standardized English date buckets, range filters, streaks, heatmap dates, and export timestamps on UTC.
|
|
64
|
+
|
|
65
|
+
[1.0.8]: https://github.com/ParticleLight/dsh-all-usage/releases/tag/v1.0.8
|
|
66
|
+
[1.0.7]: https://github.com/ParticleLight/dsh-all-usage/releases/tag/v1.0.7
|
|
67
|
+
[1.0.6]: https://github.com/ParticleLight/dsh-all-usage/releases/tag/v1.0.6
|
|
68
|
+
[1.0.5]: https://github.com/ParticleLight/dsh-all-usage/releases/tag/v1.0.5
|
|
69
|
+
[1.0.4]: https://github.com/ParticleLight/dsh-all-usage/releases/tag/v1.0.4
|
package/README.md
CHANGED
|
@@ -17,6 +17,18 @@ DeepSeek Harness 全量用量看板:按模型、供应商、工作区和时间
|
|
|
17
17
|
- **时间范围**:今日、近 30 天、近 90 天、全部,或在全部可扫描历史日数据中自定义起止日期;热力图始终展示最近 53 周
|
|
18
18
|
- **工作区别名**:在侧栏入口打开看板后管理,持久化保存到 $DSH_HOME/storages 的 KV 单元 `all_usage_aliases`
|
|
19
19
|
- **界面语言**:在看板顶部切换中文与 English;选择会保存到浏览器本地
|
|
20
|
+
- **完整历史与增量重建**:基线扫描全部可读历史会话;独立用量账本同时作为每会话游标——未变化的会话直接复用账本,新增事件只增量回填,长历史重启不再全量重建
|
|
21
|
+
- **重启免读**:用持久化日志的 revision 作为每会话的变更信号(只读头部行 + stat,不读全量)——日志未变的会话重启时连事件都不读,直接从账本复用;仅日志变化(新增/修改)的会话才做增量读取
|
|
22
|
+
- **Token 口径**:输入按「未含缓存命中」计,缓存命中 / 写入与推理独立成桶;全 0 用量的重放事件不会覆盖已记录的真实用量,仅缓存命中的请求也会计入
|
|
23
|
+
|
|
24
|
+
### 最近更新
|
|
25
|
+
|
|
26
|
+
**v1.0.8**
|
|
27
|
+
|
|
28
|
+
- 重启免读:用持久化日志的 revision 做每会话变更信号,未变化会话重启时完全不读事件,直接复用账本;仅在日志变化时增量读取
|
|
29
|
+
- 账本记录持久化每会话的日志 revision(旧记录首次重读后自动回填)
|
|
30
|
+
|
|
31
|
+
完整版本记录见 [CHANGELOG.md](CHANGELOG.md)。
|
|
20
32
|
|
|
21
33
|
### 截图 / Screenshots
|
|
22
34
|
|
|
@@ -26,7 +38,7 @@ DeepSeek Harness 全量用量看板:按模型、供应商、工作区和时间
|
|
|
26
38
|
|
|
27
39
|
### 安装
|
|
28
40
|
|
|
29
|
-
本插件是标准的 DSH 社区插件包(声明 `dsh.bundle` manifest 与 Web Client
|
|
41
|
+
本插件是标准的 DSH 社区插件包(声明 `dsh.bundle` manifest 与 Web Client),数据来自持久化会话日志和独立用量账本,安装后自动回填历史。
|
|
30
42
|
|
|
31
43
|
#### 官方插件命令(推荐)
|
|
32
44
|
|
|
@@ -68,6 +80,7 @@ dsh plugin --profile web add github:ParticleLight/dsh-all-usage
|
|
|
68
80
|
- 按日范围统计会保留全部可读取历史会话的有使用记录日期;热力图仅作为最近 53 周的固定视图窗口
|
|
69
81
|
- 会话删除后,已成功 flush 的用量仍从独立账本恢复;会话销毁提示和周期对账只负责触发重建,不会删除账本记录
|
|
70
82
|
- 同一会话的同一 `turn / step` 只保留一份最终 usage;重试或替换消息会替换旧贡献,不重复累计
|
|
83
|
+
- 输入 Token 按「未含缓存命中」计(缓存命中 / 写入独立成桶);全 0 用量的重放事件不会覆盖已记录的真实用量,纯缓存命中的请求仍会计入
|
|
71
84
|
- 看板中的总处理量 = 输入 + 输出 + 缓存读写 + 推理;缓存命中表示复用的上下文 Token,不等于新生成 Token 或实际费用
|
|
72
85
|
- 余额查询走 DeepSeek 官方 `/user/balance` 接口;未配置 API Key 时卡片显示引导文案
|
|
73
86
|
- 仅统计能归属到已注册工作区(按会话 cwd 匹配)的会话
|
|
@@ -90,10 +103,22 @@ A full usage dashboard for DeepSeek Harness. Analyze tokens, cache behavior, acc
|
|
|
90
103
|
- **Time ranges**: today, last 30 days, last 90 days, all time, or a custom start/end date across all available historical daily data; the heatmap always shows the latest 53 weeks
|
|
91
104
|
- **Workspace aliases**: manage aliases from the sidebar dashboard; values persist in the $DSH_HOME/storages KV cell `all_usage_aliases`
|
|
92
105
|
- **Interface language**: switch between Chinese and English from the dashboard header; your choice persists locally in the browser
|
|
106
|
+
- **Full history & incremental rebuild**: the baseline scans every readable historical session; the durable usage ledger doubles as a per-session cursor, so unchanged sessions are reused straight from the ledger and only newly appended events are folded — long histories restart without a full rebuild
|
|
107
|
+
- **Restart with no re-read**: the persisted log revision (a header-line + stat via `sessionPersistence.listSnapshots()`) acts as a per-session change signal — sessions whose log is unchanged are applied from the ledger on restart without reading their events at all; only changed/new sessions are read incrementally
|
|
108
|
+
- **Token accounting semantics**: input tokens are fresh (exclude cache hits/writes, which sit in separate buckets along with reasoning); all-zero usage replays never overwrite recorded usage, while cache-only requests still count
|
|
109
|
+
|
|
110
|
+
### Latest Update
|
|
111
|
+
|
|
112
|
+
**v1.0.8**
|
|
113
|
+
|
|
114
|
+
- Restart with no re-read: the persisted log revision acts as a per-session change signal, so unchanged sessions are applied from the ledger on restart without reading any events; only changed logs are read incrementally
|
|
115
|
+
- The ledger now stores each session's log revision; existing rows are re-read once and backfilled automatically
|
|
116
|
+
|
|
117
|
+
See [CHANGELOG.md](CHANGELOG.md) for the complete version history.
|
|
93
118
|
|
|
94
119
|
### Installation
|
|
95
120
|
|
|
96
|
-
This is a standard DSH community bundle. It declares a `dsh.bundle` manifest and a web client, and backfills its data from persisted session logs after installation.
|
|
121
|
+
This is a standard DSH community bundle. It declares a `dsh.bundle` manifest and a web client, and backfills its data from persisted session logs and the durable usage ledger after installation.
|
|
97
122
|
|
|
98
123
|
#### Official plugin command (recommended)
|
|
99
124
|
|
|
@@ -135,6 +160,7 @@ The profile patch layer hot-reloads; save the file and refresh the page.
|
|
|
135
160
|
- Day-level range data retains every readable historical session date with tracked usage; the heatmap is only a fixed latest-53-week view
|
|
136
161
|
- After a session is deleted, successfully flushed usage is restored from the separate ledger; disposal hints and periodic reconciliation trigger rebuilds without deleting ledger rows
|
|
137
162
|
- For each session and logical `turn / step`, only the final usage contribution is kept; retries or replaced messages do not double-count
|
|
163
|
+
- Input tokens are fresh (exclude cache hits/writes, which sit in their own buckets); all-zero usage replays do not overwrite recorded usage and pure cache-read requests still count
|
|
138
164
|
- Processed tokens = input + output + cache read/write + reasoning; a cache hit means reused context, not newly generated tokens or actual cost
|
|
139
165
|
- Balance data comes from DeepSeek’s official `/user/balance` endpoint; the card shows guidance when no API key is configured
|
|
140
166
|
- English mode uses UTC for date buckets, range filters, streaks, heatmap dates, and export timestamps; Chinese mode uses local time
|
package/lib/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { randomBytes, timingSafeEqual } from 'node:crypto'
|
|
4
4
|
|
|
5
5
|
const name = 'dsh-all-usage'
|
|
6
|
-
const inject = ['sessionQuery', 'workspaceRegistry', 'timer', 'storage']
|
|
6
|
+
const inject = ['sessionQuery', 'workspaceRegistry', 'timer', 'sessionPersistence', 'storage']
|
|
7
7
|
|
|
8
8
|
// webServer route handlers do not inherit the connection API fence; keep this plugin
|
|
9
9
|
// local and require a browser-originated capability for state-changing reads/writes.
|
|
@@ -75,6 +75,10 @@ function apply(ctx) {
|
|
|
75
75
|
const settings = ctx.get('settings')
|
|
76
76
|
const storage = ctx.get('storage')
|
|
77
77
|
const webServer = ctx.get('webServer')
|
|
78
|
+
// v1.0.8: optional sessionPersistence exposes listSnapshots() — a cheap per-session
|
|
79
|
+
// revision (header line + stat, no full-log read) that lets the baseline skip
|
|
80
|
+
// re-reading unchanged sessions after a DSH restart.
|
|
81
|
+
const sessionPersistence = ctx.get('sessionPersistence')
|
|
78
82
|
|
|
79
83
|
// ---------- owned aggregation state ----------
|
|
80
84
|
const wsMeta = new Map()
|
|
@@ -259,6 +263,10 @@ function apply(ctx) {
|
|
|
259
263
|
const values = usageValues(usage)
|
|
260
264
|
const modelId = typeof model === 'string' && model !== '' ? model : '未知模型(历史记录缺少路由)'
|
|
261
265
|
const eventSeq = typeof seq === 'number' ? seq : -1
|
|
266
|
+
// v1.0.7: a usage event carrying no billable token in any bucket must not add a
|
|
267
|
+
// meaningless row nor wipe previously recorded real usage (cc-switch
|
|
268
|
+
// has_billable_tokens parity). Pure cache-read requests are billable and pass.
|
|
269
|
+
if (noValues(values)) return
|
|
262
270
|
const key = usageStepKey(sid, data, seq)
|
|
263
271
|
const previous = usageByStep.get(key)
|
|
264
272
|
// A late replay of an older raw event cannot replace the canonical later step.
|
|
@@ -297,7 +305,7 @@ function apply(ctx) {
|
|
|
297
305
|
function ledgerEventKey(event, index) {
|
|
298
306
|
return typeof event.seq === 'number' ? String(event.seq) : 'event:' + index
|
|
299
307
|
}
|
|
300
|
-
function buildLedgerRecord(session, workspaceId, source = 'scan') {
|
|
308
|
+
function buildLedgerRecord(session, workspaceId, source = 'scan', revision) {
|
|
301
309
|
const sid = session && typeof session.id === 'string' ? session.id : ''
|
|
302
310
|
const events = session && Array.isArray(session.events) ? session.events : []
|
|
303
311
|
if (sid === '' || workspaceId === undefined) return null
|
|
@@ -320,6 +328,9 @@ function apply(ctx) {
|
|
|
320
328
|
}
|
|
321
329
|
if (event.type !== 'assistant/message' || data === null || typeof data !== 'object' || data.usage === undefined) continue
|
|
322
330
|
const values = usageValues(data.usage)
|
|
331
|
+
// v1.0.7: all-zero usage rows carry no billable tokens and stay out of the
|
|
332
|
+
// durable ledger (cc-switch has_billable_tokens parity).
|
|
333
|
+
if (noValues(values)) continue
|
|
323
334
|
const modelId = typeof modelFromMessage(data, currentModel) === 'string' && modelFromMessage(data, currentModel) !== ''
|
|
324
335
|
? modelFromMessage(data, currentModel) : '未知模型(历史记录缺少路由)'
|
|
325
336
|
const eventSeq = typeof event.seq === 'number' ? event.seq : -1
|
|
@@ -328,7 +339,7 @@ function apply(ctx) {
|
|
|
328
339
|
if (previous !== undefined && eventSeq >= 0 && previous.seq > eventSeq) continue
|
|
329
340
|
usage.set(key, { key, seq: eventSeq, time: event.time, workspaceId, modelId, values })
|
|
330
341
|
}
|
|
331
|
-
return { version: LEDGER_VERSION, sessionId: sid, workspaceId, lastSeq: lastSeqOf(events), source, updatedAt: nextLedgerRevision(), turns: Array.from(turns.values()), usage: Array.from(usage.values()) }
|
|
342
|
+
return { version: LEDGER_VERSION, sessionId: sid, workspaceId, lastSeq: lastSeqOf(events), source, updatedAt: nextLedgerRevision(), lastRevision: typeof revision === 'string' ? revision : undefined, turns: Array.from(turns.values()), usage: Array.from(usage.values()) }
|
|
332
343
|
}
|
|
333
344
|
function normalizeLedgerRecord(raw, key) {
|
|
334
345
|
if (raw === null || typeof raw !== 'object' || raw.version !== LEDGER_VERSION || typeof raw.sessionId !== 'string' || raw.sessionId !== key) return null
|
|
@@ -346,7 +357,7 @@ function apply(ctx) {
|
|
|
346
357
|
}
|
|
347
358
|
const updatedAt = typeof raw.updatedAt === 'number' ? raw.updatedAt : 0
|
|
348
359
|
ledgerRevision = Math.max(ledgerRevision, updatedAt)
|
|
349
|
-
return { version: LEDGER_VERSION, sessionId: raw.sessionId, workspaceId: raw.workspaceId, lastSeq: typeof raw.lastSeq === 'number' ? raw.lastSeq : -1, source: raw.source === 'flush' ? 'flush' : 'scan', updatedAt, turns: Array.from(turnMap.values()), usage: Array.from(usageMap.values()) }
|
|
360
|
+
return { version: LEDGER_VERSION, sessionId: raw.sessionId, workspaceId: raw.workspaceId, lastSeq: typeof raw.lastSeq === 'number' ? raw.lastSeq : -1, source: raw.source === 'flush' ? 'flush' : 'scan', updatedAt, lastRevision: typeof raw.lastRevision === 'string' ? raw.lastRevision : undefined, turns: Array.from(turnMap.values()), usage: Array.from(usageMap.values()) }
|
|
350
361
|
}
|
|
351
362
|
function applyLedgerRecord(record) {
|
|
352
363
|
if (record === null || record === undefined) return
|
|
@@ -523,6 +534,22 @@ function apply(ctx) {
|
|
|
523
534
|
} catch (err) {
|
|
524
535
|
console.error('[all-usage] session list failed:', err)
|
|
525
536
|
}
|
|
537
|
+
// v1.0.8: cheap per-session change signal (header line + stat, no full-log read)
|
|
538
|
+
let snapshots = null
|
|
539
|
+
if (sessionPersistence !== undefined && typeof sessionPersistence.listSnapshots === 'function') {
|
|
540
|
+
try {
|
|
541
|
+
const rows = await sessionPersistence.listSnapshots()
|
|
542
|
+
if (Array.isArray(rows)) {
|
|
543
|
+
snapshots = new Map()
|
|
544
|
+
for (const row of rows) {
|
|
545
|
+
const rid = row && row.header && typeof row.header.id === 'string' ? row.header.id : undefined
|
|
546
|
+
if (rid !== undefined && row && typeof row.revision === 'string') snapshots.set(rid, row.revision)
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
} catch (err) {
|
|
550
|
+
console.error('[all-usage] session persistence snapshots unavailable:', err)
|
|
551
|
+
}
|
|
552
|
+
}
|
|
526
553
|
if (disposed || generation !== aggregationGeneration) return
|
|
527
554
|
if (setupFailed || !Array.isArray(records)) {
|
|
528
555
|
// A transient registry failure must not be reported as a completed empty scan.
|
|
@@ -560,18 +587,60 @@ function apply(ctx) {
|
|
|
560
587
|
if (disposed || generation !== aggregationGeneration) return
|
|
561
588
|
try {
|
|
562
589
|
if (sessionSeq.has(sid)) return
|
|
590
|
+
// v1.0.8: when the persisted log revision is unchanged since the last ledger
|
|
591
|
+
// write, the whole readSession (full event transfer) is skipped — the ledger
|
|
592
|
+
// record is applied directly and the live feed keeps catching new events.
|
|
593
|
+
const previousRecord = ledgerRecords.get(sid)
|
|
594
|
+
const revision = snapshots === null ? undefined : snapshots.get(sid)
|
|
595
|
+
if (previousRecord !== undefined && typeof previousRecord.lastRevision === 'string' && typeof revision === 'string' && revision === previousRecord.lastRevision) {
|
|
596
|
+
applyLedgerRecord(previousRecord)
|
|
597
|
+
for (const item of previousRecord.usage) {
|
|
598
|
+
usageByStep.set(item.key, { seq: item.seq, wsId: item.workspaceId, time: item.time, values: item.values, modelId: item.modelId, sid })
|
|
599
|
+
}
|
|
600
|
+
sessionSeq.set(sid, previousRecord.lastSeq)
|
|
601
|
+
sessionCount.add(sid)
|
|
602
|
+
return
|
|
603
|
+
}
|
|
563
604
|
const snap = await ctx.sessionQuery.readSession(sid)
|
|
564
605
|
if (disposed || generation !== aggregationGeneration) return
|
|
565
606
|
if (snap && Array.isArray(snap.events)) {
|
|
566
|
-
|
|
607
|
+
// v1.0.7: incremental seed — the durable ledger doubles as a per-session
|
|
608
|
+
// cursor (cc-switch session_log_sync mtime+offset parity). An unchanged
|
|
609
|
+
// session applies its canonical record directly and never re-folds;
|
|
610
|
+
// a changed session seeds the previous record once, then folds only the
|
|
611
|
+
// new tail (previously every listed session was re-read and fully rebuilt).
|
|
612
|
+
const currentLastSeq = lastSeqOf(snap.events)
|
|
613
|
+
const previous = ledgerRecords.get(sid)
|
|
614
|
+
if (previous !== undefined) {
|
|
615
|
+
applyLedgerRecord(previous)
|
|
616
|
+
// Seed usageByStep so a later live retry of an already-recorded step
|
|
617
|
+
// reverses the ledger contribution instead of double-counting it.
|
|
618
|
+
for (const item of previous.usage) {
|
|
619
|
+
usageByStep.set(item.key, { seq: item.seq, wsId: item.workspaceId, time: item.time, values: item.values, modelId: item.modelId, sid })
|
|
620
|
+
}
|
|
621
|
+
if (previous.lastSeq >= currentLastSeq) {
|
|
622
|
+
// Content unchanged despite a revision change (rare: ctime-only churn):
|
|
623
|
+
// refresh the stored revision so future restarts can skip the read.
|
|
624
|
+
if (revision !== undefined && previous.lastRevision !== revision) {
|
|
625
|
+
previous.lastRevision = revision
|
|
626
|
+
void persistLedgerRecord(previous)
|
|
627
|
+
}
|
|
628
|
+
sessionSeq.set(sid, previous.lastSeq)
|
|
629
|
+
sessionCount.add(sid)
|
|
630
|
+
return
|
|
631
|
+
}
|
|
632
|
+
foldEvents(wsId, snap.events, previous.lastSeq, sid)
|
|
633
|
+
} else {
|
|
634
|
+
foldEvents(wsId, snap.events, undefined, sid)
|
|
635
|
+
}
|
|
636
|
+
const ledger = buildLedgerRecord({ id: sid, header: record.header, events: snap.events }, wsId, 'scan', revision)
|
|
567
637
|
const canonical = ledger === null ? ledgerRecords.get(sid) : storeLedgerRecord(ledger)
|
|
568
638
|
if (canonical === ledger) {
|
|
569
639
|
void persistLedgerRecord(ledger)
|
|
570
|
-
foldEvents(wsId, snap.events, undefined, sid)
|
|
571
640
|
} else if (canonical !== undefined) {
|
|
572
641
|
applyLedgerRecord(canonical)
|
|
573
642
|
}
|
|
574
|
-
sessionSeq.set(sid,
|
|
643
|
+
sessionSeq.set(sid, currentLastSeq)
|
|
575
644
|
sessionCount.add(sid)
|
|
576
645
|
}
|
|
577
646
|
} catch (err) {
|
|
@@ -786,6 +855,18 @@ function apply(ctx) {
|
|
|
786
855
|
processedTotal: 'input + output + cacheRead + cacheWrite + reasoning',
|
|
787
856
|
cacheRead: 'reused context tokens; not newly generated output',
|
|
788
857
|
cacheWrite: 'tokens written into a provider cache',
|
|
858
|
+
// v1.0.7: structured, machine-readable accounting semantics (cc-switch
|
|
859
|
+
// input_token_semantics parity). DSH reports input as fresh (cache read /
|
|
860
|
+
// cache write sit in their own buckets) — verified against real ledger data;
|
|
861
|
+
// reasoning is bucketed separately from output and assumed non-overlapping.
|
|
862
|
+
semantics: {
|
|
863
|
+
input: 'fresh (excludes cache-read and cache-write tokens, bucketed separately)',
|
|
864
|
+
buckets: ['input', 'output', 'cacheRead', 'cacheWrite', 'reasoning'],
|
|
865
|
+
inputIncludesCache: false,
|
|
866
|
+
cacheBucketed: true,
|
|
867
|
+
reasoningSeparate: true,
|
|
868
|
+
gate: 'all-zero usage rows are ignored; pure cache-read requests still count',
|
|
869
|
+
},
|
|
789
870
|
},
|
|
790
871
|
totals: { turns: totals.turns, sessions: sessionCount.size, input: totals.input, output: totals.output, cacheRead: totals.cacheRead, cacheWrite: totals.cacheWrite, reasoning: totals.reasoning },
|
|
791
872
|
perWorkspace: Array.from(perWorkspace, (p) => ({ workspaceId: p[0], turns: p[1].turns, input: p[1].input, output: p[1].output, cacheRead: p[1].cacheRead, cacheWrite: p[1].cacheWrite, reasoning: p[1].reasoning })),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-all-usage",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "DeepSeek Harness usage dashboard with model, provider, workspace, cache, balance, and CSV insights",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"files": [
|
|
38
38
|
"lib/index.js",
|
|
39
39
|
"lib/client.js",
|
|
40
|
+
"CHANGELOG.md",
|
|
40
41
|
"cordis.patch.yml",
|
|
41
42
|
"assets"
|
|
42
43
|
],
|