dshworks/dsh-meter

dsh-meter

The DeepSeek time-of-use meter for dsh: what this session cost, which tariff is running, when it flips, and the balance behind it — one line under the composer

미분류balancecostcost-tracking

dsh-meter

English | 中文

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

Two peak windows a weekday, off-peak at half the peak rate — and since 22 August, off-peak all weekend. 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.

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:

The card shows · Why it is there The session total, request count, models · The number, once, at full size A 24-hour tariff strip in your local time, with a live now-marker · Peak windows are published in UTC and restricted to Beijing weekdays. Reading them off a strip beats doing timezone arithmetic at 11pm — and on a weekend the strip is empty, which is the answer cache hits / fresh input / output — tokens and money on each · A 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 credit · Granted balance expires; topped-up balance does not The same tokens priced under the other tariff · Before the switchover: what the new rates do to this session. After: what waiting for off-peak is worth What the cache saved · The counterfactual where every hit had been a miss

Settings: the meter before you spend

The line needs a session, and a session that has already billed something — so the one question this plugin exists to answer was unaskable until after you had paid to ask it. Settings → Meter answers it cold.

It is deliberately not a form. Both config knobs live in the plugin's own configuration, where the harness already edits plugin config; a second write path for two fields would be the settings-page reflex rather than the content. What is here is the instrument:

The section shows · Why it is there The tariff running now, at full size, with the countdown and the clock time it ends · The reason to open the panel, answered before you read anything else The week — seven local days by twenty-four hours, peak in amber, a live now-marker · The card's strip answers "when does today change". Since weekends went off-peak this is a weekly question, and 35 amber cells out of 168 is the fastest way to see that peak is 35 hours a week, not 49 The published rate card in the currency your account is billed in · At the precision DeepSeek publishes it — ¥1.5, not ¥1.50. A rate is a quotation, not a total Your balance · Read once when the panel opens, through the same host route the card uses

The grid is laid out in your days, not Beijing's, and every cell asks the same function that prices a request — so the picture and the bill cannot disagree while still sparing you the timezone arithmetic.

In Chinese the hour slots carry a small joke. Peak and valley — 峰 and 谷 — are the old words for time-of-use electricity, and the peak half is one character from the name of the man who founded the company selling the tokens. Hover a slot: 文峰, 文谷.

Proof

Live-verified against dsh 0.1.1-rc.2 on 2026-08-24, in a real web session on DeepSeek-V4-Pro and V4-Flash — not a mock:

Re-run, because reading the source was not enough. Through rc.8 this note
argued from a byte-identical src/ that the projection contract had not
moved. It then moved: 0.1.1-rc.1 renamed schema to stateSchema and made
the client-visible view an optional wire. Because the registry reads the definition field by field and never
validates it, the old spelling did not fail — it silently meant *host-only*,
and the dock line rendered nothing on an otherwise healthy harness. A
contract that degrades to silence cannot be checked by diffing its source;
the table below is a fresh live run, not an argument.

Claim · How it was checked Loads in a stock web profile · dsh --profile web --dump-config lists @dshworks/dsh-meter; /plugins/@dshworks/dsh-meter/client.js serves 200 The line renders · ¥0.2185 \ · off-peak \ · peak in 14h under the composer, read out of the live DOM — the check that would have caught the rc.1 rename The readout is correct · 36.1K cache-miss input + 147 output across V4-Flash and V4-Pro = ¥0.2162 + ¥0.0023 = ¥0.2185, matching the harness's own token counts on the stats line directly above it Survives a restart · Server restarted, an eight-day-old session reopened cold — the projection replays from the durable log at the same figure The card opens · Per-model split, the cache counterfactual (all-peak ¥0.2185 vs all-off-peak ¥0.1093), and the account balance, with no page errors Currency detection · A live account returns {"currency":"cny", ...} from /dsh-meter/balance and the whole surface switches to ¥ with no configuration Both themes, both tariff states · Light and dark, flat and peak, captured above on 2026-08-15 — that run predates the 08-16 switchover, so its flat readout is one a new session no longer reaches 130 tests, CI green · pnpm test — the fold, the tariff clock, the rate card, the saving-mode nudge, the balance reader, the registration contract against a stand-in registry of each era, 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, per 1M tokens.

· cache hit · cache miss · output flash off-peak · $0.003 / ¥0.02 · $0.15 / ¥1 · $0.6 / ¥4 flash peak · $0.006 / ¥0.04 · $0.3 / ¥2 · $1.2 / ¥8 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, Monday to Friday, except Chinese public holidays (09:00–12:00 and 14:00–18:00 Beijing). Every other hour is off-peak, including the two-hour gap between the windows, the whole weekend, and every public holiday in full. Off-peak is exactly half of peak — and on pro still above the flat rate it replaced, by about 2.3x on output.

Chinese public holidays bill off-peak all day. The pricing footnote gained that clause between 2026-09-18 and 2026-09-19, with no date and no announcement. The meter carries the State Council's 2026 calendar, each festival's whole 放假 period (National Day is October 1–7), and its daily pricing check fails before that calendar runs out. Make-up working days that fall on a weekend stay off-peak, as the page words it. Both readings are the page's, not yet a bill's: the first peak-hour bill probe on a holiday is Tuesday 2026-10-06.

`deepseek-flash` is DeepSeek-V4.1-Flash, since 2026-09-10. It replaced V4 Flash and V4 Flash Vision Exp — vision is built in now — and the Flash price fell with it: off-peak cache miss ¥1.5 -> ¥1, output ¥4.5 -> ¥4, cache hit ¥0.05 -> ¥0.02. The old names still answer, served by V4.1 Flash and billed at the Flash price, so the meter bills a deepseek-v4-flash request dispatched after the cutover as deepseek-flash, and one dispatched before it at the old rate. The API echoes back whatever name you sent, so the echo cannot say which model billed; the dispatch time can.

The new price took effect at 2026-09-10 04:00 UTC (12:00 Beijing), as DeepSeek's release note states; the meter cuts over at exactly that instant. The pricing page itself lagged by hours, so don't date a price change by when its reference page moved. A probe at 2026-09-11 17:03 UTC settled on the new card to the cent.

Weekends have been off-peak all day since 2026-08-22 16:00 UTC (00:00 Beijing, Sunday 23 August), on the *Beijing* calendar — so the weekend turns over at 16:00 UTC, not at midnight UTC. Peak is 35 hours a week, not 49. DeepSeek announced this only in the pricing-page footnote and only until it took effect; the live page now states just the settled rule, so the announcement survives at the archived page.

Web search bills per request from 2026-10-31 16:00 UTC (00:00 Beijing, November 1). dsh's own searches already go through DeepSeek's Search and Fetch APIs and are free until then, the pricing page says. After that, a request bills only when it returns a result; failed and empty ones are free.

· per 1,000 successful requests Search · $0.75 / ¥5 Fetch · $0.75 / ¥5

The card shows a web search row once a session searches: free before that instant, then a ceiling marked ≤, kept out of the session total. It is a ceiling because of what dsh writes down. With the shipped search setting, each query is one auxiliary request to DeepSeek's Messages API, which may run up to 5 searches (max_uses). The session log records the request (web/deepseek-search-llm-request) but not how many searches it ran, so the meter counts each request at 5. Two things it cannot see at all:

  • The opt-in switch, "Use the new Web Search API" (the Web search

settings page, dsh 0.2.1 and later). Those calls leave nothing in the log that says DeepSeek served them rather than another provider, so the meter does not count them.

  • The auxiliary request's own tokens. dsh does not log them either.

The default fetch provider is plain HTTP, not DeepSeek's, so it bills nothing.

The retired flat card, kept to reprice history

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 hit · cache miss · output 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.

The retired V4 Flash card, kept to reprice history

Billed by deepseek-v4-flash and deepseek-v4-flash-vision-exp until 2026-09-10 04:00 UTC; both names bill as deepseek-flash since.

· cache hit · cache miss · output 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

Vision Exp billed at exactly these rates from its launch on 2026-08-21.

Source: , diffed against both locales daily.

And checked against the bill, twice a week. A published table is what a vendor says; a balance is what it does, and only one of those is what you pay. scripts/verify-bill.mjs spends a known number of tokens, waits for the charge to settle, and compares the balance delta against what this card predicts. Measured 2026-09-09, peak, on a live account:

model · tokens · card predicts · actually settled deepseek-v4-flash · 254,682 miss · ¥0.7641 · ¥0.77 deepseek-v4-pro · 127,491 miss · ¥1.1475 · ¥1.14

It has already caught one repricing, and nearly let it go. On 2026-09-11, off-peak, one round across all three models settled ¥0.58 against the card's ¥0.749 — the new V4.1 Flash price, to the cent (¥0.583), a day after DeepSeek cut it. The band was 25% then and the run printed ok: a one-model change spread over a multi-model probe arrives diluted. It is 10% now, and a test replays that run's exact token counts.

The two checks answer different questions, and the pair is the diagnosis:

pricing page · the bill · what it means ok · ok · the card is right differs · ok · the page moved, billing has not — a pre-announcement ok · differs · billing moved and the page did not differs · differs · a repricing; the card needs updating

Row three has no other detector. It also covers something that is not a price change at all: a request served by a model other than the one asked for, billed at that model's rate. The API's model response field is an echo of the request — measured across every published id — so the name can never be the evidence for that. The money can.

Three things the probe got wrong first, and how

Measuring a balance is harder than it sounds, and the first version of this script looked right until it ran.

Settlement is delayed, and it arrives in steps. Two hand probes settled as one lump about 145s after the requests, so v1 waited for two consecutive equal balance reads. Its first real run reported deepseek-v4-pro billing ¥1.42/1M against a card of ¥9 — confident, alarming and false. The charge had reached ¥0.02, then ¥0.04, and a plateau between two steps looks exactly like a finished settlement if a plateau is all you check for. A delta is settled only once it has not moved for longer than a whole settlement takes, and a zero delta is never settled at all — for the first two minutes a real charge reads as ¥0.00, which is indistinguishable from free.

A probe contaminates the next one. Settlement outlives a probe, so probe N's tail lands inside probe N+1's window and is billed to the wrong model. All models are now measured in one window against one summed prediction; --isolate re-probes a single one when something needs localising.

"A bill can only be contaminated upwards" was wrong. v1 argued that other traffic on the key can only *add*, so a bill that looked too small needed no confirming. Incomplete settlement also makes a bill look small, and did. Both directions are re-probed now.

Settlement has a tail, and the tail was a 3% "mystery". A full curve, sampled every 20 seconds: ¥0.27 at t+40s, ¥0.26 at t+100s — then single cents at t+222s and t+749s, 500 seconds apart and still arriving twelve minutes in. About two cents a round land after anyone has stopped watching, which is precisely the ~3% by which every earlier reading fell short of the eventual charge. So the probe does not wait for the true total, because waiting for it never terminates: a cent is noise, a step is a step, and the alarm sits far above both. Every failure worth catching is enormous next to a few cents — a rerouted model is 3x, a wrong tariff column 2x, a mispriced cache bucket 30x.

This verifies the CNY card only. The balance is CNY; the USD table is published separately and there is no USD balance to read.

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 weekly schedule, the retired flat card for repricing history, and the bucket definitions — generated from lib/core.js by 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-flash', 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 week as a grid; you index it with the current Beijing weekday and 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: