# Changelog All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses [Semantic Versioning](https://semver.org/). 中文版见 [CHANGELOG_ZH.md](./CHANGELOG_ZH.md). ## [1.1.6] - 2026-09-19 ### Fixed - **Host pricing file source lifecycle (`pricing.json` override & change watcher)** — [PR #10](https://github.com/Max-Samson/dsh-usage-chart/pull/10): - Fixed premature disposal of `filePricingSource` during plugin registration: `ctx.effect(() => fileSource.dispose(), ...)` called `dispose()` synchronously while building the fiber and registered `undefined` as the cleanup handler, aborting initial asynchronous file loading and disabling the file change watcher. Resolved by returning a disposer function: `ctx.effect(() => () => fileSource.dispose(), ...)`. - Updated test mocks in `tests/core.test.mjs`, `tests/pricing.test.mjs`, and `tests/rounds.test.mjs` to properly track and execute Cordis effect disposers in `after()`. - Added regression test covering the `/pricing` route with user `pricing.json` overrides enabled at startup. ## [1.1.5] - 2026-09-12 ### Changed - **DeepSeek official pricing update (2026-09-10) & `deepseek-flash` support** — [PR #8](https://github.com/Max-Samson/dsh-usage-chart/pull/8): - **New `deepseek-flash` model pricing**: Updated builtin pricing according to the official DeepSeek pricing table (verified 2026-09-10). The new `deepseek-flash` rates are: - **Off-peak** (Beijing Mon–Fri non-peak & weekends): CNY 1.0 / 0.02 / 4.0 per 1M tokens (cache-miss input / cache-hit input / output); USD 0.15 / 0.003 / 0.6 per 1M tokens. - **Peak** (Beijing Mon–Fri 09:00–12:00 & 14:00–18:00): CNY 2.0 / 0.04 / 8.0 per 1M tokens; USD 0.30 / 0.006 / 1.2 per 1M tokens. - **Backward compatibility for legacy model names**: `deepseek-v4-flash` and `deepseek-v4-flash-vision-exp` are mapped to `deepseek-flash` in `BUILTIN_PRICING` so historical session logs continue resolving without falling back to unknown-model estimates. - **Fallback pricing**: Default unlisted model fallback (`FALLBACK_PRICING`) now points to the new `deepseek-flash` pricing. - **Docs & test synchronization**: Updated custom `pricing.json` example in `README.md` and `README_ZH.md` (`verifiedAt: 1788998400000`), and aligned test cases across `tests/core.test.mjs`, `tests/pricing.test.mjs`, and `tests/rounds.test.mjs` (`BUILTIN_VERIFIED_AT` set to `2026-09-10T00:00:00Z`). ## [1.1.4] - 2026-09-08 ### Fixed - **`dsh-session >= 0.1.2-rc.1` compatibility (snapshotEvents())** — [PR #7](https://github.com/Max-Samson/dsh-usage-chart/pull/7): `dsh-session` removed its public `events` property in `0.1.2-rc.1` (replaced by the `snapshotEvents()` method); the `/usage` route then threw `TypeError: events is not iterable`, which `dsh-host-webserver` wrapped as a bare `400` empty response, and the per-round chart disappeared (the client fell back to the empty observed-rounds path). Reproduced live on `dsh-session 0.1.2-rc.1`: `/usage?session=` returned an empty `400` while `/usage?session=` returned the plugin `404` JSON and folding the same log offline succeeded — confirming the crash was only the new runtime API shape. The host now reads events via capability detection — prefers `session.snapshotEvents()` when the function is present and falls back to the legacy `session.events ?? []` (so a missing API can never crash the route again). The vendored `SessionEventLike` type adds the `time` field (Unix epoch ms) and a `SessionHandle` shape (optional `events` + optional `snapshotEvents`) so `SessionStoreService.get()/list()` stay valid across both API generations. Verified against the upstream `deepseek-harness` source: `Session.events` was removed from `0.1.2-alpha.4` onward (official architecture note `2026-08-21-session-log-read-intent`); the installed `0.1.0-rc.6` still exposes `events`, so the fallback path remains covered. - **Test coverage for the new `snapshotEvents()` access path**: added `tests/rounds.test.mjs` → `/usage route prefers snapshotEvents() when the new API is present`, mocking a session handle that exposes only `snapshotEvents()` (its `get events()` throws, proving the route never reads the removed property), asserts `time` is consumed (duration/peak billing) and that `snapshotEvents()` wins when both APIs are present. ## [1.1.2] - 2026-08-26 ### Improved & Fixed - **Compact 2-Column Hover Tooltip & Overflow Elimination**: - **Height Reduction**: Redesigned `.duc-chart-tooltip` into a compact 2-column grid (2×2 token breakdown + 2-column metadata pairs), reducing card height from ~280px to ~120px (>55% reduction). - **Top-Aligned Positioning**: Adjusted placement from `top: 20px` to `top: 2px` (240px width), ensuring the hover card stays strictly within the 132px chart boundary and never expands container scroll height or triggers popover scrollbars. ## [1.1.1] - 2026-08-26 ### Improved & Fixed - **Compact Panel UI & Vertical Overflow Elimination**: - **Height Reduction**: Tightened `.duc-section` vertical padding from `12px` to `8px`, compacted `.duc-cell` padding from `8px 9px` to `5px 8px`, reduced bar heights and line-heights. Overall panel height shrinks from ~800px to ~560px (~170px reduction), allowing the full dashboard to fit comfortably in viewport without vertical scrolling. - **Removed Redundant Legend**: Eliminated the duplicate 4-color legend row in the "Session Usage" section (values are already labeled in the 4 grid cards above; chart legend remains intact in the per-round section). - **Lightweight Compaction Empty State**: When no compaction has occurred, the placeholder row is omitted to save vertical space; compaction details are shown only when compactions exist. - **SVG Chart Height Optimization**: Reduced `RoundBars` baseline height from `96px` to `76px` (canvas height reduced from `166px` to `132px`), keeping all scroll arrows, duration lines, anomaly markers, and hover tooltips aligned and legible. - **Robustness Defense**: Added fallback defaults for missing `currency` parameter (`'cny'`) and safe optional chaining on `round.cost` in `RoundBars`. ## [1.1.0] - 2026-08-26 Milestone release: **Context Explainability & Compaction Diagnostics**. Turns "why context grows, which round got compacted, how many tokens were freed, and how much the summarize step cost" into an interpretable view (dock indicator + dedicated panel diagnostics section), alongside user/message input source attribution. ### Added - **Context & Compaction Diagnostics Section**: added a dedicated diagnostics section in the panel showing System Prompt, Tools Schema, and Message History token counts and percentage breakdown, complete with a multi-segment color bar (system blue, tools amber, messages green) and an explicit heuristic-approximation note based on the official `contextBreakdown` projection. - **Compaction Lifecycle Folding & Cost Accounting**: host-side `foldCompactions` (`src/usage/compactions.ts`) parses `compaction/start`, `compaction/summary`, `compaction/prune`, and `compaction/end` events from session logs, tracking shadowed token count, shadowed range/seqs, round attribution, summarize model, and dual-currency summarize cost (peak/off-peak tier aware). - **Compaction Timeline & Savings**: the diagnostics section summarizes total freed tokens and total compaction occurrences, listing each compaction record (round, freed tokens, summarize cost). - **Context Occupancy Recommendations**: actionable recommendations based on `contextPressure` occupancy (≥75% suggests starting a new session or trimming large file injections; ≥90% warns of imminent context exhaustion). - **Segmented Dock Pressure Bar**: consumes official `contextBreakdown` projection to render system (blue), tools (amber), and messages (green) multi-segment bars on the composer dock indicator with detailed tooltip percentages, smoothly falling back to single-color bar when breakdown is unavailable. - **Per-Round User Source Attribution**: `foldRounds` captures `userSource` (`human`, `agent.inject`, `continuation`) from `user/message` events and displays the attribution badge in the chart tooltip explainer card. - **New Exports**: `foldCompactions` function and `CompactionRecord` type. ## [1.0.2] - 2026-08-26 ### Fixed - **Weekend off-peak tier rule**: `isPeakHour` and `tierAt` now take day-of-week into account — peak hours strictly apply to **Monday through Friday 09:00–12:00 and 14:00–18:00 (Beijing time / UTC+8)**; all hours on Saturday and Sunday are now correctly classified as off-peak (leisure/discount period). - **Builtin pricing coverage**: added `deepseek-v4-flash-vision-exp` to builtin pricing table (identical pricing to flash); updated `BUILTIN_VERIFIED_AT` to `2026-08-26`. ## [1.0.1] - 2026-08-17 Billing updated to the latest DeepSeek pricing (official pricing pages, fetched 2026-08-17): prices are now quoted in **two currencies — CNY (Chinese page) and USD (English page) — per 1M tokens** with **peak / off-peak tiers** — peak hours (Beijing time 09:00–12:00 and 14:00–18:00, i.e. UTC 01:00–04:00 and 06:00–10:00) are billed at 2× the off-peak rate. ### Added - **Peak/off-peak tiered billing**: the builtin price table stores both `peak` and `offPeak` unit prices per model; each round's cost is billed with the tier of its start time (`tierAt` resolves Beijing time = UTC+8). Unknown timestamps fall back to the peak tier (conservative estimate). - **Official dual-currency pricing**: each tier carries both the official CNY quote and the official USD quote; costs are computed with the list price of the selected display currency — **no FX conversion**, consistent with the official bill. The CNY/USD toggle now drives the indicator, the panel, the chart cost view and the cost badge. - `pricing.json` now accepts the dual-currency tiered shape `{ "peak": { "cny": {…}, "usd": {…} }, "offPeak": {…} }`; legacy shapes are still accepted (single-currency tiers or the flat shape are treated as CNY, with USD derived at the default rate 6.76). - New exports: `costSplitAt`, `tierAt`, `isPeakHour`, `formatCny`, and the `CostCurrency` / `PriceTier` / `PriceTierId` / `BucketPrices` types. - The cost note in the panel now shows both tiers: e.g. cache-miss input `1.5/3.0` (off-peak/peak) in the selected currency. - **Live billing-tier tag**: the panel's session-usage header shows a red (peak) / green (off-peak) tag for the current billing period (Beijing time, auto-flips at hour boundaries), and each round's tooltip shows its own billing tier. - **Per-round cost always visible**: in the cost chart view every bar shows its own cost value (not only the current round), and the observed-rounds fallback (no host history) now derives per-round cost client-side from the `/pricing` snapshot + per-turn model/start time, so the cost view works even without host history. ### Changed - Builtin list price updated to the official prices (flash: CNY off-peak 1.5 / 0.05 / 4.5, peak 3.0 / 0.10 / 9.0; USD off-peak 0.22 / 0.007 / 0.66, peak 0.44 / 0.014 / 1.32; pro: CNY off-peak 4.5 / 0.15 / 13.5, peak 9.0 / 0.30 / 27.0; USD off-peak 0.66 / 0.022 / 1.98, peak 1.32 / 0.044 / 3.96 — miss input / hit input / output); `BUILTIN_VERIFIED_AT` updated to 2026-08-17. - Cost computation is now currency-parameterized: `costSplit(usage, pricing, tier, currency)` and `costSplitAt(usage, pricing, timeMs, currency)` return a currency-neutral `CostSplit` (`input` / `cacheRead` / `output` / `total`) in the requested currency. `formatMoney` / `formatPricePerM` drop the FX-rate parameter (amounts are already in the display currency); `toDisplayAmount` is removed. `estimateCost` keeps returning `{ cny, estimated }` (official CNY prices). - `foldRounds` bills each round with the tier of its `turn/start` time (fallback: `turn/end`, then peak) and publishes **both** `cny` and `usd` cost splits in the `/usage` payload, so the client can switch currencies without re-deriving. - The FX rate (`config.cnyPerUsd`, `/rate`, "Refresh rate") is now informational only ("1 USD ≈ X CNY" note); costs never depend on it for official models. ### Fixed - The old builtin table (single-currency USD, flat price) no longer matched the official tiered billing in either currency, so costs were stale; the whole pipeline now follows the current official pricing pages (CNY + USD). ## [1.0.0] - 2026-08-15 First complete release — the in-session usage interpreter is feature-complete (per-round chart with full-history horizontal scroll + cost/timing/anomaly explainer + multi-currency costs with live rates + account balance). ### Changed - Per-round chart no longer truncates to the latest 12 rounds: **all** rounds render into a horizontally scrollable area with a fixed slim bar width (30px), so bars stay constant-width and never look crowded. It auto-scrolls to the latest round, and arrow buttons + edge fades appear when content overflows; the scrollbar is slimmed down. - Per-bar value labels adapt to density: in scrollable (dense) mode only the current round keeps its top-of-bar value (details stay in the hover explainer card); otherwise overly long labels (e.g. `$0.0013`, `123.4K`) are elided so neighbouring labels never overlap. - Chart minimum width adapts to the panel width: short histories fill/center without stretching bar widths; the tooltip follows the active bar across scrolling (content coordinate − scroll offset, clamped to the visible area). ### Fixed - The tooltip could not be positioned correctly once the chart could scroll; it now tracks the bar precisely at any scroll position. - Short histories could overflow on narrow panels; the adaptive minimum width removes the needless scroll. - The balance no-key test now isolates `$DEEPSEEK_API_KEY`, so the suite passes on hosts that have the key in the environment; added coverage for the env fallback itself. ## [0.3.0] - 2026-08-15 ### Added - Cost display currency: `config.currency` ('usd' | 'cny') and `config.cnyPerUsd` (default 6.76); costs render in the chosen currency. - USD/CNY toggle in the cost section; the choice is remembered in the browser (localStorage). - Rate-refresh button: fetches the latest USD→CNY rate through the new same-origin `/dsh-usage-chart/rate` proxy (`config.fxUrl`) and re-estimates immediately. - `/dsh-usage-chart/meta` route serving display-currency config to the client. ### Changed - Per-model price notes follow the display currency and show the applied rate (e.g. `CNY(1 USD ≈ 6.76 CNY)`). ### Fixed - Resilient rate refresh: falls back to a built-in FX source (frankfurter.dev) when the default (open.er-api.com) is unreachable or blocked; the last successful rate is persisted, so offline refreshes keep the previous rate instead of the fixed default. - Client slots register via `ctx.slots.inject` (wait for declaration), fixing `slot "…" is not declared` when loader order changes. ## [0.2.0] - 2026-08-15## [0.2.0] - 2026-08-15 ### Added - **Per-round cost explainability (v0.2, see `docs/ROADMAP.md`)**: the host fold (`RoundFold`, `src/usage/rounds.ts`) now derives per-round duration (`turn/start → turn/end`), TTFT (start → first usage sample), output throughput (tokens/s), model attribution (`request/context` → `request/header` → cross-round carry-forward), end reason, and a per-round cost split (input / cache-read / output × unit price). - **Pricing governance**: `src/pricing.ts` is split into a pure math module (`pricing/calc.ts`, bundled by both halves) and a host-only `PricingSource` seam (`pricing/source.ts` — builtin list with a verification date + user-override `pricing.json` file adapter with change watching) plus a `PricingResolver` (`pricing/resolve.ts`, priority file > builtin > fallback, unknown models explicitly marked). New `/dsh-usage-chart/pricing` route exposes the resolved snapshot — the client's **only** price input (ADR 2); the old bundled pricing constants no longer drift from host resolution. - **`/usage` route** now returns `rounds` (with cost/timing/model/endReason) instead of bare `turns` (`foldTurnUsage` kept as a v0.1-compatible wrapper). - **RoundBars cost view**: a third chart mode stacks bars by cost (bucket × unit price); a duration polyline overlays bar tops; anomalously expensive rounds (relative to the previous N rounds, `src/client/diagnose/anomaly.ts`) get a warning marker with attributed reason chips; a per-round cache-hit mini tick sits under the baseline; the tooltip became an explainer card (tokens + cost + model + duration + TTFT + TPS + cache hit + end reason + anomaly chips). - **Cost badge**: a dismissible `≈ $0.00xx` badge per assistant message (`conversation.chat.assistant-actions` slot, data from the host `/usage` history). - **Dock context pressure bar**: slim `contextPressure` bar in the indicator (green → amber → red as occupancy rises). - **Tests**: `tests/rounds.test.mjs` (fold timing/TTFT/TPS/model/cost + route), `tests/pricing.test.mjs` (resolver priority, file source with temp dir, unknown marking + route), `tests/anomaly.test.mjs` (spike flagging and attribution); 28 tests pass via `npm run verify`. - **Configuration**: optional `config.pricingFile` overrides the default `$DSH_HOME/data/dsh-usage-chart/pricing.json` (falls back to `~/.dsh/...`). ### Fixed - Cost estimates are now single-sourced: real-time indicator cost and panel cost both consume the `/pricing` snapshot; the price source, verification date, and unknown-model marker are shown in the panel. - Panel cost resolution now prefers the host fold's authoritative model attribution (ADR 1) over snapshot provenance, which can be absent for older sessions — the price source previously showed a spurious "fallback estimate" in that case. ### Upgrade note - **Restart `dsh web` after upgrading to 0.2.0.** The Host process caches plugin code in memory (no hot reload): the new `/dsh-usage-chart/pricing` route and the `rounds`-shaped `/dsh-usage-chart/usage` response are only served after a restart. Until then the indicator silently omits the cost segment and the panel shows "Price snapshot unavailable". ## [0.1.1] - 2026-08-14 ### Added - Balance queries now resolve the DeepSeek API key through the DSH credentials service (`ctx.get('credentials')`), so a key configured in the web UI (Settings → Models) or in `.credentials.yaml` works without an environment variable or plugin config. ### Fixed - Install warning `missing peer react@^18.2.0` (react is provided by the DSH web platform; the peer is now marked optional). - Docs: document uninstall/cleanup steps in the README. ## [0.1.0] - 2026-08-14 ### Added - Composer usage indicator and expandable SVG dashboard. - Session token, cache, context-pressure, model, and estimated-cost views. - Per-turn usage chart with total/mix views, hover and keyboard tooltips, and a highlighted current-round band; host-side per-turn aggregation from the session log. - Host-side DeepSeek balance proxy. - Full zh/en localization: the indicator, dashboard, charts, and balance views follow the DSH in-app Language setting through the `locale` service (dictionaries registered under the `dsh-usage-chart` namespace; browser language only seeds the initial value). - Release metadata, bilingual documentation, CI, contribution/security policies, and portable visual probe scripts (`scripts/*.mjs`). - Privacy-safe README demo screenshot with fictional usage values and no account data. ### Fixed - Light-mode readability: indicator, panel, and SVG charts now use DSH theme tokens (`--dsw-alias-label-*` / `--dsw-alias-bg-*`) and theme static palette colors, so text and chart segments stay legible in both light and dark appearances. ### Security - Restrict Host JSON routes to same-origin GET requests. - Require HTTPS for custom API endpoints, except loopback HTTP proxies. - Validate token-usage samples before aggregation.