DeepSeek Harness plugin

dsh-meter

DeepSeek's time-of-use meter for the Web UI: session cost billed at the tariff in force when each request was dispatched, a local-time tariff clock, cache economics, and the account balance.

Jump to install

Source facts

Repository
dshworks/dsh-meter
Latest update
Aug 20, 2026
Category
UI Enhancements
GitHub stars
1
Format
plugin
Catalog evidence
Upstream dsh.bundle evidence
Evidence path
package.json#dsh.bundle
Checked against
0.1.0-rc.8
Upstream check date
2026-08-20

This evidence comes from the upstream catalog. This site has not installed, run, or security-reviewed the plugin.

Install

Start with a prompt that asks an agent to review the GitHub repository and source. Switch to the command if you want to install it yourself.

Copy this prompt into DSH, Codex, or another agent and ask it to review the GitHub repository and source first.

Do not install or run any commands yet. Read this plugin's GitHub repository, README, and relevant source code. Then answer the questions below clearly and directly so I can decide whether it fits my needs:

1. What is this plugin, and what problem does it solve?
2. Who is it for, and what are its typical use cases?
3. How is it used after installation? Include one minimal example.
4. What known limitations or privacy, security, compatibility, or maintenance risks does it have?
5. Give a clear recommendation: recommend, conditionally recommend, or do not recommend, with reasons.

Distinguish statements documented by the repository, inferences from source code, and unknowns. If evidence is insufficient, say so explicitly. Do not guess or simply repeat the README.

GitHub: https://github.com/dshworks/dsh-meter
Plugin: dsh-meter
Author: dshworks

Check the source files

Read the README and other files from this plugin directory before installing.

File explorer4 files
README.mdSource · read only
README language

<table> <tr> <td width="42%" valign="top">

dsh-meter

English | 中文

DeepSeek bills by time of day now. This is the meter for it.

Two peak windows a day, off-peak at half the peak rate. Cost stopped being a number you read afterwards and became a rate you are standing in.

One line under the composer: what this session cost, which tariff is running, how long until it flips. Hover it for the tariff clock, the cache economics, and your balance.

![site](https://dsh.works/dsh-meter/) ![ci](https://github.com/dshworks/dsh-meter/actions/workflows/ci.yml) ![powered by dsh](https://github.com/deepseek-ai/deepseek-harness) ![license: MIT](LICENSE)

</td> <td width="58%" valign="top">

<img src="https://raw.githubusercontent.com/dshworks/dsh-meter/main/docs/meter-light.png" alt="The meter's line under the composer, with its card open: session total, tariff clock, cache and input breakdown, and account balance" width="420">

</td> </tr> </table>

Install

dsh plugin --profile web add @dshworks/dsh-meter
dsh --profile web

dsh plugin forwards to pnpm, so pnpm must be on PATH. Nothing else to configure: the meter appears under the composer as soon as a session bills its first request.

The line

2 turns · 2 steps | LLM 3.1s | TTFT avg 1.3s · 41 tok/s | Cache hit 50% | Input 44.3K tok
                    ¥0.0672  |  peak  |  off-peak in 48m

The harness's own stats line, untouched, and ours under it. This plugin adds a line — it does not shadow the shipped one, which is what a cost plugin has to do to append to it.

Hover or focus for the card. Inside a peak window the tariff is called out in amber and the countdown runs to the next off-peak hour:

<img src="https://raw.githubusercontent.com/dshworks/dsh-meter/main/docs/meter-dark.png" alt="The same card in dark mode inside a peak window" width="620">

The card showsWhy it is there
The session total, request count, modelsThe number, once, at full size
A 24-hour tariff strip in your local time, with a live now-markerPeak windows are published in UTC. Reading them off a strip beats doing timezone arithmetic at 11pm
cache hits / fresh input / output — tokens and money on eachA cache hit costs 1/30th of a miss. This is the row that shows whether your prompt prefix is stable
Your account balance, and how much of it is granted creditGranted balance expires; topped-up balance does not
The same tokens priced under the other tariffBefore the switchover: what the new rates do to this session. After: what waiting for off-peak is worth
What the cache savedThe counterfactual where every hit had been a miss

Proof

Live-verified against dsh 0.1.0-rc.6 on 2026-08-15, in a real web session on DeepSeek-V4-Pro — not a mock:

> Through 0.1.0-rc.8, checked at the source rather than re-run. The one > harness package this plugin reads, @deepseek-ai/dsh-session-projection, has > a byte-identical src/ in rc.7 and rc.8, so the projection schema the fold > below is written against did not move. rc.8 does change the SQLite storage > format incompatibly — that is the store underneath the projection, not the > projection, and this plugin never touches it. The receipts in the table are > still rc.6 receipts; nobody has re-run them on rc.8.

ClaimHow it was checked
Loads in a stock web profiledsh --profile web --dump-config lists it; /plugins/@dshworks/dsh-meter/client.js serves 200
The readout is correct22.2K cache-miss input on v4-pro at the flat rate = ¥0.0665; the line and the card agree with the harness's own token counts
Survives a restartServer restarted, session reopened cold — the projection replays from the durable log at the same figure
Both themes, both tariff statesLight and dark, flat and peak, captured above — this predates the 08-16 switchover, so the flat readout is one a new session no longer reaches
Currency detectionA live account returns {"currency":"cny", ...} and the whole surface switches to ¥ with no configuration
50 tests, CI greenpnpm test — the fold, the tariff clock, the rate card, the balance reader, and the generated-bundle sync check

Two currencies, no conversion

DeepSeek publishes two independent rate cards — USD for the international platform, CNY for the mainland one. An account is billed against exactly one of them, and neither table is a conversion of the other, so a plugin that picks with an exchange rate is wrong twice.

dsh-meter computes both and lets the account decide. GET /user/balance returns the currency the account is denominated in — a live account lists both rows with only one funded — so the meter reads the funded row and shows that side. Nothing to configure, nothing to guess from your interface language.

The balance request runs on the host, keyed by the same credential seam the LLM adapter uses; the browser gets parsed numbers over GET /dsh-meter/balance and never the key. It fires when the meter mounts and when you open the card, not on a schedule — set balance: false to turn the whole thing off and the meter keeps working.

The rate card

Carried verbatim in [lib/core.js](lib/core.js), per 1M tokens.

cache hitcache missoutput
v4-flash off-peak$0.007 / ¥0.05$0.22 / ¥1.5$0.66 / ¥4.5
v4-flash peak$0.014 / ¥0.10$0.44 / ¥3$1.32 / ¥9
v4-pro off-peak$0.022 / ¥0.15$0.66 / ¥4.5$1.98 / ¥13.5
v4-pro peak$0.044 / ¥0.30$1.32 / ¥9$3.96 / ¥27

Peak is 01:00–04:00 and 06:00–10:00 UTC (09:00–12:00 and 14:00–18:00 Beijing). Every other hour is off-peak, including the two-hour gap between the windows. Off-peak is exactly half of peak — and still above the flat rate it replaced, by about 2.3x on output.

<details> <summary>The retired flat card, kept to reprice history</summary>

Billed at every hour until 2026-08-16 16:00 UTC. Upstream no longer publishes it; the meter keeps it because a session logged before the switchover must still cost what it actually cost.

cache hitcache missoutput
v4-flash flat$0.0028 / ¥0.02$0.14 / ¥1$0.28 / ¥2
v4-pro flat$0.003625 / ¥0.025$0.435 / ¥3$0.87 / ¥6

Against it, pro rose 6x/12x on cached input, 1.5x/3x on cache-miss input and 2.3x/4.6x on output (off-peak/peak). The steepest rise is on the cheapest token, which is the one an agent sends most of.

</details>

Source: <https://api-docs.deepseek.com/quick_start/pricing>, diffed against both locales daily (see below), last confirmed in sync 2026-08-20 — and against a real bill: 188,542 cache-miss tokens on pro, off-peak, settled at ¥0.84, i.e. ¥4.46/1M against the published 4.5.

The card is also a feed

Building your own estimator? Take the card, not the numbers in this table.

https://dsh.works/dsh-meter/pricing.json

Static JSON, no key, no rate limit. Both currencies, both tariffs, the UTC schedule, the retired flat card for repricing history, and the bucket definitions — generated from [lib/core.js](lib/core.js) by [scripts/build-feed.mjs](scripts/build-feed.mjs), so it cannot state a price the meter would not charge.

In JavaScript, skip the fetch and call the same module directly:

import { costOf, tariffAt } from '@dshworks/dsh-meter/core'

const tokens = { miss: 188_542, hit: 1_204_880, out: 9_310 }
costOf(tokens, 'deepseek-v4-pro', tariffAt(Date.now()), 'cny')

lib/core.js imports nothing. Rate card, tariff clock, and cost fold are pure functions of their arguments — no clock reads inside, so history reprices at the tariff it was actually billed under.

What does it cost right now?

The feed answers this without containing a "now". It publishes the 24-hour schedule; you index it with the current UTC hour. That is why a CDN can cache it for ten minutes, or you can vendor it into a binary, and it still cannot be stale about which tariff is running:

curl -s https://dsh.works/dsh-meter/pricing.json | jq -r '
  (now|gmtime|.[3]) as $h
  | .timeOfUse.scheduleUtc[$h] as $t
  | "\($t) · v4-pro out $\(.models["deepseek-v4-pro"].rates[$t].usd.out)/1M"'

An endpoint that returned the answer instead would be wrong for as long as its cache lives, four times a day, on exactly the boundary where being wrong costs 2x.

Two ways to get this wrong, both silent:

  • Local hours. scheduleUtc is indexed by UTC hour and is not

rotated into your timezone. new Date().getHours() returns a plausible tariff that is wrong for most of the planet.

  • Broken-down time offsets. jq's gmtime is

[year, month, day, hour, …] — the hour is .[3]. Writing .[2] reads the day of the month, which is a valid index into a 24-hour array. Testing this section at 09:59 UTC, .[2] said offpeak while the real tariff was peak. It looked completely reasonable.

The schedule is anchored to Beijing (UTC+8), and China has not observed daylight saving since 1991 — that fixed offset is the only reason a table of UTC hours is safe to publish at all. The feed states it under timeOfUse.anchor, and verify-pricing checks the English page's UTC sentence and the Chinese page's Beijing sentence separately, then checks that they still agree.

One caveat no feed can fix: now is your machine's clock. If it has drifted, so has your tariff.

Why not use an existing price feed? Because none of them are right. Checked 2026-08-20, four days after the switchover, both of the sources an estimator usually reaches for still published DeepSeek's retired flat card as current:

Sourcev4-pro cache-miss inputUnderstates the real bill by
models.dev$0.4351.5x off-peak, 3.0x peak
LiteLLM$0.4351.5x off-peak, 3.0x peak
this feed$0.66 off-peak / $1.32 peak

On cached input, the bucket an agent sends most of, models.dev is off by 12x at peak. And this is not a staleness bug they can patch: both schemas hold one flat price per model per bucket, with nowhere to put a tariff. A number that is wrong for seven hours of every UTC day cannot be represented correctly in either.

OpenRouter's /api/v1/models is accurate, but for a different question — it quotes what OpenRouter charges to resell the model, not what DeepSeek bills your account.

Nobody types these numbers

Not us, and ideally not you. A hand-maintained rate card rots, and the vendor is the proof: DeepSeek's own [pi integration guide][pi-guide] ships a cost block where v4-flash's cache-read rate is a 10x decimal slip (0.028 for a rate of 0.0028) and the v4-pro figures are exactly 4x the card — they are Azure's resale prices, pasted into DeepSeek's documentation, on a page people copy into their agent config.

So the card is written by machine and reviewed by human:

readscripts/verify-pricing.mjsscrapes both locales — prices, peak windows, model list — and diffs them against the card
writescripts/apply-pricing.mjssplices the scraped values back into lib/core.js, then re-runs the verifier against its own output and reverts if that second read disagrees
shipnpm run buildregenerates the bundle, the site and the feed from the rewritten card
judgeyouthe job opens a PR; it never merges one

[A daily job](.github/workflows/pricing-watch.yml) runs all four. Locally it is npm run verify:pricing to look and npm run sync:pricing to rewrite.

Why it opens a PR instead of merging. Every invariant CI can check is structural — off-peak is half of peak, output costs more than a cache miss, a live request never prices at the retired card — and a wrong-but-plausible parse satisfies all of them. No test can tell a right price from a believable one. A human looking at a diff can.

The last price change arrived with no announcement in any channel we watch. That is the argument for the alarm; the vendor's own 10x typo is the argument for taking the keyboard away from all of us.

[pi-guide]: https://api-docs.deepseek.com/quick_start/agent_integrations/pi_mono

How the money is counted

BehaviorDetail
Source of truthProvider-reported token counts in the durable session log. Nothing is sampled or inferred
Tariff per requestDecided by dispatch time (step/start), not by when the answer finished. A request sent at 00:59 UTC is billed off-peak even if it streams into the peak window, and a session that spans a boundary is billed correctly on both sides
Billed bucketsinputTokens at the cache-MISS rate, cacheReadTokens at the cache-HIT rate, outputTokens at the output rate. The harness reports these disjoint (the DeepSeek adapter subtracts hits out of prompt_tokens), and reasoning tokens are already inside output
Cache writesFolded into the miss bucket. DeepSeek publishes no separate write price and bills a first-time prompt at the miss rate; the DeepSeek adapter never reports one
Failed requestsA step's usage chunk is counted even when the request then fails; the finalized message REPLACES that sample rather than adding to it, including when the message names a different model than the request header did
Unknown modelsCounted and named, never priced. A model with no published rate contributes tokens and requests to the readout and zero money, and the card says so
DurabilityOne session projection (costMeter), folded from the log. It survives paging, compaction, a reload, and a server restart, and rides the standard projection cache

Model Experience

None. dsh-meter adds no tool, no system-prompt section, no message, and no model call; it does not touch the request. Cost belongs to the person paying, not to the agent's context window.

#### KV Cache effect

None — the plugin never participates in a request.

Configuration

Everything below is a validated config field, set in your profile's cordis.patch.yml:

- id: dsh-meter
  config:
    currency: cny        # pin a rate card instead of detecting it
    balance: false       # never call /user/balance
KeyDefaultMeaning
currencyautoWhich rate card to show: auto (the account's own balance currency, then the interface language), usd, or cny
balancetrueServe the account balance to the Web UI
apiKeyEnvDEEPSEEK_API_KEYCredential reference holding the API key
baseUrlhttps://api.deepseek.comOrigin the balance is read from
balanceTtlMs300000Minimum age before a card opening refetches the balance
balanceTimeoutMs4000Per-request timeout for the balance read

Development

pnpm install
pnpm test                        # generated-artifact checks, then vitest
pnpm run build                   # regenerate all three artifacts after editing core/ui
pnpm run verify:pricing          # diff the card against DeepSeek's own pages (network)

lib/core.js holds the rate card, the tariff clock, and the fold. lib/balance.js is the account reader. src/ui.js is the browser surface. Three artifacts are generated from that one card — lib/client.js (the bundle the harness serves), docs/index.html (the site) and docs/pricing.json (the feed) — so the price table exists in exactly one file, and pnpm test fails if any of them drifts from it.

verify:pricing is the only script that touches the network, and it is deliberately outside pnpm test: a unit suite must not fail because a documentation site is slow, and a price alarm must not stay silent because nobody opened a PR this week. It runs on its own daily schedule.

Known limitations

  • It is an estimate, not the invoice. List prices times reported

tokens. Promotional pricing and any relay in front of the API are invisible to it.

  • Tariff is inferred from the dispatch timestamp, which is the

harness's clock. A request DeepSeek receives on the far side of a boundary can bill differently by a second or two.

  • Non-DeepSeek routes are not priced. Their tokens are counted and

their model named; the card reports them as unpriced instead of quietly applying DeepSeek's rates to someone else's API.

  • The rate card is compiled in. DeepSeek changes prices; this plugin

ships the time-of-use card as re-checked on 2026-08-17 and needs a release to follow the next change.

  • Web only. The projection is available to any surface, but the

readout is built for the Web UI. There is no TUI line.

Prior art

The dsh registry lists ~50 cost and usage plugins; several arrived the same week DeepSeek dated the switchover, and reading them shaped this one. The cache-savings framing comes from deepseek-cli's local usage ledger.

License

MIT. Not affiliated with DeepSeek. "DeepSeek Harness" is DeepSeek's trademark, used here only to say what this works with; the name follows the "DSH" form their brand guidelines recommend.