Lopncod/dsh-cost-meter

dsh-cost-meter

DSH plugin: a persistent per-answer / per-session cost pill under the composer, priced with the official DeepSeek peak/off-peak rates.

Uncategorizedcost-meterdeepseek-harnesstoken-usage

dsh-cost-meter

A cost meter plugin for DeepSeek Harness (DSH) Web / Desktop: one small ambient pill right under the composer that tells you what this answer cost and what this session has cost so far.

本会话消耗 ¥1.23 · 本次回答 ¥0.087            off-peak · peak in 2h15m

English | 中文

What it shows

  • Last answer — the estimated cost of the most recently completed turn. It refreshes the moment the turn settles and flashes once.
  • Session cost — the running total for the current session, including the turn still in flight.
  • Click the pill for a per-turn breakdown: cost per turn, the four token buckets (cache-hit read / cache-miss read / cache write / output), the rates applied to that turn, its peak/off-peak tag, and its step count. When the ledger holds more than one session, the panel also lists every locally recorded session and a grand total.

Install

Install the bundle with DSH's Plugin Manager:

plugin_manager  action: install_bundle  target: <this directory, absolute path, or this repository>

or point it straight at the repository:

plugin_manager  action: install_bundle  target: github:Lopncod/dsh-cost-meter

DSH must be restarted afterwards. The Host half is an ESM module imported at startup: editing files or re-enabling the bundle does not reload it (verified locally — a toggle only remounts the same module generation).

How costs are computed

Estimates use DeepSeek's published CNY-per-million-token rates and its peak/off-peak schedule:

BucketMeaningFlash off-peak / peakPro off-peak / peak
Cache-hit readPrompt tokens served from the prefix cache0.02 / 0.040.15 / 0.30
Cache-miss readPrompt tokens not in cache1 / 24.5 / 9
Cache writePrompt tokens written to cache (charged at the miss rate)1 / 24.5 / 9
OutputCompletion, reasoning tokens included4 / 813.5 / 27
  • Peak = Asia/Shanghai, Mon–Fri 09:00–12:00 and 14:00–18:00. Every other moment — evenings, weekends, and Chinese statutory holidays — is off-peak.
  • These are estimates, not a bill. The real charge is whatever the balance on the DeepSeek side moved by.

Token accounting (the load-bearing detail)

All four buckets come from the real usage on committed assistant/message events — the token counts the model was actually billed for. Nothing is estimated:

  • inputTokens is the uncached bucket and is disjoint from cacheReadTokens / cacheWriteTokens; the three sum to the prompt total. That definition is copied from DSH itself — the tokenUsage projection in @deepseek-ai/dsh-token-meter calls it uncachedInputTokens and adds it to the other two (lib/types/usage-projection.js).
  • reasoningTokens is a subset of outputTokens and is not charged again, or the output side would be billed twice.
  • A repeated settlement for the same (turn, step) replaces rather than adds to the previous one; only llm/retry-started reopens that slot. Same semantics as token-meter's addReplacing, so a retry or a second subscription cannot bill one turn twice.

Configuration (optional)

$DSH_HOME/.dsh-cost.json (on Windows, C:\Users\<you>\.dsh\.dsh-cost.json). It is re-read on change; no restart needed:

{
  "peakMode": "auto",
  "defaultModel": "deepseek-flash",
  "rates": {
    "my-proxy-flash": { "hit": 0.01, "miss": 0.5, "out": 2 }
  }
}
FieldMeaning
peakMode"auto" (default, judge peak/off-peak in Beijing time) / "always" (price everything at peak, an upper bound) / "never" (everything off-peak)
defaultModelModel id used to pick a rate row when request/header is missing (old logs, non-standard callers)
ratesCustom rates. Keys are substrings of the model id, longest match first, overriding the built-in rows. Values are {hit, miss, out} in CNY per million tokens; a single number means the same rate at peak and off-peak; the key _default replaces the fallback row

The ledger lives at $DSH_HOME/.dsh-cost-ledger.json (per-session, per-turn). Deleting it only loses history; the next event starts accumulating again.

HTTP surface

The Host half registers a few read-only routes through ctx.webServer, readable by any same-origin script:

RoutePurpose
GET /dsh-cost/state?sessionId=<id>Current session + all-session summary + peak state. An unknown sessionId falls back to the most recently recorded session and reports fallback: true
GET /dsh-cost/ratesEffective built-in / custom rate table
GET /dsh-cost/debugThe last 12 /state request URLs — answers "is the page asking, and with which identity?"

DSH's browser auth fence covers only the /api channel; plugin routes are unaffected.

Development

npm test        # 18 checks: peak boundaries, longest-match rate lookup, real cached-request pricing,
                # replacement semantics, ledger JSON round-trip
FileResponsibility
lib/pricing.jsPure pricing layer: peak schedule, rate lookup, bucket split, cost
lib/ledger.jsFolds session events into per-turn / per-session rows, with (turn, step) replacement and the persisted shape
lib/index.jsHost half: folds session/event, persists the ledger, serves the routes
client.jsClient half: the pill and breakdown panel in the conversation.composer.dock slot

The Client half is a DSH dynamic client module (window.__ModuleLoader__.load({ id, factory })). It requires react from the frozen platform table and no Harness Client package at all; every style is inline and references only --dsw-alias-* theme tokens.

Known limitations

  • Only turns after the plugin was enabled. The ledger accumulates from the live session/event feed; sessions that ran before installation have no history.
  • Subagent sessions are recorded per their own sessionId rather than folded into the parent row, but they do count toward the grand total.
  • A forked / resumed session accumulates only events that happen in this process; the copied historical prefix is not billed twice, matching how the provider charges.

License

MIT © Lopncod