# dsh-gauge [中文](README.md) | English Accurate cache-hit rate, token usage, and cost estimates for the DeepSeek Harness Web UI. The official stats line rounds the cache-hit rate to an integer — `Math.round` turns 99.8% into a misleading **100%**. dsh-gauge replaces it with a precise one-decimal (configurable) figure, adds a per-bucket token breakdown, a session usage panel, and a session cost estimate that automatically follows the official DeepSeek price changes (using the official API). ## Features - **Precise cache-hit rate** — `cacheRead / (cacheRead + uncached + cacheWrite)` with a configurable number of decimals (default 1), so 99.8% stays 99.8%. - **Bucket breakdown** — cache-hit / cache-miss input tokens and output tokens. The *write* bucket is automatically hidden when it is 0 (the opencode-go/pi-ai adapter never reports cache-write tokens, so it is always 0 in practice). - **Cost estimate + price-change comparison** — estimated from actual usage × model prices (deepseek-v4-flash / deepseek-v4-pro, auto-detected from the conversation). Before the new official prices take effect it shows **current cost + upcoming price + projected increase**; at the switch moment it automatically moves to the new prices. - **Per-request peak/off-peak billing** — the estimate is **not** a "current time" snapshot: every assistant message is priced by its own timestamp, so tokens consumed during peak hours are billed at the peak rate and off-peak tokens at the discounted rate, then summed. - **Peak-hour badge** — Beijing 09:00–12:00 / 14:00–18:00 are DeepSeek's peak-pricing windows. The stats-row tail shows **peak**/**off-peak** according to the current Beijing hour (also after the price switch). - **Usage panel** — a ⓘ trigger in the session header opens the session's model, hit rate, billed input, per-bucket totals (full numbers by default), context occupancy, and cost estimate (with the post-switch comparison). - **Dual-row stats (official metrics + precise usage)** — the first row keeps the official session metrics (turns/steps, LLM/tool-call time, TTFT avg, tok/s) in a replica of the official styling (line height deliberately tightened so the two rows read as one block); the second row is the plugin's precise usage (hit rate, buckets, output, cost, peak/off-peak badge). With `replaceNativeStatsLine: false` the official native row (including its usage segment) stays. - **Bilingual & currency-aware** — when the UI is English, all text switches to English and the cost is estimated in USD with the international price table. Locale and currency follow live changes, no restart needed. ## Screenshots ![Stats line](https://raw.githubusercontent.com/noone89A/dsh-gauge/main/docs/images/stats-line-en.png) ![Usage panel](https://raw.githubusercontent.com/noone89A/dsh-gauge/main/docs/images/usage-pane-en.png) ## Install ```sh # One command (recommended): dsh plugin runs pnpm add and automatically # appends any dependency that declares dsh.bundle to dsh.profile.bundles dsh plugin --profile web add dsh-gauge # Manual: cd ~/.dsh/profiles/web && npm install dsh-gauge, then edit # profiles/web/package.json and add "dsh-gauge" to dsh.profile.bundles # restart dsh web ``` You should see the precise stats row under the composer and the ⓘ usage trigger in the session header. > **Local development**: install from source instead — `pnpm add file:C:/Object/dsh-plugin/dsh-gauge` > (run `npm run build` after source changes; useful when tuning `src/config.ts` prices/peak windows). ### Works out of the box & the config card After install and restart everything works **with zero configuration**: - Two stats rows under the composer: the precise cache-hit rate (99.8% stays 99.8%), bucket breakdown, output tokens, estimated cost, and a peak/off-peak badge; - The ⓘ usage panel in the session header: full token totals, context occupancy, model, cost and the post-switch price comparison; - UI copy and the cost currency follow the interface language automatically. **The dsh-gauge card under Settings → Plugins → configurable**: current DSH versions hard-code the plugin settings exposed to web clients in an allowlist (`dsh-host-apiproxy`'s `WEB_SETTINGS_NAMESPACES`), so a third-party plugin needs a one-time `gauge` allowlist entry for the card to show up (steps in Troubleshooting). **Not adding it affects nothing else** — alternatively edit `cordis.patch.yml` directly (see Configuration). > **Local development**: replace step ② with `pnpm add file:C:/Object/dsh-plugin/dsh-gauge` — after > source changes run `npm run build` and restart; useful when tuning `src/config.ts` prices/peak > windows. ## How it works - The metrics row registers into the `conversation.composer.dock` slot at `priority: -1` in the official `stats` cell, shadowing the native row; the usage row is added at `order: 1`. With `replaceNativeStatsLine: true` (default) the native usage segment is replaced by the precise row; with `false` the native row (including its usage segment) stays. - Token totals come from the `tokenUsage` projection (`@deepseek-ai/dsh-token-meter`); context occupancy from `contextPressure`. - The cost estimate pages the **full session history** (`sessions.history`): each finalized assistant message carries its own completion time and `usage`, so every request is priced with the rate applicable at that moment (peak or off-peak for the new scheme; flat for the current pre-change prices) and the results are summed — history before "load earlier" is priced precisely too, so a session with 200M hit tokens never shows a few cents of cost. - The active model is derived from the last assistant message of the full history (`source.model`; without a connection surface it falls back to the trajectory view's `requestConfig.model`), or fixed via the `model` config key. - After compaction, old events are replaced by summaries: the estimate and the official `tokenUsage` projection are based on the same event set, so they stay consistent (the official projection discards compacted usage the same way). ## Configuration Most users need zero configuration — the plugin works out of the box. The keys below can be tweaked through **Settings → Plugins → dsh-gauge** — saved changes apply **immediately, no restart needed** (the one exception is `replaceNativeStatsLine`, which decides the shadow registration and needs a restart), or by patching the `gauge` row in `~/.dsh/profiles/web/cordis.patch.yml`: | Key | Default | Meaning | |---|---|---| | `showPrice` | `true` | Show the cost estimate (stats line + panel) | | `showPeakBadge` | `true` | Show the Beijing peak-hour badge in the stats line | | `replaceNativeStatsLine` | `true` | Replace the official stats row (`false` keeps the official native row) | | `hitRateDecimals` | `1` | Decimals for the cache-hit percent (0–2) | | `tokenDecimals` | `1` | Decimals for the compact K/M token format (0–2) | | `panelExactTokens` | `true` | Show full token totals in the panel (`false` uses K/M abbreviations) | | `currency` | `auto` | `auto` follows the UI language (English → `$` + USD plans, otherwise `¥` + CNY plans); or set `¥` / `$` explicitly | | `model` | `auto` | `auto` derives the model from the session; or set a model id explicitly | **Developer-only (not exposed in the UI)** — the peak windows `peakHours`, the CNY price table `pricePlans`, the USD price table `usdPricePlans`, the effective-date `nextFrom`, and the off-peak factor `offPeakFactor` are built into `src/config.ts` with DeepSeek-official defaults. Change them by editing that file; ordinary users should not override them. ```yaml # ~/.dsh/profiles/web/cordis.patch.yml — flat loader patch entries - id: gauge config: showPrice: true showPeakBadge: true hitRateDecimals: 1 tokenDecimals: 1 panelExactTokens: true currency: auto ``` **Built-in prices & the price switch** — the current flat prices (before the switch) and the new peak/off-peak official prices are baked into `src/config.ts` (`pricePlans` for the CNY table; `usdPricePlans` for the international USD table). The estimate walks each assistant message and prices it by its own timestamp (peak rate during peak windows, half price otherwise), and automatically switches to the new prices at `nextFrom`. There is no separate "cache write" bucket in the official price list. > The cost is an **estimate**; the official bill is authoritative. Patch entries are **flat** > `{id, ...}` loader entries — there is no `update:`/`disable:` wrapper; writing `- update:` is > rejected with an error. If the config card is missing, see the whitelist note in Troubleshooting. ## Comparison with dsh-usage [`dsh-usage`](https://www.npmjs.com/package/dsh-usage) (v0.1.0) appeared the same day this plugin was written, so here is an honest, source-level comparison. | Dimension | dsh-usage | dsh-gauge | |---|---|---| | Cache-hit **rate** (%) | — no hit-rate figure at all, only raw cache tokens | precise hit rate with configurable decimals (99.8% stays 99.8%) | | Peak/off-peak pricing | — no peak/off-peak handling; bundled rates are the **2026-04-24 USD table**, so the estimate goes stale after the 2026-08-16 price change | per-message-timestamp peak/off-peak pricing, current-vs-next price comparison with automatic switch at the effective date | | Granularity | per-turn readout under each assistant message + Settings **Usage** page (52-week heatmap, provider/model totals, cross-session) | session-scoped stats row (replaces the native row) + header ⓘ panel (model, buckets, context occupancy, cost) | | Cost accounting | replay-derived `modelCost` projection, effective-dated rates, coverage notes for unpriced/without-usage calls | `tokenUsage` projection × built-in price tables (CNY + USD) | | Data source | durable log replay (survives paging/compaction) | `tokenUsage`/`contextPressure` projections | | Language | English-only UI text | bilingual (zh/en) | | Native row | adds its own row | **shadows** the official row by default | | Cache-write bucket | priced as its own bucket | hidden while 0 | **In short:** dsh-gauge is a precision/efficiency gauge — the exact cache-hit rate the official UI rounds away, peak-hour awareness, and a live cost check that follows the new peak/off-peak pricing without a maintenance burden. The two are **complementary** and can be installed side by side — different slots, different ids, no conflict. ## Troubleshooting - **"写入" (write) is always 0** — by design: some adapters never report cache-write tokens, so the bucket is hidden while zero. If a future provider reports writes, it reappears. - **Price/cost looks wrong** — the estimates use the built-in price tables in `src/config.ts`; adjust `pricePlans`/`usdPricePlans` there, or set `currency`/`model` explicitly. The cost is an estimate; the official bill is authoritative. - **Changes don't show up** — except `replaceNativeStatsLine` (which decides the shadow registration), saved config changes apply **immediately**; if you changed `cordis.patch.yml` instead, restart `dsh web`. - **No dsh-gauge card under Settings → Plugins → configurable** — current DSH versions hard-code the namespaces exposed to web clients in `dsh-host-apiproxy`'s `WEB_SETTINGS_NAMESPACES` allowlist (the official comment marks plugin self-registration as deferred work); a namespace absent from the allowlist is never returned by `settings.describe` even when its owner registered it, so the card stays hidden. Add `gauge` to the allowlist in the host installation and restart `dsh web`: ```js // /node_modules/@deepseek-ai/dsh-host-apiproxy/lib/index.js const WEB_SETTINGS_NAMESPACES = [ "agent-loop", "shell", "locale", "permission", "ui-conversation", "ui-theme", "web-search-deepseek", "gauge", // ← add this line ]; ``` Without the allowlist entry the stats row and usage panel still work — only the config card is hidden (you can still configure via `cordis.patch.yml`). The requirement disappears once DSH opens plugin self-registration. - **The page fails to boot** — make sure `lib/client.js` is the bundled client artifact (run `npm run build`, which produces the `__ModuleLoader__.load` format; a raw tsc ESM output breaks the page). ## Development ```sh pnpm install pnpm run typecheck pnpm test pnpm run build ``` `npm run build` compiles with tsc and then bundles the client entry with `scripts/build-client.mjs` into the DSH client-module loader format. ## License MIT