Shows token and CNY cost per turn and per session in the harness's own statistics form, a whole-account bill over 1/7/30-day windows with cost-per-unit-of-work trends, and an advisor that re-measures its own suggestions after you adopt them.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:Pingze-github/dsh-cost-audit
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
中文 | English
DSH Cost Audit
A DeepSeek Harness plugin that answers one question: where did the money go, and did acting on it help?
It puts cost in RMB on the two surfaces the harness already uses for statistics — under each turn, and under the whole session — and it counts things nothing else in the tree counts, starting with the summarization call a context compaction bills (split from the compactions you asked for yourself). On top of that sits the part that makes it an audit rather than a dashboard: an advisor that names what is actually costing you, offers a one-click fix, and then re-measures the metric after you adopt it, so "I followed the advice" becomes a number instead of a feeling.
Everything is rendered in the harness's own stats form: the same icon pill that
opens a trigger-anchored dt/dd panel, the same design tokens, the same
geometry. The official pills are untouched; this plugin adds its own beside them.
What it adds
Per turn — a pill in the assistant actions row, between the copy button and
the branch button, next to the official "consumed" and "ran for" pills. It shows
¥0.01 and opens:
| Row | Meaning |
|---|---|
| Model | the route that served the turn |
| Cache hit | cache-read share of prompt input, never rounded up to a false 100% |
| Cache read | prompt tokens served from cache (缓存输入) |
| Uncached input | prompt tokens billed at the miss rate |
| Cache write | prompt tokens written to cache (only when non-zero) |
| Total input | 总输入 tokens over the three disjoint prompt buckets |
| Output | 输出 tokens, reasoning included |
| Turn cost | 本轮费用, CNY |
| Timing | 本轮耗时分布 — see below |
Per session — a pill on the same line as the official session stats
(1 turns 297 steps · 237 tok/s · 73.2M tok · Cache hit 99.7%), immediately to
their right: ¥2.72 · today ¥0.41 · Account balance ¥113.45 — the session total,
what of it was spent today, and the live account balance. A session picked up
again days later shows today's spend from zero, not the lifetime figure. It opens the same breakdown for
the whole durable log, plus the timing section and 账户余额 read live from the
DeepSeek billing API:
| Row | Meaning |
|---|---|
| Session cost | 会话总费用, CNY |
| today | 今日 — this session's spend on the browser's calendar day |
| of which cache re-read | 其中缓存重读 — the part of the bill that is the same context read again |
| of which compaction | 其中压缩摘要 — the summarize calls' own bill, which no other display counts |
| Cache hit / read / uncached / cache write / total input / output | whole-session token buckets |
| Timing | 会话耗时分布 — see below |
| Account balance | 总余额, live |
| Granted / Topped up | 赠送余额 and 充值余额 |
Timing breakdown (耗时分布)
Both panels end their usage section with where the time actually went, and the tool rows rank their own tool names:
| Row | Meaning |
|---|---|
| Total wall | 总耗时 — turn/start → turn/end |
| LLM time | 模型用时 — step/start → assistant/message, with the call count |
| Avg time to first token (TTFT) | 首 token 平均 — step/start → first output token |
| Generation | 模型生成 — first token → settlement, with tok/s |
| Tool time | 工具调用用时 — tool/call → tool/result, with the call count |
| Other overhead | 其他开销 — wall time the two above do not account for |
| By tool | 工具明细 — the busiest 6 tool names, then one row for the tail |
Token-saving tips (省 Token 建议)
A fourth pill appears only while there is something worth saying — a healthy session pays no space for it. It opens a list of data-grounded suggestions, each with a severity, a one-line fix, and a per-session Dismiss:
| Code | Fires when | One click does |
|---|---|---|
context-reread |
cache re-read is ≥ 35% of the session's spend (over 30+ model calls) | Compact this session — submits /compact |
fragmented-tools |
one tool has 30+ calls, 60%+ of them under 2s | steers the agent to merge the batch into one script |
repeated-target |
the same tool hits the same file/command 4+ times | steers it to read once and locate with grep |
idle-grinding |
30+ steps with no write, edit, or deliverable | asks for a status report instead of more probing |
tool-failures |
one tool fails 3 times in a row | tells it to stop and read the error |
cache-hit-drop |
hit rate below 85% over 50+ calls | asks it to find what rewrites the request head |
compaction-churn |
3+ automatic compactions, or those summaries cost ≥ 10% of the session | — (a host config value) |
model-retries |
5+ model retries | — |
balance-low |
the balance covers fewer than five sessions at this burn rate | — (top up) |
A compaction you ran yourself does not count as churn. The re-read tip above
recommends /compact, so counting the compaction that follows it made the
advisor argue with itself: it asked you to compact and then complained that you
compacted too often. The count gate started at 2, which meant one automatic
compaction plus the one this panel requested was already "churn". Only the
harness's own compactions feed the rule now (attributed from the command/run
that precedes the summary), the count floor is 3, and the cost gate uses their
share alone. That matters in practice — three of this machine's sessions have
53, 35 and 17 automatic compactions and zero user-triggered ones.
The body of that tip also no longer quotes a thresholdRatio figure. It used to
prescribe "0.8 → 0.3", which was wrong twice over: this machine's preset is
already standard-half at 0.5, and the plugin cannot read the host's
compaction configuration anyway. It now states the trade-off (a lower threshold
means a shorter replay and a cheaper summary, but more of them) and the manual
line names where the value actually lives.
Money is always rendered to two decimals — a ten-thousandth of a yuan is
not a figure anyone acts on. The one exception is a real cost too small to
survive that rounding, which reads <¥0.01 rather than pretending to be free.
Applying a tip
Every actionable tip carries a button that submits into this session through
the composer's own action face (setDraft + submit) — the same path the send
button takes, so the message lands in the transcript and the agent picks it up on
its next step (queued as steering when the turn is already running). A tip with
no honest automated fix carries its own line saying what to do instead — the
agent preset key to change, the thing to check in the console — because a single
generic "this one is yours to handle" named no action and read as a shrug.
Two guards, both deliberate: the button is disabled while the composer holds a draft, because acting means writing the composer and a click must never throw away what a human typed; and each tip disables itself once sent. Dismissing a tip is remembered per session in this browser.
After you apply one
An applied tip leaves the pill's count and stays in the list, marked with a verdict that keeps updating as the session runs:
| Verdict | Meaning |
|---|---|
| Adopted · improved | the metric moved the good way by more than its floor |
| Adopted · about the same | it moved less than the floor — the change did not register |
| Adopted · got worse | it moved the wrong way by more than the floor |
| Adopted · too early to tell | not enough calls since adoption to judge; the panel says how many |
The verdict block never prints a bare state. It always names what actually
ran (Ran /compact, Sent the merge instruction, …), because "Adopted · too
early to tell" on its own is indistinguishable from a click that did nothing —
which is exactly how it read the first time. While the sample is still short it
also prints the baseline it captured and how much more evidence it is
waiting for, and for /compact it prints the command's own bill once the
summary call lands (Cost ¥0.42). That last line needs the compaction tally
recorded at click time, so adoptions made before it existed simply omit it.
The reading is since adoption, not a lifetime average that history would drown out: the browser snapshots the cumulative counters the moment you click and subtracts them from a later reading. The metric per tip is the one that tip is about — context tokens per request, short-call share, repeat-call share, tool failure share, cache hit rate — and every floor is a share of the baseline, so a token count and a ratio are judged on the same scale. Nothing here calls a model; it is arithmetic on the same fold the pills already read.
A verdict is a measurement, not a promise: a metric can improve for reasons the tip had nothing to do with. Treat "about the same" as the honest default and the numbers as the evidence.
Every one is folded from the durable log — the advisor never calls a model, because a token-saving feature that spends tokens is self-defeating. The thresholds are deliberately conservative and every rule needs a sustained pattern: an advisor that cries wolf stops being read.
The interface follows the harness locale: Simplified Chinese under zh, English
under en.
The account-wide report
Per-session figures answer "what did this conversation cost" and cannot answer "am I spending less than I used to" — one session is one piece of work, and no two are comparable. So there is a second read: a route merges every session's day buckets into one calendar, and the cost panel grows a "whole account, last 7 days" section over it.
The denominators are chosen so the advice cannot mark its own homework:
| Row | Why that denominator |
|---|---|
| Per turn | the denominator is the human's — your own message count — and no advice of ours can change it, so the trend is the fair comparison |
| Per edit delivered | what one write / edit / present costs: the actual work denominator. A day of pure chat or research has none and reads — |
| Per 1K output tokens | it rises when the input is many times the output, which is where cache and context show up |
| Re-read / cold input / output | these three are the total; the total alone cannot say why it moved |
| of which compaction | a subset of the three above, not a fourth line — a summarization call's tokens are still re-read or cold input |
| Peak-hour share | a figure, not advice |
Every row carries its own one-line explanation, in both locales. A denominator nobody understands is worse than no number: the first question this panel got was "which of these is actually the work".
Three boundaries are printed in the panel itself, and worth repeating here:
- Amounts use the configured list prices.
web/deepseek-search-llm-requestandsession/title-llm-requestcarry no usage in the log, so this is a lower bound, not an exact figure.- The report shows that spending moved; it cannot show that your own advice caused it. It picks denominators the advice cannot move and splits the cost into explainable parts; attributing the change is still yours to do, by lining the adoption dates up against the trend.
Because the whole thing is folded from the durable log, history backfills — you do not have to wait a week to see a week (up to 90 days).
Pricing
Costs are the official DeepSeek list prices in CNY per 1,000,000 tokens (api-docs.deepseek.com), split by billing period. Peak is Beijing time (UTC+8) Monday–Friday 09:00–12:00 and 14:00–18:00; every other hour, plus all weekend, bills at half.
| Model family | Cache hit (空闲 / 高峰) | Cache miss (空闲 / 高峰) | Output (空闲 / 高峰) |
|---|---|---|---|
deepseek-flash, deepseek-v4.1-flash, deepseek-v4-flash |
0.02 / 0.04 | 1 / 2 | 4 / 8 |
deepseek-v4-pro, deepseek-pro |
0.15 / 0.30 | 4.5 / 9 | 13.5 / 27 |
A model name that carries neither a v4 nor a deepseek marker is counted but
never priced — the panel shows — for its cost and says how many tokens were
left unpriced, rather than billing a foreign model at DeepSeek's rates.
Costs are an estimate: they are computed from provider-reported usage, not read back from a billing statement. The account balance is not an estimate — it is a live read, so it is the ground truth to check the estimate against.
Config
Every field is optional; overrides go in a profile patch layer with the same id.
- id: dsh-cost-audit
config:
baseUrl: "https://api.deepseek.com" # billing API origin
credentialRef: "DEEPSEEK_API_KEY" # reference resolved through ctx.credentials
balanceCacheMs: 60000 # how long one balance read is reused
requestTimeoutMs: 8000 # upstream timeout
pricing: # CNY per 1,000,000 tokens
flash:
peak: { cacheHit: 0.04, cacheMiss: 2, output: 8 }
off: { cacheHit: 0.02, cacheMiss: 1, output: 4 }
pro:
peak: { cacheHit: 0.30, cacheMiss: 9, output: 27 }
off: { cacheHit: 0.15, cacheMiss: 4.5, output: 13.5 }
Install
dsh plugin --profile web add github:Pingze-github/dsh-cost-audit
Then reload the browser page to pick up the client bundle. From a checkout you are editing, install it as a live link instead:
dsh plugin --profile web add link:/path/to/dsh-cost-audit
⚠️ Renaming a link:-installed plugin leaves a boot entry that cannot be
unmounted, and both names then resolve to one client.js: the browser executes
one bundle twice, duplicate factory registration cascades, and the whole plugin
list fails to load. The remedy is removing the bundle from the profile manifest
and adding it back — every entry then re-resolves its path — not restarting
dsh web.
link: keeps the checkout live, so edits to index.js / client.js are served
without reinstalling. dsh-hotswap (if installed) hot-mounts a new bundle entry
from the written dsh.profile.bundles — no dsh restart, which matters because
restarting dsh web kills the session hosting it.
The scripts under scripts/ verify against a running deployment, not a
fixture: check.sh is the offline gate, smoke.sh sweeps every session on the
machine through the live route, and gui-probe.mjs renders the real GUI in
headless Chromium. smoke.sh needs DSH_HOME and an authenticated URL (it reads
one from /var/log/dsh-web.log, or DSH_STATS_URL).
How it works
- Host half (
index.js) registers one session projection,dshCostAudit, that folds the whole durable log into per-turn and whole-session billed buckets with their CNY cost. It rides the same pipeline as the harness's owntokenUsage/sessionStatsunits, so figures stay complete however much history a client has paged in. Retry accounting mirrorstoken-meter: an Assistant settlement replaces its own(turn, step)slot, andllm/retry-startedcloses that slot so a retried attempt adds instead. - Host half also registers two exact Connection Fetch routes.
/api/dsh-cost-audit.balanceserves the account balance and an on-demand fold of any session; the fold exists because the projection pipeline only serves a unit to a client once that session has a materialized cell, so a session whose persisted projection checkpoint predates this plugin has nodshCostAuditrow and the route closes that gap from the same unit definition./api/dsh-cost-audit.reportmerges every session's day buckets into one calendar for the account-wide report. It caches for a minute, because folding a hundred-plus sessions costs a full log read each. - Client half (
client.js) registers into the harness'sconversation.chat.assistant-actionsandconversation.composer.dockslots. It carries no build step: it is a hand-written bundle in thewindow.__ModuleLoader__.load({ id, factory })form, so the package installs straight from a checkout. - Daily spend is folded by local calendar day (the host's clock, which is the browser's too) and kept for the newest 90 days, so a session carried across months does not grow its checkpoint without bound. Each day carries three independent splits that the report never mixes up: the token axis (re-read + cold input + output) and the tariff axis (peak + off-peak) each sum to the day's total, while the compaction figure is a subset of the token axis rather than a fourth sibling.
- Session-row placement is measured, not hard-coded, and the pair stays
centred. The composer dock stacks its slot entries and the official stats row
is a centred flex row this plugin does not own, so three things are measured:
the row is lifted by the official row's height, its content indented to start
where the official content ends, and the official row is shifted left by half
of what this pill adds (a
translateXthis plugin sets and clears, never a layout change) so the two read as one centred group. A longer official label, a changed font size, or a resized window all land in the right place; when the group would not fit in the band, the row falls back to a centred line of its own with the official row left exactly as the harness drew it.
Layout
index.js host half: dshCostAudit projection + balance / report routes
index.d.ts public types + the SessionProjectionMap augmentation
client.js browser half: the two slot entries
cordis.patch.yml bundle patch (mounts the host entry)
scripts/check.sh the project's single success criterion
scripts/check.mjs host-half behaviour: pricing, retry accounting, route, fold
scripts/smoke.sh the runtime verification in one call (gate + every session)
scripts/link-deps.sh links node_modules at the running harness for check.sh
scripts/gui-probe.mjs headless-Chromium probe of the live GUI
License
MIT.
Links
More in this category
bowenliang123/dsh-context★ 1444
DSH context insight panel: Context dashboard + /context command + Context browser — one-stop context lifecycle management with categorized composition, content details, evolution trends, compaction/injection events, and stats.
Han-1413141/dsh-cost-meter★ 314
Per-session and daily API cost, budget with usage %, official balance, history dashboard, and one-click official price sync with peak/off-peak pricing.
zh667/TokenLedger★ 202
Sidebar usage panel that attributes tokens to the relay site that served each request, read from your existing provider config: today/month/all-time totals, per-site and per-model breakdowns, a year activity heatmap, and New API / Sub2API / DeepSeek balances.
wssfk12138/dsh-damage-pulse★ 180
Tracks DeepSeek token usage, per-call and session costs, and account balance with cache-aware charge animations in the DSH Web UI.
Ychris12138/dsh-usage-stats★ 161
Multi-provider usage dashboard with provider/model token breakdowns, calendar drill-downs, account balances, and OpenCode Go / Z.ai subscription quota tracking.
PolinniZhong/dsh-personal-center★ 120
Personal center for DeepSeek Harness: cross-session usage statistics, per-model cost estimation, global custom instructions, a global font-size adjuster, a data-driven desktop pet with bitmap & vector skins, and a conversation status overview, all local and offline.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.