dsh-all-usage 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/CHANGELOG.md CHANGED
@@ -4,6 +4,114 @@ All notable changes to `dsh-all-usage` are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ### Added
8
+
9
+ - DeepSeek peak/off-peak billing as a first-class, versioned **temporal pricing plan**: the cost schema is v2, and each cost snapshot records the billing instant, its time source (request-context / usage-event), the UTC band (peak / off-peak), the policy id, and a policy hash. Request logs show the band and UTC billing time, and the cost settings panel marks which usage models follow the DeepSeek band plan.
10
+ - Built-in first-party DeepSeek profiles (deepseek-v4-flash, deepseek-v4-flash-vision-exp, deepseek-v4-pro) with explicit per-band rates for the UTC windows 01:00-04:00 and 06:00-10:00 on weekdays; the peak rates are stored as data, never derived as an automatic discount rule.
11
+ - UTC band boundaries are half-open: 00:59:59 off-peak, 01:00:00 peak, 03:59:59 peak, 04:00:00 off-peak, 06:00:00 peak, 10:00:00 off-peak; weekends are off-peak. Band selection uses UTC fields only, so viewer timezones, DST, and fractional-hour zones cannot change a result.
12
+ - Deterministic reuse: a later usage sample for the same turn/step reuses the previous cost snapshot only when identity, token buckets, band, policy id/hash, and applicability all match, so a message crossing a peak boundary never inherits the chunk price.
13
+ - Requests preferred as the billing instant when the request/context time matches the usage turn/step; otherwise the usage event time is the auditable fallback. Both are persisted in the snapshot.
14
+ - Controlled DeepSeek temporal reconciliation: first-party DeepSeek snapshots are re-priced against the usage instant on baseline/backfill/sync, v1 snapshots are migrated to the v2 shape in memory and in the persisted ledger, and a plan whose effective window does not cover the instant fails closed as unsupported (temporal-price-history-unavailable) instead of guessing.
15
+ - Route safety boundary: the band plan applies only to first-party deepseek routes or routes explicitly mapped to the DeepSeek official entry; reseller routes (OpenRouter and other gateways) keep the static official price and are labelled route-not-official.
16
+
17
+ - Respect the official effective instant: the built-in DeepSeek band plan starts exactly at 2026-08-16T16:00:00Z (V4-Flash-Vision-Exp at 2026-08-21), and usage before it fails closed as unsupported instead of inheriting today's V4 rates.
18
+ - Persist the request-context archive inside ledger records and seed incremental folds from it after a restart, so an open request whose context arrived in a previous batch keeps its peak/off-peak billing instant.
19
+ - Clear stale request-context instants and route identity on full folds (baseline rebuilds and live resyncs), so a history rewrite that removes a request no longer leaves its band attached to later usage.
20
+ - Keep priced v2 snapshots auditable across catalog refreshes: the policy hash no longer includes live entry rates and reconciliation never rewrites priced history; an explicit repriceTemporal=true request re-estimates every snapshot.
21
+ - Support an ordered policy archive (policies: [...]) with non-overlapping effective windows, reject overlapping rules across rules (JSON array order can never decide a price), keep an invalid temporal config visible and fail closed instead of silently falling back to the built-in profile, and compare temporal plans in the duplicate-candidate check so conflicting band plans resolve ambiguous.
22
+ - Include pricingAt, pricingTimeSource, band, policy id and policy hash in the reconciliation equivalence check and in the CSV audit export.
23
+
24
+ - Persist the invalid temporal-config sentinel through the serialize/normalize cycle: a rejected temporalPricing edit stays fail-closed across restarts instead of re-enabling the built-in DeepSeek profile.
25
+ - Rebuild a session atomically on authoritative live resyncs: usage samples, turn records, date indexes and cost aggregates are removed before the snapshot is folded, so usage deleted by a rewritten history disappears from the aggregate (lagging snapshots keep upsert semantics until the follow-up resync aligns).
26
+ - Base the live resync cursor on the snapshot tail (merged with an absorbed fallback event) instead of the previous cursor, so truncated or rewritten history cannot skip over missing events that arrive later.
27
+ - Accept deepseek-official as a first-party catalog provider in the official provider rules, so its entries price and follow the band plan like deepseek.
28
+ - Validate that a priced snapshot's instant is still covered and band-consistent when deciding whether a policy still matches: snapshots that slipped into a gap (same id/hash, window moved) fail closed instead of staying priced.
29
+ - Include pricingAt and pricingTimeSource in the live/incremental reuse check, so audit metadata refreshes when the request instant or its source changes even within one band.
30
+ - Use real LRU eviction (delete + set) for the bounded request-context archive, applying the same 512-entry cap to the live fold, the persisted ledger, and deserialization.
31
+
32
+ - An authoritative live resync (full snapshot or a snapshot that provably contains the triggering event with a monotonic sequence) rebuilds the session's ledger record from the snapshot, replaces it in state and persists it, so a restart after a live history rewrite cannot resurrect deleted usage; persist failures keep explicit dirty/recovery state and surface as resync-ledger-persist-failed.
33
+ - Live resync authority is now proof-based: snapshots that only have a higher tail, or events without a usable sequence, are treated as lagging (upsert the event, stay pending, align on the follow-up resync) instead of being destructively replaced.
34
+
35
+ - Show real vendor brand icons instead of neutral dots for models in the request log, the selected-call details, the model summary table, and the cost-settings match table. The 11 brand SVGs live in assets/model-icons/ with a manifest (provider aliases, model prefixes, exact overrides, source and licence) and are validated and embedded as data: URIs by scripts/build-client.mjs, so the runtime never reads local paths or the network. Model namespaces win over the DSH provider name so reseller and gateway routes are attributed by the model they actually served; unknown or mixed-brand rows keep the neutral fallback, and workspace colour dots plus chart legend dots are unchanged.
36
+
37
+ - Harden the model brand icons: the load-failure flag is scoped to one icon identity (a shared component that later shows another brand retries instead of staying neutral), an unrecognised actualModel no longer inherits the requested model's brand, model prefixes match on token boundaries only (o10-preview and o3x stay neutral), the resolution cache is bounded, and labelled icons expose the brand through the image's accessible name instead of a title on an aria-hidden host.
38
+ - Build-time SVG validation now decodes XML entities before checking, rejects unquoted URI attributes, external CSS url()/image-set() and @import targets (including escaped/commented/CDO-CDC/namespaced <svg:style> forms), DOCTYPE/CDATA/processing instructions and every external-resource element; the CSS checks run only on style attributes and <style> bodies so icon text and comments stay inert. It lives in scripts/svg-guard.mjs so the tests exercise the exact same guard with adversarial fixtures.
39
+ - Bundle the icon provenance required for release: the manifest pins the upstream commit, records per-file upstream paths and modification flags, and assets/model-icons/LICENSE.upstream-lobe-icons.txt ships the MIT notice, copyright, trademark disclaimer and the local modifications. The build fails if the pinned revision, licence file or per-file attribution is missing.
40
+
41
+ ## [1.1.4] - 2026-09-03
42
+
43
+ ### Fixed
44
+
45
+ - Hardened the model-icon build guard after an independent audit: the CSS checks now reject CDO/CDC-adjacent `@import`, external `@import` inside namespaced `<svg:style>` elements, CSS-escaped `url(` identifiers and external `image-set()` targets, all matching Blink's own tokenizer. Style-attribute `url(...)` references are checked on both the raw and entity-decoded document so `&quot;` quoting cannot split a payload while encoded `@import` is still caught after decoding.
46
+ - CSS `url()`/`image-set()` checks now run only on style attributes and `<style>` bodies, so provenance comments, `<text>` content and CSS comments stay inert (no false positives); the adversarial fixtures assert the rejection reason instead of accepting any error.
47
+ - The model filter dropdown now renders the vendor brand icon for each model option (actual model first, neutral fallback for unknown/private models) instead of the generic line icon, and the three usage-dashboard screenshots were refreshed to the current UI.
48
+
49
+ ## [1.1.3] - 2026-09-01
50
+
51
+ ### Added
52
+
53
+ - Make Cost Statistics tier-aware: show expandable official rate schedules and support validated context bands in explicit price overrides.
54
+ - Added deterministic usage invariants and a redacted fixture replay command covering failed requests, orphan usage chunks, cache buckets, replacement semantics, and exact expected totals.
55
+ - Added a Node 22/24 CI matrix with package-content checks and required real DSH runtime smoke coverage for DSH 0.1.1-rc.1 and 0.1.1-rc.2.
56
+ - Added community issue templates for data inconsistencies, plugin startup failures, and cost calculation issues.
57
+
58
+ ### Performance Optimizations
59
+
60
+ This release reduces historical scan work, storage write amplification, and Dashboard render churn while preserving durable-ledger recovery guarantees.
61
+
62
+ - Avoid rebuilding and writing a clean session's derived ledger at `session/flush`; queue and coalesce dirty records, then drain pending writes during disposal.
63
+ - Store derived ledger records in 32 stable-hash JSON units so a session update rewrites only its shard; retain the legacy single-unit migration path.
64
+ - Build scoped query and fixed 53-week heatmap results from ingest-time date/workspace/model cubes; use exact BigInt decimal accumulators and a minimal heatmap projection instead of rescanning usage rows or parsing cost strings on cold reads.
65
+ - Isolate heatmap pointer tracking from the dashboard render path: memoize the 53-week calendar, cells, lookup maps, charts, records, and pricing dialog, while tooltip coordinates are coalesced through refs and requestAnimationFrame, with a throttled fallback for background tabs.
66
+ - Generate the shipped browser entry from readable `src/client.js` with pinned Terser during prepack, reducing the current raw client artifact from about 276 KB to about 177 KB; transport gzip/Brotli remains a DSH host concern.
67
+
68
+ ### Correctness
69
+
70
+ - Keep the usage ledger debounce timer referenced while write waiters are pending: an unreferenced timer let the event loop settle before `persistLedgerRecord` could run, cancelling Node 22 test runs and short-lived processes.
71
+ - Derive the latest session sequence from the tail event instead of scanning the full session log on live flush and again while building the ledger record.
72
+ - Use the previous ledger cursor for safe append-only tail folding and fall back to a full rebuild when sequence continuity or turn replacement is uncertain.
73
+ - Wait for the persisted pricing configuration (and the ledger it backfills) before serving pricing reads and writes, so an early API call can neither report an empty config nor be silently overwritten by the load.
74
+ - Force a full historical rebuild when the workspace behind a session path was recreated with a different id; the previous record can no longer be reused under the wrong workspace, and ledger rows whose top-level workspace differs from their historical items are marked unusable and rebuilt once.
75
+ - Keep the pricing refresh classification order intact: a pricing revision change always wins over a concurrent data change so summary costs and the open settings panel cannot consume a partially-consumed baseline.
76
+ - Refresh the open cost-settings panel when the pricing revision changes, merging fresh catalog data while preserving unsaved local mappings and overrides.
77
+ - Invalidate every in-flight official-model search when a mapping row is deleted (row generation bump), so a stale response can never populate a shifted row even when per-index sequence numbers collide.
78
+ - Skip the pricing revision bump when a catalog sync succeeds with an unchanged catalog hash and no newly priced usage.
79
+ - Round hourly bucket indices against the requested range so fractional-hour zone offsets (Lord Howe) never drop or merge events, and emit real bucket boundaries as trend labels so spring-forward hours carry the true 03:00 label instead of drifting 30 minutes off.
80
+ - Mark mixed-workspace ledger records as unfoldable so both the scan tail path and session flush rebuild them instead of persisting historical items under the wrong workspace.
81
+ - Exclude the per-fetch fetchedAt stamp from the catalog content hash, so syncing identical models.dev contents keeps pricingRevision (and query caches) stable.
82
+ - Wait for the persisted pricing and ledger state before serving the full snapshot, so the first payload cannot embed a default empty pricing summary.
83
+ - Bump the official-model search row generation when the settings panel closes, so in-flight responses cannot populate a reopened panel.
84
+ - Require safe-integer event sequences everywhere sequences gate folding or fast-forward comparisons, so contract-external values such as Infinity cannot freeze later flushes or poison the live-event cursor.
85
+ - Keep the mixed-workspace upgrade flag through pricing backfills too: repairing costs must never re-enable tail folding of a record whose historical items still belong to another workspace.
86
+ - Invalidate in-flight official-model searches whenever the catalog is replaced (manual sync, open-panel refresh, close/reopen), not only when a mapping row is deleted.
87
+ - Normalize live-fallback sequences to non-negative safe integers and give positional keys to invalid events, so contract-external sequences cannot overwrite ledger turns or poison the live cursor; ledger rows with non-safe sequences are marked for rebuild.
88
+ - Sort the catalog by its stable key before truncating, so models.dev catalogs above the entry cap keep the same models and content hash regardless of upstream object order.
89
+ - Enforce the ledger sequence contract (-1 sentinel or non-negative safe integer) when loading persisted records, flag old Infinity/NaN/negative keys and sequences for rebuild, and never clear the rebuild flag during cost repacking or ledger recovery.
90
+ - Resolve normalized duplicate catalog entries deterministically (stable sort with conflict groups comparing full content, provider id case-normalized) so enumeration order can no longer change which price survives.
91
+ - Extend the ledger sequence contract to every write and read path: oversized numeric keys (past Number.MAX_SAFE_INTEGER) and unsafe numbers in usage/turn data are normalized at the entry points and flagged for rebuild; the rebuild reason is persisted so a source-read failure followed by a restart can never silently reuse a polluted record.
92
+ - Persist the rebuild reason for legacy-version and invalid-updated-at records too, not only sequence/workspace damage, so a source-read failure followed by restart cannot fast-path a record that was upgraded in memory.
93
+ - Recognise any non-canonical numeric text (floats, exponents, negatives) in old event keys as pollution, and forbid both memory and ledger tail-folding when an incremental log contains invalid sequences anywhere before the fold start.
94
+ - Refuse to tail-fold a flush whose log tail does not advance and whose history contains invalid sequences: the whole usage index is rebuilt from source so the in-memory aggregate and the persisted ledger cannot disagree.
95
+ - Detect invalid sequences on every dirty flush (not only when the tail did not advance), so a history rewrite followed by a normal appended tail also rebuilds instead of folding stale in-memory rows.
96
+ - Persist an invalid-flush-sequence rebuild flag on the stale ledger record before triggering the full rebuild, so a lagging persistence revision cannot fast-path the baseline back to the old data.
97
+ - Match Infinity/NaN pollution only as bare whole values, keeping composite keys such as Infinity-session:step:1:1 on the revision fast path.
98
+ - Keep the server-persisted pricing auto-sync flag as the single truth: the client stops seeding the draft from its own local UI state, so saving unrelated settings cannot revert the server value.
99
+ - Move in-flight official-model search state (timers, sequence guards, results) when mappings are deleted, so a stale response can no longer populate a shifted row.
100
+ - Restrict pricing revision bumps to cost-affecting changes: sync attempts and failed attempts no longer invalidate scoped query caches or records cursors, while the client treats pricing changes as a full snapshot refresh so summary costs stay fresh.
101
+ - Bucket hourly rows by the local hour boundary at the event's own offset, keeping both repeated hours of a DST fall-back day distinct.
102
+ - Add a CI guard that rejects a missing or untracked generated client bundle (`git ls-files --error-unmatch`) before the determinism diff.
103
+ - Initialized the durable ledger revision clock and reject persisted ledger records whose `updatedAt` is not finite.
104
+ - Require HTTP socket peers to be loopback before accepting a request, including IPv4-mapped loopback addresses.
105
+ - Preserve route-specific pricing mappings through the `identityKey` field, while accepting legacy `usageIdentityKey` data.
106
+ - Add validated context-tiered model pricing using the request input context; malformed schedules remain unsupported instead of producing a guessed estimate.
107
+ - Require `webServer` before Host activation so the dashboard routes cannot silently disappear when the service mounts late.
108
+ - Migrate legacy tiered flat cost snapshots to `unsupported`, reject timestamps outside JavaScript's TimeClip range, and exercise the real SessionStore append/flush firehose in runtime smoke tests.
109
+ - Require manual npm recovery dispatches to provide and verify the target release tag and full commit SHA.
110
+
111
+ ### Documentation
112
+
113
+ - Documented the DSH compatibility matrix, the difference between local replayable statistics and provider billing, cache/token examples, and the fixture workflow.
114
+
7
115
  ## [1.1.2] - 2026-08-30
8
116
 
9
117
  ### Added
@@ -127,6 +235,9 @@ All notable changes to `dsh-all-usage` are documented here.
127
235
  - Allowed same-origin browser balance GET requests that omit `Origin` while retaining token protection.
128
236
  - Standardized English date buckets, range filters, streaks, heatmap dates, and export timestamps on UTC.
129
237
 
238
+ [1.1.4]: https://github.com/ParticleLight/dsh-all-usage/releases/tag/v1.1.4
239
+ [1.1.3]: https://github.com/ParticleLight/dsh-all-usage/releases/tag/v1.1.3
240
+ [1.1.2]: https://github.com/ParticleLight/dsh-all-usage/releases/tag/v1.1.2
130
241
  [1.1.0]: https://github.com/ParticleLight/dsh-all-usage/releases/tag/v1.1.0
131
242
  [1.0.9]: https://github.com/ParticleLight/dsh-all-usage/releases/tag/v1.0.9
132
243
  [1.0.8]: https://github.com/ParticleLight/dsh-all-usage/releases/tag/v1.0.8
package/README.md CHANGED
@@ -21,28 +21,94 @@ DeepSeek Harness 全量用量看板:按模型、供应商、工作区和时间
21
21
  - **完整历史与增量重建**:基线扫描全部可读历史会话;独立用量账本同时作为每会话游标——未变化的会话直接复用账本,新增事件只增量回填,长历史重启不再全量重建
22
22
  - **重启免读**:用持久化日志的 revision 作为每会话的变更信号(只读头部行 + stat,不读全量)——日志未变的会话重启时连事件都不读,直接从账本复用;仅日志变化(新增/修改)的会话才做增量读取
23
23
  - **数据健康与按需刷新**:扫描完成后浏览器只检查轻量状态版本,只有用量、别名或同步状态变化时才拉完整历史;显示本次数据更新时间、历史扫描健康、revision 免读、实际读取、账本恢复和失败,网络异常保留上次成功数据并可重试
24
- - **性能优化**:Host 使用 local/UTC 日期索引、revision-scoped snapshot/records 缓存和可回收的实时事件队列;Client scope 聚合、统计行和官方模型检索做防抖/缓存,并只渲染当前明细页签
24
+ - **性能优化**:Host ingest 时维护 local/UTC 的日期、工作区、模型身份日级 cube 与单日小时桶;scope 查询按 bucket 合并,成本使用精确 BigInt 小数累加,53 周热力图只生成实际需要的字段,并继续使用 revision-scoped snapshot/records 缓存和可回收的实时事件队列;Client 将热力图、tooltip、趋势、环形图、请求日志和定价对话框隔离为 memoized 边界,指针坐标通过 ref + requestAnimationFrame 更新,不再触发整页重渲染;浏览器入口在打包前确定性压缩
25
25
  - **趋势折线图**:按当前范围、时区、工作区、供应商和模型显示输入、缓存读写、输出、推理及总处理量;单日范围按小时聚合并显示小时轴,跨日范围按日聚合;使用平滑单调曲线与入场动画,悬停查看精确值,图例可切换曲线,点击点位进入当日明细
26
26
  - **统一筛选与审计**:工作区、供应商、模型和日期筛选贯穿摘要、热力图、趋势、表格与 CSV;工作区、供应商、模型三个筛选维度可独立自由组合,工作区、供应商和模型选项只展示当前日期范围内实际使用过的值;切换范围后失效筛选会自动清除;请求日志以紧凑分页表常驻显示,选择单条后查看分组 Token 详情
27
27
  - **Token 口径**:输入按「未含缓存命中」计,缓存命中 / 写入与推理独立成桶;全 0 用量的重放事件不会覆盖已记录的真实用量,仅缓存命中的请求也会计入
28
28
  - **成本口径**:模型价格来自 models.dev 的 USD / 1M Token 目录;成本快照按 DSH 已归一化的 fresh input 和四类价格桶计算,倍率只作用于最终总价,已有正成本历史不会因价格更新重算;只按模型选择官方厂商条目,未找到官方价格时显示为未计价
29
29
 
30
- ### 最近更新
30
+ ### 兼容性与已知限制
31
+
32
+ - **运行环境**:需要 Node.js `>=22 <25`;CI 会在 Node 22 和 Node 24 上运行测试、语法检查和 npm 包内容检查。
33
+ - **DSH 兼容**:`package.json` 声明 DSH runtime `>=0.1.1-rc.1 <0.1.2`,已使用 `0.1.1-rc.2` 和 `0.1.1-rc.1` 的真实 Cordis 服务链验证。
34
+ - **Web 服务依赖**:Host 将 `webServer` 声明为必需依赖,确保服务晚挂载时由 DSH 等待后再执行插件;该包面向 DSH Web profile,不提供无 WebServer 的 headless 路由。HTTP 守卫还会检查真实 socket peer,反向代理只有在连接本身来自 loopback 时才会被接受。
35
+
36
+ | DSH runtime | Node.js 支持 | 真实 Cordis smoke | 结论 |
37
+ | --- | --- | --- | --- |
38
+ | `0.1.1-rc.2` | `>=22 <25`,CI 覆盖 22/24 | 通过(当前 Node 24) | 已声明、已验证 |
39
+ | `0.1.1-rc.1` | `>=22 <25`,CI 覆盖 22/24 | 通过(当前 Node 24) | 已声明、已验证 |
40
+ | 其他版本 | `>=22 <25` | 未测试 | 不在已验证矩阵内 |
41
+
42
+ 未列出的 DSH 版本不代表一定不兼容;提交问题时请附 DSH、Node.js 和插件版本。
43
+
44
+ - **中断请求**:上游请求被中断时可能只有 `assistant/chunk` 的 usage,没有最终 `assistant/message`;本插件会保留该 chunk 用量。同一 `turn / step` 后续出现最终 message 时,message 会替换 chunk。若上游完全没有 usage 事件,则无法从响应内容精确恢复 Token。
45
+ - **估算成本**:成本是基于 models.dev 价格和 DSH usage 桶的估算,不是供应商账单;目录不可用或模型没有官方匹配时不会猜测价格,而是显示未计价。缓存读取、缓存写入和 reasoning 的口径取决于 DSH 上游事件。
46
+ - **分层价格**:models.dev 的 tiered/context-dependent 价格按本次请求的输入上下文(fresh input + cache read + cache write)选择对应档位;阈值边界遵循目录定义,无法验证的异常 tier 仍显示为 unsupported。
47
+ - **历史边界**:只有能按 cwd 映射到已注册工作区的会话会进入统计;会话尚未成功 flush 前删除或损坏的日志无法由独立账本恢复。
48
+
49
+ ### 本地统计与官方账单
50
+
51
+ 本插件展示的是 DSH 本地事件日志上的可重放统计,不是供应商账单的镜像:
52
+
53
+ - 本地统计读取 DSH 的 `assistant/chunk`、最终 `assistant/message` 和其他会话事件,按同一 `turn / step` 去重和替换;官方账单可能按供应商自己的请求、分词器、舍入、折扣、免费额度和结算周期计算。
54
+ - 失败请求只要留下 usage chunk,就会进入本地统计;供应商是否对该失败请求收费,应以官方账单为准。
55
+ - 价格来自 models.dev 的公开模型目录和本地显式覆盖;目录价格、供应商实际价格、区域费率和账单折扣可能不同。成本字段应理解为估算值。
56
+ - 本地统计只包含能映射到已注册工作区的会话,并可能因日志损坏、清理或上游没有发出 usage 而少于官方账单。
31
57
 
32
- **v1.1.2**
58
+ ### 可复现事件示例
33
59
 
34
- - 模型与工作区环图及右侧列表新增 Token 数、成本和占比展示,其他分组同步合并成本
35
- - Host 增加日期索引、快照/日志查询缓存、实时事件恢复和写入生命周期保护
36
- - 成本设置官方模型检索增加防抖,新增 body、别名、时间戳和扫描失败边界校验
37
- - 更新插件自有截图清单,使用全量看板、成本设置和请求日志截图
60
+ 下面的事件是脱敏的最小示例;完整可运行数据见 [`fixtures/usage-events.json`](fixtures/usage-events.json)。
61
+
62
+ #### 失败请求仍保留 usage chunk
63
+
64
+ ~~~json
65
+ [
66
+ {"type": "assistant/chunk", "data": {"turn": 1, "step": 1, "chunk": {"type": "usage", "usage": {"inputTokens": 100, "outputTokens": 20}}}},
67
+ {"type": "request/error", "data": {"code": "upstream-failed"}}
68
+ ]
69
+ ~~~
70
+
71
+ 没有最终 `assistant/message` 时,chunk 仍计为一个本地调用;这不等于官方一定收费。
72
+
73
+ #### 孤立 usage chunk
74
+
75
+ ~~~json
76
+ {"type": "assistant/chunk", "data": {"turn": 1, "step": 1, "chunk": {"type": "usage", "usage": {"inputTokens": 7, "cacheReadTokens": 8}}}}
77
+ ~~~
78
+
79
+ 缺少 request/context 或 request/header 时,Token 仍可统计,但模型身份显示为 Unknown;插件不会从 Provider 名称或展示字符串猜测模型。
80
+
81
+ #### 缓存 Token 的四桶含义
82
+
83
+ ~~~json
84
+ {"inputTokens": 100, "outputTokens": 20, "cacheReadTokens": 40, "cacheWriteTokens": 5, "reasoningTokens": 3}
85
+ ~~~
86
+
87
+ 本地 processed total 为 `100 + 20 + 40 + 5 + 3 = 168`;成本只对 input、output、cacheRead、cacheWrite 四个桶定价,reasoning 不会再次加到 output。
88
+
89
+ 使用仓库中的 fixture 复现:
90
+
91
+ ~~~bash
92
+ node scripts/replay-fixture.mjs fixtures/usage-events.json
93
+ ~~~
94
+
95
+ 该命令会加载真实插件 Host、调用兼容 API、校验预期 Token/records,并输出不含敏感信息的摘要。
96
+
97
+ ### 报告问题
98
+
99
+ - [数据不一致 / Data inconsistency](.github/ISSUE_TEMPLATE/data-inconsistency.md)
100
+ - [插件启动失败 / Plugin startup failure](.github/ISSUE_TEMPLATE/startup-failure.md)
101
+ - [成本计算问题 / Cost calculation issue](.github/ISSUE_TEMPLATE/cost-calculation.md)
102
+
103
+ ### 最近更新
38
104
 
39
- **v1.1.1**
105
+ **v1.1.3**
40
106
 
41
- - 结构化模型身份与兼容 ledger v2:区分 Provider、请求模型和实际模型,旧账本自动升级
42
- - 统一 scope 查询:时间、时区、工作区、Provider、模型筛选同时作用于摘要、热力图、趋势、表格和 CSV
43
- - Token 趋势折线图:单日范围按小时、跨日范围按日;平滑单调曲线、分层入场动画、多桶图例切换、悬停精确值和点位审计钻取
44
- - **模型与工作区统计**:在明细表上方提供 Token 占比环形图、中心总量、Top 项目图例和 hover 明细;环段与右侧列表同时展示 Token、成本和占比,tooltip 会跟随鼠标位置
45
- - turn / step 审计明细:常驻请求日志标签、紧凑分页表、选中行分组详情、脱敏来源标记和当前筛选范围明细 CSV
107
+ - 成本统计支持经验证的 context-tiered 官方费率、可展开费率表和显式价格覆盖。
108
+ - **性能优化**:账本采用稳定分片、dirty flush 合并和 revision 快路径;查询、趋势和固定 53 周热力图改用写入时聚合索引,减少历史扫描、存储写放大和 Dashboard 重渲染。
109
+ - 定价目录、价格同步、模型检索和映射编辑强化确定性与并发刷新边界。
110
+ - 加强非法序列、旧账本、工作区重建、滞后 persistence revision 与复合账本键的恢复保护。
111
+ - 增加 Node 22/24、DSH rc.1/rc.2 runtime smoke、脱敏 fixture replay 和包内容发布门禁。
46
112
 
47
113
  完整版本记录见 [CHANGELOG.md](CHANGELOG.md)。
48
114
 
@@ -86,7 +152,7 @@ dsh plugin --profile web add github:ParticleLight/dsh-all-usage
86
152
 
87
153
  ### 架构
88
154
 
89
- - **Host 端**(`lib/index.js`):扫描持久化会话日志聚合用量(`turn/end` + `assistant/message.usage`),监听 `session/event` 实时折叠;通过 `webServer` 服务注册数据路由:
155
+ - **Host 端**(入口 `lib/index.js`,组装 `lib/plugin.js`):按职责拆分为 `aggregation.js`(聚合与查询)、`ledger.js`(持久账本)、`session-sync.js`(历史/实时同步)、`pricing-runtime.js`(运行时定价)、`balance.js`(余额)、`http.js`(安全路由);扫描 `turn/end`、`assistant/chunk` usage 和最终 `assistant/message.usage`,监听 `session/event` 实时折叠,并通过 `webServer` 服务注册数据路由:
90
156
  - `GET /api/all-usage` — 兼容统计快照
91
157
  - `GET /api/all-usage/status` — 轻量 revision 与同步健康状态
92
158
  - `GET /api/all-usage/query` — 按 scope 返回聚合、daily/hourly 趋势和 heatmap 数据;单日 scope 填充 `hourly`,跨日 scope 的 `hourly` 为空
@@ -95,13 +161,13 @@ dsh plugin --profile web add github:ParticleLight/dsh-all-usage
95
161
  - `POST /api/all-usage/alias` — 设置工作区别名
96
162
  - `GET /api/all-usage/pricing` — 查看 models.dev 同步状态、已用模型匹配和显式覆盖
97
163
  - `GET /api/all-usage/pricing/models?q=...` — 检索官方模型 ID 与名称匹配结果
98
- - `POST /api/all-usage/pricing` — 保存同步、mapping 和显式价格覆盖
164
+ - `POST /api/all-usage/pricing` — 保存同步、mapping 和显式价格覆盖(含 context tier 档位)
99
165
  - `POST /api/all-usage/pricing/sync` — 手动同步 models.dev 并回填未计价调用
100
- - **Client 端**(`lib/client.js`):`window.__ModuleLoader__` 工厂格式的浏览器 bundle,注册侧边栏「用量统计」入口(`sidebar.footer.action` 槽位)。所有 API 仅接受本机 loopback 请求并拒绝显式跨域请求;余额读取与别名写入还要求插件启动时生成、仅在当前进程有效的令牌(余额 GET 兼容浏览器省略 Origin)。英文模式的日期分桶、范围筛选、连续使用、热力图和导出时间统一按 UTC;中文模式按本地时区。
166
+ - **Client 端**:可读源码位于 `src/client.js`,`npm run build:client` 使用固定版本 Terser 生成 `window.__ModuleLoader__` 工厂格式的 `lib/client.js` 浏览器 bundle,并注册侧边栏「用量统计」入口(`sidebar.footer.action` 槽位)。所有 API 仅接受本机 loopback 请求并拒绝显式跨域请求;余额读取与别名写入还要求插件启动时生成、仅在当前进程有效的令牌(余额 GET 兼容浏览器省略 Origin)。英文模式的日期分桶、范围筛选、连续使用、热力图和导出时间统一按 UTC;中文模式按本地时区。
101
167
 
102
168
  ### 数据说明
103
169
 
104
- - 使用次数与 Token 来自 DSH 会话日志,并在 `session/flush` 时写入独立用量账本;插件激活时会回填日志与账本历史,插件卸载/重启后已成功持久化的数据不丢
170
+ - 使用次数与 Token 来自 DSH 会话日志;`session/flush` 只在存在新的相关事件时重建并将派生账本写入异步队列,同一 session 的 pending record 会合并,插件退出时 drain;插件激活时会回填日志与账本历史,插件卸载/重启后已成功持久化的数据不丢
105
171
  - 按日范围统计会保留全部可读取历史会话的有使用记录日期;热力图仅作为最近 53 周的固定视图窗口
106
172
  - 会话删除后,已成功 flush 的用量仍从独立账本恢复;会话销毁提示和周期对账只负责触发重建,不会删除账本记录
107
173
  - 同一会话的同一 `turn / step` 只保留一份最终 usage;重试或替换消息会替换旧贡献,不重复累计
@@ -110,15 +176,19 @@ dsh plugin --profile web add github:ParticleLight/dsh-all-usage
110
176
  - scope query 将回合(turns)、模型调用(calls)和去重会话(sessions)分开统计;Provider/模型筛选缺少路由信息时明确归为 Unknown,不从展示字符串猜测
111
177
  - records 接口只返回短 hash、时间、工作区 ID、结构化模型身份、turn/step、Token buckets 和当前物化来源,不返回原始 session ID、路径、提示词、回复或凭据
112
178
  - 看板中的总处理量 = 输入 + 输出 + 缓存读写 + 推理;缓存命中表示复用的上下文 Token,不等于新生成 Token 或实际费用
113
- - 成本计算沿用 cc-switch 的四桶公式:输入、输出、缓存读取和缓存写入分别乘每百万价格,四项相加后再乘倍率;DSH 的 reasoning 字段不再次加到 output,避免底层 completion/thoughts 已含推理时重复计费
114
- - 价格同步默认关闭;models.dev 不可用时保留最近一次成功目录,未匹配模型不会套用默认价格;手工 mapping/override 仅用于模型别名、官方目录缺失或有权威官方价格;看板范围与明细视图保存在浏览器本地,6 小时自动同步开关会立即写入受保护的 pricing API
179
+ - 成本计算沿用 cc-switch 的四桶公式:输入、输出、缓存读取和缓存写入分别乘每百万价格,四项相加后再乘倍率;context tier 在输入上下文严格大于阈值时为整次请求切换四项费率,不做渐进分段;DSH 的 reasoning 字段不再次加到 output,避免底层 completion/thoughts 已含推理时重复计费
180
+ - 历史账本中带 `tiered` 标志的旧 flat 成本会在加载升级时迁移为 `unsupported`(`tiered-pricing-not-modeled`),不再继续显示为当前精确 priced;Token 统计不受影响。
181
+ - 价格同步默认关闭;models.dev 不可用时保留最近一次成功目录,未匹配模型不会套用默认价格;成本设置可展开查看官方档位,并为显式 override 增删 context tier;看板范围与明细视图保存在浏览器本地,6 小时自动同步开关会立即写入受保护的 pricing API
182
+ - Mapping 语义:带 `identityKey` 的 mapping 只对精确路由身份生效;不带身份键的 mapping 才按模型做全局回退;旧配置中的 `usageIdentityKey` 会在加载时归一化。
115
183
  - 余额查询走 DeepSeek 官方 `/user/balance` 接口;未配置 API Key 时卡片显示引导文案
184
+ - 账本按 session ID 稳定 hash 到 32 个 JSON shard,单次 flush 只重写对应 shard;旧的 `all_usage_ledger.json` 会在首次加载时迁移,异步写失败或退出前未落盘不会丢失内存统计,只会让下次启动重新扫描
116
185
  - 仅统计能归属到已注册工作区(按会话 cwd 匹配)的会话
117
186
 
118
187
  ### 开发
119
188
 
120
- - 修改 `lib/client.js` 后刷新页面即可;修改 `lib/index.js` 后,需由 DSH 重载该包或重启进程,单纯刷新页面不会替换已运行的 Host 代码
121
- - 插件包无第三方依赖:Host 端只使用 Cordis 服务,Client 端只使用 runtime 提供的 React 模块
189
+ - 修改 `src/client.js` 后先运行 `npm run build:client`,再让 DSH 重载客户端模块并刷新页面;`lib/client.js` 是生成产物,不直接编辑。修改 `lib/plugin.js` 或其他 Host 模块后,需由 DSH 重载该包或重启进程
190
+ - 插件无第三方运行时依赖:Host 端只使用 Cordis 服务,Client 端只使用 runtime 提供的 React 模块;Terser 仅作为固定版本开发依赖生成浏览器产物
191
+ - 手动恢复 npm 发布时,GitHub Actions 要求输入目标 `v<package.version>` tag 和完整 commit SHA,并在 checkout 后校验 tag、SHA 与包版本一致;Release 事件同样执行 commit 校验。
122
192
 
123
193
  ## English
124
194
 
@@ -137,21 +207,94 @@ A full usage dashboard for DeepSeek Harness. Analyze tokens, cache behavior, est
137
207
  - **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
138
208
  - **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
139
209
  - **Data health and on-demand refresh**: after a scan completes, the browser polls only a lightweight status revision and fetches full history only after usage, alias, or sync state changes; it shows the latest full-data update, historical scan health, revision skips, rereads, ledger recovery, and failures while preserving last-good data on network errors
140
- - **Performance**: Host reuses canonical identities, local/UTC date keys, and the current-revision records ordering; Client memoizes scope aggregates and detail rows and renders only the active detail tab
210
+ - **Performance**: Host maintains ingest-time local/UTC day, workspace, model-identity cubes and single-day hour buckets; scope queries merge buckets, exact costs use BigInt decimal accumulators, and the 53-week heatmap emits only the fields it consumes, while revision-scoped snapshot/records caches and recyclable live-event queues remain in place. Client isolates the heatmap, tooltip, trend, donuts, request records, and pricing dialog behind memoized boundaries; pointer coordinates update through refs plus requestAnimationFrame instead of rerendering the page, and the browser entry is deterministically minified before packing
141
211
  - **Trend line chart**: show input, cache read/write, output, reasoning, and total processed tokens for the active range, timezone, workspace, provider, and model scope; use hourly buckets for a single-day scope and daily buckets for cross-day scopes, with smooth monotone curves, staged entrance animation, hover for exact values, and click a point to inspect that day
142
212
  - **Unified filters and audit**: workspace, provider, model, and date filters apply to the summary, heatmap, trend, tables, and CSV; workspace, provider, and model filters remain independent and can be combined freely, while workspace, provider, and model options are limited to values used in the selected date range and stale selections clear automatically; request logs stay visible as a compact paginated table with grouped Token details for the selected row
143
213
  - **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
144
214
  - **Cost semantics**: prices come from the models.dev USD per 1M token catalog; DSH-normalized fresh input and the four cost buckets are snapshotted at calculation time, the multiplier applies only to final total, and existing positive historical costs are not recalculated; matching uses the model's official vendor entry and ignores the DSH provider, while missing official prices stay unpriced
145
215
 
216
+ ### Compatibility and Known Limitations
217
+
218
+ - **Runtime**: Node.js `>=22 <25` is required. CI runs the test suite, syntax checks, and package-content checks on Node 22 and Node 24.
219
+ - **DSH compatibility**: `package.json` declares DSH runtime `>=0.1.1-rc.1 <0.1.2`; the real Cordis service chain is verified on `0.1.1-rc.2` and `0.1.1-rc.1`.
220
+ - **Web service dependency**: the Host declares `webServer` as a required dependency, so DSH waits for a late-mounted service before applying the plugin; this package targets the DSH Web profile and does not expose routes without WebServer. The HTTP guard also checks the actual socket peer, so a reverse proxy is accepted only when the connection itself is loopback.
221
+
222
+ | DSH runtime | Node.js support | Real Cordis smoke | Conclusion |
223
+ | --- | --- | --- | --- |
224
+ | `0.1.1-rc.2` | `>=22 <25`, CI covers 22/24 | Passed (current Node 24) | Declared and verified |
225
+ | `0.1.1-rc.1` | `>=22 <25`, CI covers 22/24 | Passed (current Node 24) | Declared and verified |
226
+ | Other versions | `>=22 <25` | Not tested | Outside the verified matrix |
227
+
228
+ An unlisted DSH version is not necessarily incompatible. Include the DSH, Node.js, and plugin versions when reporting an issue.
229
+
230
+ - **Interrupted requests**: an interrupted upstream request may emit only `assistant/chunk` usage and never produce a final `assistant/message`; that chunk is retained. A later final message for the same turn/step replaces it. If the upstream emits no usage event at all, exact token usage cannot be reconstructed from response text.
231
+ - **Estimated cost**: cost is an estimate based on models.dev rates and DSH usage buckets, not a provider invoice. Unavailable catalogs and unmatched models remain unpriced instead of receiving guessed rates. Cache reads, cache writes, and reasoning follow the buckets reported by the upstream DSH event.
232
+ - **Tiered prices**: models.dev context-tiered entries select the applicable rate from the request input context (fresh input plus cache read/write tokens); malformed schedules remain unsupported.
233
+ - **History boundary**: only sessions whose cwd maps to a registered workspace are included; data deleted or corrupted before a successful session flush cannot be recovered from the separate ledger.
234
+
235
+ ### Local Statistics vs Official Billing
236
+
237
+ This plugin reports replayable statistics from local DSH event logs; it is not a mirror of a provider invoice:
238
+
239
+ - Local statistics read DSH `assistant/chunk`, final `assistant/message`, and related session events, then deduplicate and replace samples by logical `turn / step`. Official billing may use a provider tokenizer, rounding rules, discounts, free quotas, and billing periods.
240
+ - A failed request is included locally whenever it leaves a usage chunk; whether the provider charged for that failed request must be checked against the official bill.
241
+ - Prices come from the public models.dev catalog and local explicit overrides. Catalog prices can differ from provider prices, regional rates, and invoice discounts, so the cost field is an estimate.
242
+ - Local statistics include only sessions mapped to registered workspaces and can be lower than the official bill when logs are damaged, cleaned up, or the upstream emits no usage event.
243
+
244
+ ### Reproducible Event Examples
245
+
246
+ The following are redacted minimal examples; the complete runnable data is in [`fixtures/usage-events.json`](fixtures/usage-events.json).
247
+
248
+ #### Retaining a failed request chunk
249
+
250
+ ~~~json
251
+ [
252
+ {"type": "assistant/chunk", "data": {"turn": 1, "step": 1, "chunk": {"type": "usage", "usage": {"inputTokens": 100, "outputTokens": 20}}}},
253
+ {"type": "request/error", "data": {"code": "upstream-failed"}}
254
+ ]
255
+ ~~~
256
+
257
+ Without a final `assistant/message`, the chunk remains one local call; this does not mean the provider necessarily charged for it.
258
+
259
+ #### Orphan usage chunk
260
+
261
+ ~~~json
262
+ {"type": "assistant/chunk", "data": {"turn": 1, "step": 1, "chunk": {"type": "usage", "usage": {"inputTokens": 7, "cacheReadTokens": 8}}}}
263
+ ~~~
264
+
265
+ Without request/context or request/header, tokens are still counted, but the model identity is shown as Unknown; the plugin does not guess a model from a provider name or display string.
266
+
267
+ #### Cache token buckets
268
+
269
+ ~~~json
270
+ {"inputTokens": 100, "outputTokens": 20, "cacheReadTokens": 40, "cacheWriteTokens": 5, "reasoningTokens": 3}
271
+ ~~~
272
+
273
+ The local processed total is `100 + 20 + 40 + 5 + 3 = 168`; cost uses the input, output, cacheRead, and cacheWrite buckets, and reasoning is not added to output again.
274
+
275
+ Replay the repository fixture:
276
+
277
+ ~~~bash
278
+ node scripts/replay-fixture.mjs fixtures/usage-events.json
279
+ ~~~
280
+
281
+ The command loads the real plugin Host, calls its compatible APIs, checks the documented token/record totals, and prints a non-sensitive summary.
282
+
283
+ ### Report An Issue
284
+
285
+ - [Data inconsistency / 数据不一致](.github/ISSUE_TEMPLATE/data-inconsistency.md)
286
+ - [Plugin startup failure / 插件启动失败](.github/ISSUE_TEMPLATE/startup-failure.md)
287
+ - [Cost calculation issue / 成本计算问题](.github/ISSUE_TEMPLATE/cost-calculation.md)
288
+
146
289
  ### Latest Update
147
290
 
148
- **v1.1.0**
291
+ **v1.1.3**
149
292
 
150
- - Structured model identity with backward-compatible ledger v2 migration
151
- - Unified scope queries for time, timezone, workspace, provider, and model filters
152
- - Token trend line chart with hourly single-day data, selectable series, exact hover values, and point-to-audit drill-down
153
- - Model and workspace analytics with Token-share donut charts, center totals, ranked legends, animated arc reveals, and cursor-following hover details
154
- - Paginated turn/step audit records with redacted provenance and scoped detail CSV export
293
+ - Added validated context-tiered official pricing, expandable rate schedules, and explicit price overrides.
294
+ - **Performance optimizations**: the durable ledger uses stable shards, dirty-flush coalescing, and revision reuse; scoped queries, trends, and the fixed 53-week heatmap use ingest-time aggregates to reduce historical scans, storage write amplification, and Dashboard rerenders.
295
+ - Hardened deterministic pricing catalogs, pricing sync, official-model search, and mapping refresh races.
296
+ - Strengthened recovery for invalid sequences, legacy ledgers, recreated workspaces, lagging persistence revisions, and composite ledger keys.
297
+ - Added Node 22/24, DSH rc.1/rc.2 runtime smoke, redacted fixture replay, and package-content release gates.
155
298
 
156
299
  See [CHANGELOG.md](CHANGELOG.md) for the complete version history.
157
300
 
@@ -187,7 +330,7 @@ The profile patch layer hot-reloads; save the file and refresh the page.
187
330
 
188
331
  ### Architecture
189
332
 
190
- - **Host** (`lib/index.js`): aggregates persisted session logs (`turn/end` and `assistant/message.usage`), folds live `session/event` updates, and exposes data routes through `webServer`:
333
+ - **Host** (entry `lib/index.js`, assembled by `lib/plugin.js`): split by responsibility across `aggregation.js` (aggregation/query), `ledger.js` (durable ledger), `session-sync.js` (history/live sync), `pricing-runtime.js` (runtime pricing), `balance.js` (balance), and `http.js` (protected routes); aggregates `turn/end`, `assistant/chunk` usage, and final `assistant/message.usage`, folds live `session/event` updates, and exposes data routes through `webServer`:
191
334
  - `GET /api/all-usage` — compatible usage snapshot
192
335
  - `GET /api/all-usage/status` — lightweight revision and sync health
193
336
  - `GET /api/all-usage/query` — scoped aggregate, daily/hourly trend, and heatmap data; single-day scopes populate `hourly`, while cross-day scopes return an empty `hourly` array
@@ -196,13 +339,13 @@ The profile patch layer hot-reloads; save the file and refresh the page.
196
339
  - `POST /api/all-usage/alias` — update workspace aliases
197
340
  - `GET /api/all-usage/pricing` — inspect models.dev sync status, used-model matches, and explicit overrides
198
341
  - `GET /api/all-usage/pricing/models?q=...` — search official model IDs and display-name matches
199
- - `POST /api/all-usage/pricing` — save sync, mappings, and explicit price overrides
342
+ - `POST /api/all-usage/pricing` — save sync, mappings, and explicit price overrides, including context-tier bands
200
343
  - `POST /api/all-usage/pricing/sync` — sync models.dev and backfill unpriced calls
201
- - **Client** (`lib/client.js`): a `window.__ModuleLoader__` browser bundle that registers the “Usage statistics” sidebar entry through the `sidebar.footer.action` slot. All API routes accept loopback requests and reject an explicit cross-origin Origin; balance reads and alias writes also require a process-scoped token generated when the plugin starts (the balance GET tolerates browsers omitting Origin).
344
+ - **Client**: readable source lives in `src/client.js`; `npm run build:client` uses the pinned Terser version to generate the `window.__ModuleLoader__` bundle at `lib/client.js`, which registers the “Usage statistics” sidebar entry through the `sidebar.footer.action` slot. All API routes accept loopback requests and reject an explicit cross-origin Origin; balance reads and alias writes also require a process-scoped token generated when the plugin starts (the balance GET tolerates browsers omitting Origin).
202
345
 
203
346
  ### Data semantics
204
347
 
205
- - Calls and tokens come from DSH session logs and a separate usage ledger written at `session/flush`; readable logs and ledger history are backfilled when the plugin activates, so successfully persisted data survives reloads or session deletion
348
+ - Calls and tokens come from DSH session logs; `session/flush` rebuilds and queues the derived ledger only when related events are dirty, coalescing the latest pending record per session and draining on plugin disposal. Readable logs and ledger history are backfilled when the plugin activates, so successfully persisted data survives reloads or session deletion
206
349
  - Day-level range data retains every readable historical session date with tracked usage; the heatmap is only a fixed latest-53-week view
207
350
  - 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
208
351
  - For each session and logical `turn / step`, only the final usage contribution is kept; retries or replaced messages do not double-count
@@ -211,16 +354,20 @@ The profile patch layer hot-reloads; save the file and refresh the page.
211
354
  - Scoped results keep turns, model calls, and distinct sessions as separate metrics; missing route identity is explicitly Unknown rather than inferred from a display label
212
355
  - The records endpoint returns only a short hash, time, workspace ID, structured model identity, turn/step, token buckets, and current materialization source. It omits raw session IDs, paths, prompts, replies, and credentials
213
356
  - Processed tokens = input + output + cache read/write + reasoning; a cache hit means reused context, not newly generated tokens or actual cost
214
- - Cost follows the cc-switch four-bucket formula: input, output, cache-read, and cache-write tokens are priced independently, summed, then multiplied by the final multiplier; DSH reasoning is not added to output a second time
215
- - Pricing sync is off by default; when models.dev is unavailable the last good catalog remains in use, and unmatched models never receive a guessed default price; explicit model mappings/overrides are for aliases, missing official catalog entries, or authoritative special pricing; dashboard range and detail-view preferences are stored in browser storage, while the 6-hour sync toggle is immediately saved through the protected pricing API
357
+ - Cost follows the cc-switch four-bucket formula: input, output, cache-read, and cache-write tokens are priced independently, summed, then multiplied by the final multiplier; when input context is strictly greater than a context-tier threshold, all four rates switch for the whole request instead of progressive band splitting, and DSH reasoning is not added to output a second time
358
+ - Legacy ledger costs carrying `tiered` are migrated to `unsupported` (`tiered-pricing-not-modeled`) on load instead of remaining falsely marked as current flat priced estimates; token statistics are unchanged.
359
+ - Pricing sync is off by default; when models.dev is unavailable the last good catalog remains in use, and unmatched models never receive a guessed default price; Cost Statistics can expand official tier schedules and add or remove context tiers on explicit overrides; dashboard range and detail-view preferences are stored in browser storage, while the 6-hour sync toggle is immediately saved through the protected pricing API
360
+ - Mapping semantics: a mapping with `identityKey` applies only to that exact route identity; a mapping without an identity key is the model-wide fallback. Legacy `usageIdentityKey` values are normalized when loaded.
216
361
  - Balance data comes from DeepSeek’s official `/user/balance` endpoint; the card shows guidance when no API key is configured
217
362
  - English mode uses UTC for date buckets, range filters, streaks, heatmap dates, and export timestamps; Chinese mode uses local time
363
+ - The ledger assigns each session ID to one of 32 stable-hash JSON shards, so a flush rewrites only its shard; the old `all_usage_ledger.json` is migrated on first load. An async write failure or an unflushed shutdown does not lose in-memory statistics; the next startup simply scans that session again
218
364
  - Only sessions that can be mapped to a registered workspace by their working directory are included
219
365
 
220
366
  ### Development
221
367
 
222
- - After editing `lib/client.js`, refresh the page. After editing `lib/index.js`, reload the package through DSH or restart the process; a page refresh alone cannot replace running host code
223
- - The plugin has no third-party package dependencies: the Host uses Cordis services and the Client uses the runtime-provided React module
368
+ - After editing `src/client.js`, run `npm run build:client`, reload the DSH client module, and refresh the page; `lib/client.js` is generated and should not be edited directly. After editing `lib/plugin.js` or another Host module, reload the package through DSH or restart the process
369
+ - The plugin has no third-party runtime dependencies: the Host uses Cordis services and the Client uses the runtime-provided React module; pinned Terser is only a development dependency for generating the browser artifact
370
+ - Manual npm recovery publishes require a target `v<package.version>` tag and full commit SHA; GitHub Actions checks both against the checked-out tag and package version. Release events perform the same commit check.
224
371
 
225
372
  ## License / 许可证
226
373
 
@@ -0,0 +1,51 @@
1
+ Model brand icons bundled in assets/model-icons/
2
+ =================================================
3
+
4
+ Upstream project : lobe-icons
5
+ Repository : https://github.com/lobehub/lobe-icons
6
+ Pinned revision : 0863807f3a6ce4d7ea23ad86a6693a17638747da
7
+ Upstream licence : MIT
8
+ Licence URL : https://github.com/lobehub/lobe-icons/blob/0863807f3a6ce4d7ea23ad86a6693a17638747da/LICENSE
9
+
10
+ MIT License
11
+
12
+ Copyright (c) 2023 LobeHub
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+
32
+ Trademark notice
33
+ ----------------
34
+
35
+ The brand names, logos and marks depicted by these files remain the property
36
+ of their respective owners (DeepSeek, OpenAI, Anthropic, Google, Meta, Zhipu
37
+ AI, xAI, Alibaba, ByteDance, Moonshot AI, MiniMax). They are bundled only to
38
+ identify which vendor served a recorded request (nominative use) and do not
39
+ imply any endorsement, affiliation or sponsorship.
40
+
41
+ Local modifications
42
+ -------------------
43
+
44
+ 1. Whitespace between tags is collapsed at build time before base64 encoding
45
+ (no visual change to the artwork).
46
+ 2. grok.svg and openai-color.svg: fill="currentColor" was replaced with the
47
+ fixed neutral colour #8a8f98, because currentColor does not inherit into an
48
+ <img> element.
49
+
50
+ Per-file provenance is recorded in assets/model-icons/manifest.json
51
+ (upstreamPath + modified flag for every icon).
@@ -0,0 +1 @@
1
+ <svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Claude</title><path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z" fill="#D97757" fill-rule="nonzero"></path></svg>
@@ -0,0 +1 @@
1
+ <svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>DeepSeek</title><path d="M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z" fill="#4D6BFE"></path></svg>
@@ -0,0 +1 @@
1
+ <svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Doubao</title><path d="M5.31 15.756c.172-3.75 1.883-5.999 2.549-6.739-3.26 2.058-5.425 5.658-6.358 8.308v1.12C1.501 21.513 4.226 24 7.59 24a6.59 6.59 0 002.2-.375c.353-.12.7-.248 1.039-.378.913-.899 1.65-1.91 2.243-2.992-4.877 2.431-7.974.072-7.763-4.5l.002.001z" fill="#1E37FC"></path><path d="M22.57 10.283c-1.212-.901-4.109-2.404-7.397-2.8.295 3.792.093 8.766-2.1 12.773a12.782 12.782 0 01-2.244 2.992c3.764-1.448 6.746-3.457 8.596-5.219 2.82-2.683 3.353-5.178 3.361-6.66a2.737 2.737 0 00-.216-1.084v-.002z" fill="#37E1BE"></path><path d="M14.303 1.867C12.955.7 11.248 0 9.39 0 7.532 0 5.883.677 4.545 1.807 2.791 3.29 1.627 5.557 1.5 8.125v9.201c.932-2.65 3.097-6.25 6.357-8.307.5-.318 1.025-.595 1.569-.829 1.883-.801 3.878-.932 5.746-.706-.222-2.83-.718-5.002-.87-5.617h.001z" fill="#A569FF"></path><path d="M17.305 4.961a199.47 199.47 0 01-1.08-1.094c-.202-.213-.398-.419-.586-.622l-1.333-1.378c.151.615.648 2.786.869 5.617 3.288.395 6.185 1.898 7.396 2.8-1.306-1.275-3.475-3.487-5.266-5.323z" fill="#1E37FC"></path></svg>
@@ -0,0 +1 @@
1
+ <svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Gemini</title><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="#3186FF"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-0-_R_0_)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-1-_R_0_)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-2-_R_0_)"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-0-_R_0_" x1="7" x2="11" y1="15.5" y2="12"><stop stop-color="#08B962"></stop><stop offset="1" stop-color="#08B962" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-1-_R_0_" x1="8" x2="11.5" y1="5.5" y2="11"><stop stop-color="#F94543"></stop><stop offset="1" stop-color="#F94543" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-2-_R_0_" x1="3.5" x2="17.5" y1="13.5" y2="12"><stop stop-color="#FABC12"></stop><stop offset=".46" stop-color="#FABC12" stop-opacity="0"></stop></linearGradient></defs></svg>
@@ -0,0 +1 @@
1
+ <svg fill="#8a8f98" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Grok</title><path d="M9.27 15.29l7.978-5.897c.391-.29.95-.177 1.137.272.98 2.369.542 5.215-1.41 7.169-1.951 1.954-4.667 2.382-7.149 1.406l-2.711 1.257c3.889 2.661 8.611 2.003 11.562-.953 2.341-2.344 3.066-5.539 2.388-8.42l.006.007c-.983-4.232.242-5.924 2.75-9.383.06-.082.12-.164.179-.248l-3.301 3.305v-.01L9.267 15.292M7.623 16.723c-2.792-2.67-2.31-6.801.071-9.184 1.761-1.763 4.647-2.483 7.166-1.425l2.705-1.25a7.808 7.808 0 00-1.829-1A8.975 8.975 0 005.984 5.83c-2.533 2.536-3.33 6.436-1.962 9.764 1.022 2.487-.653 4.246-2.34 6.022-.599.63-1.199 1.259-1.682 1.925l7.62-6.815"></path></svg>
@@ -0,0 +1 @@
1
+ <svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Kimi</title><path d="M21.846 0a1.923 1.923 0 110 3.846H20.15a.226.226 0 01-.227-.226V1.923C19.923.861 20.784 0 21.846 0z" fill="#1783FF"></path><path d="M11.065 11.199l7.257-7.2c.137-.136.06-.41-.116-.41H14.3a.164.164 0 00-.117.051l-7.82 7.756c-.122.12-.302.013-.302-.179V3.82c0-.127-.083-.23-.185-.23H3.186c-.103 0-.186.103-.186.23V19.77c0 .128.083.23.186.23h2.69c.103 0 .186-.102.186-.23v-3.25c0-.069.025-.135.069-.178l2.424-2.406a.158.158 0 01.205-.023l6.484 4.772a7.677 7.677 0 003.453 1.283c.108.012.2-.095.2-.23v-3.06c0-.117-.07-.212-.164-.227a5.028 5.028 0 01-2.027-.807l-5.613-4.064c-.117-.078-.132-.279-.028-.381z" fill="#fff"></path></svg>