dsh-burn

September 3, 2026 · View on GitHub

Token spend, peak/off-peak pricing, and account balance for the DeepSeek Harness web UI.

中文

dsh-burn

What it does

  • Per-reply cost on hover — every settled assistant message shows ↑in · ↓out ¥cost in its action row (copy/like/share), priced at the billing tier in effect at that reply's own timestamp against the live price book.
  • Cumulative spend + balance in the conversation header累计 ¥xx · 余额 ¥xx sits right of the agent-preset label (Standard mode). The cumulative figure is folded live from the session snapshot (updates the moment a reply settles); the balance polls the DeepSeek /user/balance API (host-cached, 60 s).
  • Settings panel (费用 / Cost) — edit per-model prices, pick the display tier (auto / peak / offPeak / average), load the official DeepSeek price table, and customize the peak/off-peak schedule. Tier switches re-price instantly.

Pricing follows DeepSeek's billing: each reply is charged at the tier in effect at its own generation time — weekdays peak 09:00–12:00 and 14:00–18:00 Beijing time, everything else off-peak at half price, and weekends off-peak all day (2026-08-23 rule). auto (the default) and average both price that actual per-reply split (the time-weighted figure); peak / offPeak pin an explicit tier for manual inspection. The schedule is customizable. Prices default to the built-in official table, so the plugin works with zero configuration.

Compatibility

Declared in package.json (engines and dsh.compatibility.dshReleases) and rechecked by the DSH STORE source-policy scanner on every pinned commit:

TargetDeclared rangeEvidence
Node.js^22.19.0 || >=24.0.0 (engines)verified on Node 22.22.2 (macOS arm64)
DSH releases0.1.1-rc.2: compatible (dshReleases)disposable-profile install + uninstall, live web-profile boot with HTTP 200 on both plugin endpoints (see COMPATIBILITY.md)

The peer-dependency floor is the DeepSeek Harness 0.1.0-rc.7 contract family; other DSH release lines are declared per version only once verified. The DSH STORE catalog additionally tracks its own independent evidence status (verified / partial / unknown) for install/start/uninstall — this source matrix is a declaration, not a substitute for that runtime evidence.

Install

The plugin is a bundle: dsh plugin add mounts it automatically (the manifest's dsh.bundle.patch is applied for you), no manual patch needed.

From GitHub

dsh plugin --profile web add github:yishan/dsh-burn
dsh web   # restart the web service for the profile change to take effect

From npm (once published)

dsh plugin --profile web add dsh-burn
dsh web

From a local directory

dsh plugin --profile web add /path/to/dsh-burn
dsh web

Uninstall

dsh plugin --profile web remove dsh-burn
dsh web

Configuration

Zero-config by default: the built-in official price table (deepseek-v4-flash / deepseek-v4-pro) is back-filled on boot, and everything is editable live in 设置 → 费用 (persisted to a per-profile settings file — <profileDir>/plugins/dsh-burn/settings.json, derived from the Loader's base URL — which overrides the profile config on restart). A legacy global $DSH_HOME/plugins/dsh-burn/settings.json migrates once on first boot; settingsPath in the profile patch overrides the location explicitly.

The balance readout needs a DeepSeek API key resolved from DEEPSEEK_API_KEY.

Advanced: override via profile patch

A profile-level patch targeting id: dsh-burn replaces the bundle row's config (prices, display tier, schedule, balance endpoint):

# $DSH_HOME/profiles/web/cordis.patch.yml
- id: dsh-burn
  config:
    displayMode: auto          # auto | peak | offPeak | average
    tierSchedule: default      # default | custom
    customWindows: []          # [{ start: 540, end: 720, tier: 'peak' }, ...]
    defaultTier: offPeak       # tier for minutes not covered by customWindows
    prices:
      deepseek-v4-flash:
        peak:    { input: 3.0, cacheRead: 0.1,  cacheWrite: 3.0, output: 9.0 }
        offPeak: { input: 1.5, cacheRead: 0.05, cacheWrite: 1.5, output: 4.5 }
      deepseek-v4-pro:
        peak:    { input: 9.0, cacheRead: 0.3,  cacheWrite: 9.0, output: 27.0 }
        offPeak: { input: 4.5, cacheRead: 0.15, cacheWrite: 4.5, output: 13.5 }
    defaultPrice:
      peak:    { input: 3.0, cacheRead: 0.1,  cacheWrite: 3.0, output: 9.0 }
      offPeak: { input: 1.5, cacheRead: 0.05, cacheWrite: 1.5, output: 4.5 }
    apiKeyEnv: DEEPSEEK_API_KEY   # credential ref for the balance endpoint
    balanceRefreshMs: 60000
    balanceBaseURL: 'https://api.deepseek.com'
    # settingsPath: /abs/path/to/settings.json   # optional override of the profile-isolated default

Prices are CNY per 1M tokens. cacheWrite mirrors the cache-miss rate for providers that don't bill cache writes separately.

How it works

  • Host (src/index.ts, src/projection.ts): a CostMeter service folds the session log into per-model, per-tier token buckets (cost session projection — each message bucketed by the billing tier at its own timestamp), serves GET/POST /api/cost/settings, and proxies GET /api/cost/balance through the credentials seam (401 without a key, 502 on upstream failure, 60 s cache with concurrent-request dedup). The projection's totalCost is computed live from the buckets against the current price book at the current display tier, so price/tier changes apply to the whole log without a re-fold.
  • Client (src/client/): three slot seats — conversation.session.header.actions (cumulative spend + balance, right of the agent-preset label), conversation.chat.assistant-actions (per-reply cost), settings.section (the 费用 page). Token buckets are re-priced at render time against the current price book (per-reply at the reply's own tier), so tier/price changes apply instantly without a restart. The session fold is incremental (append-only O(Δ), O(1) per-message lookups via an index).
  • Runtime settings persist to the per-profile settings file (see Configuration), which overrides the profile config on boot; models missing from the book are back-filled from the official price table. POST /api/cost/settings rejects unknown keys, invalid values, overlapping custom windows (400) and oversized bodies (413), and reports the disk-write result in persisted so the UI surfaces a failed save.

Development

pnpm install
pnpm run typecheck   # tsc --noEmit
pnpm run test        # vitest (pricing logic)
pnpm run build       # esbuild: lib/index.js (host) + lib/client.js (browser)

The client bundle is a classic-script plugin registered on window.__ModuleLoader__ and may import only the platform module table (see build.mjs).

Permissions, dependencies & failure boundaries

The plugin runs in two places: a host bundle (server side, inside the DSH process) and a web client (browser, inside the DSH web UI). Capabilities are deliberately narrow.

  • Files — the host reads/writes only its own settings file (<profileDir>/plugins/dsh-burn/settings.json, derived from the loader's base URL) and performs a one-time read migration of the legacy $DSH_HOME/plugins/dsh-burn/settings.json. No directory scanning, no other paths (settingsPath config can redirect that single file). The client uses no browser storage.
  • Network — the only absolute outbound URL in the source tree is the balance endpoint (default https://api.deepseek.com/user/balance, overridable via balanceBaseURL), called with a Bearer key resolved through the DSH credentials seam. All client traffic is same-origin (/api/cost/settings, /api/cost/balance). No other hosts, no telemetry.
  • Commands — none. There is no child_process, shell, or OS command execution anywhere in src/.
  • Credentials — resolves exactly one credential reference (DEEPSEEK_API_KEY by default) through the official credentials service, used only as the Bearer for the balance call; never logged, never persisted by the plugin.
  • Runtime dependencies — the host service uses only host-provided @deepseek-ai/* services (peer range ^0.1.0-rc.7) plus @deepseek-ai/schemastery for config; runtime deps are bundled into lib/ at build time. There are no install-time lifecycle scripts (preinstall/install/postinstall/prepare) — the npm scripts are build, typecheck, test, and prepublishOnly.
  • HTTP & projection surface — registers GET/POST /api/cost/settings and GET /api/cost/balance on the profile's web server plus one cost session projection, mounted only when the matching host children exist.
  • Failure boundaries — partial assemblies degrade rather than crash: headless (no web server) simply skips the HTTP routes; a missing credential makes the balance endpoint return 401 {no_api_key}; an upstream failure returns 502; settings POST rejects unknown keys, invalid values and overlapping time windows with 400, rejects oversized bodies with 413 (1 MiB bound), and reports a failed disk write in the persisted field; a missing or corrupt settings file falls back to the profile config and heals on the next successful save. Price/tier changes re-price the whole session log live without a restart.

A high-level security posture and the vulnerability reporting path are in SECURITY.md. Note that store-side capabilities may still require a user-reviewed or blocked status on the DSH STORE; this source-level declaration does not by itself grant automatic approval.

License

MIT