dsh-consumption-badge
Verifieddsh-consumption-badge Β· v1.3.2 Β· MIT Β· Web UI
DSH Web UI plugin: a per-message consumption badge (tokens | estimated cost | peak/valley) plus a session detail modal with cumulative tokens, estimated cost, Beijing-time peak/off-peak pricing, a per-turn dual-axis chart, and the DeepSeek account balance
Install
dsh plugin add dsh-consumption-badge Confirm the layer applied with dsh --profile default --dump-config β see the install guide.
Source
Published to npm without a public repository. Inspect the package contents before installing.
Tags
Creators
Readme
dsh-consumption-badge
A persistent DSH (DeepSeek Harness) Web UI plugin that puts a live consumption badge next to every assistant message, and opens a detail panel with the session's spend curve. The UI is bilingual (follows the shell language, switches live), costs are estimated for 33 model entries across 10 providers, amounts can be shown in any of 17 major currencies, and one dropdown picks the model you want to look at. All of it lives in Settings β Usage.
β½ 3.2k | βΒ₯0.004 | Off-peak | Balance = Β₯42.50
- Collapsed pill β that assistant message's own token count, its own estimated cost
(always prefixed
β), its pricing tier (redPeak, greenOff-peak, attributed to the turn's request start), and the DeepSeek account balance; injected intoconversation.chat.assistant-actionsright after the shipped copy/branch controls. The two numbers are this turn's, not the session's: the slot renders once per turn (TurnTailNodeView) and hands the badge that turn's finalmessageId; the host answers with amessageId β messagemap (which carries the turn number) next to the per-turn fold, and the badge renders that turn's folded total. Mapping is resolved in two steps so the pill is right even before the host half is restarted: the host map first, then thedata-turn-tailattribute the shipped conversation view puts on the turn wrapper. If neither resolves (or the turn has no record yet) the pill falls back to the session-wide figures instead of showing a false zero, and its tooltip names the scope being shown. The balance segment rendersBalance = Β₯42.50when the account API answers,β¦while loading andβon failure (the reason rides the tooltip); balance uses the same 60 s cache as the modal, refreshed once a minute. The modal itself still shows the whole session, unchanged. - Detail modal (centered, blurred backdrop,
Escor backdrop-click to close): cumulative tokens with hit/miss/output split, estimated cost, current pricing tier, DeepSeek account balance, a model dropdown in the title bar, and a per-turn dual-axis line chart (left axis: tokens, right axis: money) where each model gets its own colored line and the legend names them; the hover tooltip shows the model (with its color dot), input-hit / input-miss / output tokens, the turn's cost and its peak/off-peak flag. Picking a model filters the cards + chart to that model and turns the balance hint into "about N tokens left at these prices". - Settings page Β· Usage (
settings.section, idbalance-dsh-consumption, order 26): a top row of four cards β Total tokens (with hit/miss/output split, all-history totals), Estimated cost, Current window (peak/off-peak) and Account balance (reusing the badge's cached balance) β then all-session history as one line chart, filterable by time range (Today / Last 7 days / Last 30 days / Last 12 months), by workspace (All workspaces + every registered workspace + Unassigned), and by an explicit calendar range that overrides the dropdown. Bucket size follows the range: hourly for today and any single-day calendar range, daily for multi-day ranges, and monthly for the last 12 months. Left axis (tokens) is hidden until you toggle it in the legend; the right axis (Β₯) is always on. Hovering a point shows its total, its peak/off-peak split and the models involved; the footer sums the current selection. No data in range β a plain "No usage recorded in this range" note instead of an empty chart. The cards are all-history totals (independent of the filters); the footer is the filtered total, so the two numbers differ by design. - Personalization integration (auto-detected, no configuration): when
dsh-plugin-personalizationis installed alongside this plugin, a Usage popup block is appended to its settings page β samepp-block/pp-field/pp-modeslayout and the samePillprimitive as the shipped personalization rows β offering four backgrounds for the consumption modal: Default / Glass / Custom image / Solid color.- Default keeps the themed surface; Glass is real
backdrop-filterglass β both the veil behind the modal and the panel itself are theme-derived (--dsw-alias-bg-base), so light themes get a light haze and dark themes a dark one instead of the panel painting a dark slab over the page; Custom image takes an image URL or a local image plus an Opacity slider (0β100 %, default 85 %) that sets the background layer's ownopacityβ the same semantic as the other personalization background rows, so lowering it fades the image over the themed panel instead of dimming it; Solid color takes a hex colour and derives a light/dark ink palette so the cards, chart and tooltips stay readable (the palette also overrides the tooltip surface). - A Glass switch appears for Custom image and Solid color, so glass composites with them: on an image it blurs the image itself (frosted photo), on a solid it makes the colour translucent over a blurred page. A Glass strength slider (0β48 px, default 22 px) sets the blur radius of the panel. The overlay behind the modal is deliberately never blurred.
- Clear wipes the image, the URL field and the file picker in one go. The image row shows a
thumbnail plus the live save state (Saving⦠/ Saved on this device /
error: β¦), and failures are logged with a[consumption-badge]tag instead of failing silently. The picker is a button (Choose imageβ¦ / Change imageβ¦), not a raw file input, so saving never leaves a misleading "no file chosen" label behind. - Local images are stored on this machine with no size limit: the picked file is written as a
Blob into IndexedDB (
dsh-consumption-assetsβimages), and the preference only keeps a small key β so a 20 MB wallpaper no longer blows thelocalStoragequota. On startup the blob is read back into ablob:URL for the modal; Clear deletes the stored record as well. If IndexedDB is unavailable the file is read as a data URL instead, and the row says so. - The choice is a local preference (
dsh-consumption:modal-style) and is applied to the open modal immediately. Nothing about the conversation or sidebar backgrounds is touched.
- Default keeps the themed surface; Glass is real
- Update notice (same shape as
dsh-plugin-personalization's): ~2.5 s after the page loads the plugin askshttps://registry.npmjs.org/dsh-consumption-badge/latestfor the newest version (24 h cache, 5 s timeout, fails silently). If the installed version is older β and that version was not ignored β a non-blocking card appears in the bottom-right corner with current β latest, the exactdsh plugin --profile web update dsh-consumption-badgecommand, and three actions: Copy update command, Ignore this version (remembered on this machine) and Remind me later (this page load only). It never blocks the conversation and never covers controls.
Everything is an estimate for reference only β this plugin is not a billing system of record.
Models and pricing
Costs are estimated from a flat price table of 31 model entries (current generations first,
legacy entries last so old logs still resolve). Unknown ids fall back to a family guess
(pro/opus/max β the stronger tier, everything else β DeepSeek V3) and are marked as estimates.
| Provider | Current | Legacy (kept for old logs) |
|---|---|---|
| DeepSeek | deepseek-flash = DeepSeek-V4.1-Flash-1017, deepseek-v4-pro = DeepSeek-V4-Pro-0813 (official ids/versions) |
DeepSeek V3.2 |
| OpenAI | GPT-5.6 Sol, GPT-5.6 Terra, GPT-5.6 Luna, o3 | GPT-4o, GPT-4o mini |
| Anthropic | Claude Opus 5.5, Sonnet 5.5, Haiku 5.5 | Claude Sonnet 4.6, Opus 4.x |
| Gemini 3.8 Flash, Gemini 3.1 Pro, Gemini 3 Flash | Gemini 2.5 Flash | |
| Alibaba | Qwen3.8 Max, Qwen3.8 Flash | Qwen3.7 Plus |
| Zhipu | GLM-5.3, GLM-5.1 | GLM-4.6 |
| Moonshot | Kimi K3.1 Pro, Kimi K3 | Kimi K2.5 |
| xAI | Grok 4.7 | Grok 4.3 |
| Meta | Llama 4 Maverick | β |
| Mistral | Mistral Large 4 | β |
Rules that follow from the table:
- Every turn is priced with the model it actually used β a session that mixed models is not priced with a single rate. Sessions, buckets and charts all keep the per-model split.
- Only DeepSeek has peak/off-peak pricing (Γ2 in the Beijing-time peak windows). Other vendors are flat; their card hint says so.
- Prices are CNY per 1M tokens. Non-DeepSeek entries are the vendor's USD list price Γ 7.1
(input-cache-read / input / output), taken from OpenRouter's public model catalogue; the two
harness-native DeepSeek rows keep DeepSeek's official CNY table. Edit
MODELSat the top oflib/client.jsβ each entry carriesprice {hit, miss, out},peakPricing,legacyand a chartcolor. - Model ids are resolved transparently.
deepseek-flashis the id DeepSeek's API currently routes to DeepSeek-V4.1-Flash-1017 anddeepseek-v4-proto DeepSeek-V4-Pro-0813 (perapi-docs.deepseek.com), and deprecated ids (deepseek-v4-flash*,deepseek-chat) route to the same families. Every model label in the UI can therefore be checked against the log: the settings cost card, the modal cost card, the legend tooltip and the per-turn tooltip all printFamily name (Official MODEL VERSION Β· log id <id>), so a mismatch is visible instead of hidden. - The Model dropdown (panel title bar and Settings β Usage, sharing one preference stored in
localStorage: dsh-consumption:model-filter) lists models grouped by provider in<optgroup>s; it filters the statistics and acts as the pricing fallback for turns whose log entry carries no model.All modelsturns both behaviours off. - Charts draw one line per model in that model's color (33 distinct colors), with a legend of model chips; the token axis stays a single dashed neutral line.
Currency
The price table is always CNY per 1M tokens (that is what DeepSeek bills in); the currency setting only decides how amounts are displayed. Pick it in Settings β Usage β Currency:
| Aspect | Behavior |
|---|---|
| Currencies | CNY (default) plus USD, EUR, GBP, JPY, HKD, TWD, KRW, SGD, AUD, CAD, CHF, INR, RUB, BRL, THB, MXN |
| What converts | every amount in the plugin: pill (`β# dsh-consumption-badge |
A persistent DSH (DeepSeek Harness) Web UI plugin that puts a live consumption badge next to every assistant message, and opens a detail panel with the session's spend curve. The UI is bilingual (follows the shell language, switches live) and amounts can be shown in any of 17 major currencies β pick one in Settings β Usage.
β½ 3.2k | βΒ₯0.004 | Off-peak | Balance = Β₯42.50
- Collapsed pill β that assistant message's own token count, its own estimated cost
(always prefixed
β), its pricing tier (redPeak, greenOff-peak, attributed to the turn's request start), and the DeepSeek account balance; injected intoconversation.chat.assistant-actionsright after the shipped copy/branch controls. The two numbers are this turn's, not the session's: the slot renders once per turn (TurnTailNodeView) and hands the badge that turn's finalmessageId; the host answers with amessageId β messagemap (which carries the turn number) next to the per-turn fold, and the badge renders that turn's folded total. Mapping is resolved in two steps so the pill is right even before the host half is restarted: the host map first, then thedata-turn-tailattribute the shipped conversation view puts on the turn wrapper. If neither resolves (or the turn has no record yet) the pill falls back to the session-wide figures instead of showing a false zero, and its tooltip names the scope being shown. The balance segment rendersBalance = Β₯42.50when the account API answers,β¦while loading andβon failure (the reason rides the tooltip); balance uses the same 60 s cache as the modal, refreshed once a minute. The modal itself still shows the whole session, unchanged. - Detail modal (centered, blurred backdrop,
Escor backdrop-click to close): cumulative tokens with hit/miss/output split, estimated cost, current pricing tier, DeepSeek account balance, and a per-turn dual-axis line chart (left axis: tokens, right axis: Β₯) with a hover tooltip showing the model, input-hit / input-miss / output tokens, and the turn's peak/off-peak flag. - Settings page Β· Usage (
settings.section, idbalance-dsh-consumption, order 26): a top row of four cards β Total tokens (with hit/miss/output split, all-history totals), Estimated cost, Current window (peak/off-peak) and Account balance (reusing the badge's cached balance) β then all-session history as one line chart, filterable by time range (Today / Last 7 days / Last 30 days / Last 12 months), by workspace (All workspaces + every registered workspace + Unassigned), and by an explicit calendar range that overrides the dropdown. Bucket size follows the range: hourly for today and any single-day calendar range, daily for multi-day ranges, and monthly for the last 12 months. Left axis (tokens) is hidden until you toggle it in the legend; the right axis (Β₯) is always on. Hovering a point shows its total, its peak/off-peak split and the models involved; the footer sums the current selection. No data in range β a plain "No usage recorded in this range" note instead of an empty chart. The cards are all-history totals (independent of the filters); the footer is the filtered total, so the two numbers differ by design. - Personalization integration (auto-detected, no configuration): when
dsh-plugin-personalizationis installed alongside this plugin, a Usage popup block is appended to its settings page β samepp-block/pp-field/pp-modeslayout and the samePillprimitive as the shipped personalization rows β offering four backgrounds for the consumption modal: Default / Glass / Custom image / Solid color.- Default keeps the themed surface; Glass is real
backdrop-filterglass β both the veil behind the modal and the panel itself are theme-derived (--dsw-alias-bg-base), so light themes get a light haze and dark themes a dark one instead of the panel painting a dark slab over the page; Custom image takes an image URL or a local image plus an Opacity slider (0β100 %, default 85 %) that sets the background layer's ownopacityβ the same semantic as the other personalization background rows, so lowering it fades the image over the themed panel instead of dimming it; Solid color takes a hex colour and derives a light/dark ink palette so the cards, chart and tooltips stay readable (the palette also overrides the tooltip surface). - A Glass switch appears for Custom image and Solid color, so glass composites with them: on an image it blurs the image itself (frosted photo), on a solid it makes the colour translucent over a blurred page. A Glass strength slider (0β48 px, default 22 px) sets the blur radius of the panel. The overlay behind the modal is deliberately never blurred.
- Clear wipes the image, the URL field and the file picker in one go. The image row shows a
thumbnail plus the live save state (Saving⦠/ Saved on this device /
error: β¦), and failures are logged with a[consumption-badge]tag instead of failing silently. The picker is a button (Choose imageβ¦ / Change imageβ¦), not a raw file input, so saving never leaves a misleading "no file chosen" label behind. - Local images are stored on this machine with no size limit: the picked file is written as a
Blob into IndexedDB (
dsh-consumption-assetsβimages), and the preference only keeps a small key β so a 20 MB wallpaper no longer blows thelocalStoragequota. On startup the blob is read back into ablob:URL for the modal; Clear deletes the stored record as well. If IndexedDB is unavailable the file is read as a data URL instead, and the row says so. - The choice is a local preference (
dsh-consumption:modal-style) and is applied to the open modal immediately. Nothing about the conversation or sidebar backgrounds is touched.
- Default keeps the themed surface; Glass is real
- Update notice (same shape as
dsh-plugin-personalization's): ~2.5 s after the page loads the plugin askshttps://registry.npmjs.org/dsh-consumption-badge/latestfor the newest version (24 h cache, 5 s timeout, fails silently). If the installed version is older β and that version was not ignored β a non-blocking card appears in the bottom-right corner with current β latest, the exactdsh plugin --profile web update dsh-consumption-badgecommand, and three actions: Copy update command, Ignore this version (remembered on this machine) and Remind me later (this page load only). It never blocks the conversation and never covers controls.
Everything is an estimate for reference only β this plugin is not a billing system of record.
), modal cards and both charts' right axis, the settings cards and footer totals, and the account balance (when the balance API reports CNY) |
| Rates | 1 CNY = n target currency, cached 6 h. Primary source open.er-api.com, fallback @fawazahmed0/currency-api via jsDelivr β fetched through the host route /api/consumption/rates, and directly from the browser when that route is unavailable (both endpoints send access-control-allow-origin: *) |
| Offline | if no live rate can be fetched, a built-in fallback table is used and the settings hint says so |
| Preference | localStorage key dsh-consumption:currency; the settings hint shows the active rate, its source and a timestamp |
| Unchanged | the pricing table, peak/off-peak rules and every stored record stay in CNY β switching currency never rewrites data |
costCNY = (hit/1e6)*hit + (miss/1e6)*miss + (out/1e6)*out [Γ2 at peak]
shown = costCNY Γ rate[currency]
Localization
The plugin ships Chinese and English copy and follows the DSH shell language live β no reload, no second settings page.
| Aspect | How it works |
|---|---|
| Source of truth | The client-side locale service (@deepseek-ai/dsh-client-locale, ctx.get('locale')) |
| Dictionary | DICT_EN in lib/client.js, keyed by the Chinese copy itself; under zh no dictionary is needed because the key is the text, and any key missing from DICT_EN falls back to that Chinese text (never a blank string) |
| Live switching | Slot outlets re-render on a locale switch (the shell subscribes to the locale revision), so tt() picks up the new language during the next render; the personalization page block renders in its own React root and subscribes to the locale face itself |
| Slot labels | The action-row label and the settings tab title are thunks, resolved by the slot system at read time, so the tab renames itself on a switch |
| Late service | If the locale service is provided after this plugin's apply() ran (boot order differs per composition), the first render retries the bind |
| Missing service | Without the locale service everything falls back to Chinese β the plugin still works standalone |
| Host errors | lib/index.js never returns localized prose: failures carry a language-neutral code + detail (and an English error for logs). The client maps code β copy in the active language, and prints the host's error only for an unknown code |
Adding a language means adding one dictionary next to DICT_EN; the lookup order is
active locale β key, so untranslated keys degrade to Chinese rather than breaking.
Requirements
| Capability | Why |
|---|---|
DSH 0.1.0-rc.7 or compatible |
slot API conversation.chat.assistant-actions, shell.overlay |
DEEPSEEK_API_KEY in the DSH credential store |
account balance (GET /user/balance) |
curl on PATH |
the balance request; on Windows 10+ it ships as C:\Windows\System32\curl.exe |
The API key is resolved per request through the Cordis credentials service and passed to
curl on stdin (curl -K -), never through argv, and never reaches the browser.
If the key or curl is missing, only the balance card degrades (it shows the reason);
tokens, cost and the chart keep working.
Install
dsh plugin --profile web add dsh-consumption-badge
Then restart the DSH process β a host-side plugin (and the browser plugin roster) is composed at process start, so a page refresh is not enough.
Verify:
cd "$DSH_HOME/profiles/web"
dsh --profile web --dump-config | grep consumption-badge # expect exactly one row
# after restart:
curl -s http://127.0.0.1:<port>/api/consumption/account # expect {"ok":true,...,"model":"..."}
curl -s -o /dev/null -w '%{http_code}\n' \
http://127.0.0.1:<port>/plugins/dsh-consumption-badge/client.js # expect 200
If the row does not mount
Some deployments compose dsh.profile.bundles into --dump-config without mounting those rows
at runtime. The reliable path is a roster row in the profile's own patch layer β add it to
$DSH_HOME/profiles/web/cordis.patch.yml and remove dsh-consumption-badge from
dsh.profile.bundles so the id is inserted exactly once:
- insert:
- id: consumption-badge
name: 'dsh-consumption-badge'
The package also ships its own cordis.patch.yml (dsh.bundle.patch) with the same row for
deployments where the bundle layer does mount. Keep only one of the two active.
What it reads
| Data | Source |
|---|---|
| Per-turn tokens | assistant/message.usage events in the session log, folded per turn (cacheReadTokens β hit, inputTokens + cacheWriteTokens β miss, outputTokens β output; multi-step turns are summed) |
| Turn timing | turn/start event time (peak/off-peak is attributed to request start) |
| Model per turn | request/context / request/header.config; falls back to the agent default model, then deepseek-flash |
| Session totals | the shipped tokenUsage projection when present, otherwise the folded turns |
| Balance | GET https://api.deepseek.com/user/balance via curl, 60s cache |
Turn records are also mirrored into browser localStorage under
dsh-consumption:<sessionId>:<turn> so the chart survives reloads. Persistence writes are
scheduled off the render path (requestIdleCallback), and the aggregation is O(turns).
The settings page reads those same records (no second store, no new field): workspace
attribution is resolved at read time from DSH's own accounting ledger
(WorkspaceView.sessionIds from useWorkspaces), so historical records are attributed without
any backfill. Records whose session no longer exists land in the Unassigned bucket.
Above 5000 records the aggregation runs in a Web Worker (built from the pure aggregator's own
source, so both paths are the same code); if the browser refuses the Worker it silently falls
back to the main thread, where 6000 records aggregate in ~6 ms.
Pricing model
Off-peak prices in CNY per 1M tokens; peak multiplies everything by 2.
| Model | input cache hit | input cache miss | output |
|---|---|---|---|
deepseek-flash |
0.02 | 1 | 4 |
deepseek-v4-pro |
0.15 | 4.5 | 13.5 |
cost = (hit/1e6)*hit_price + (miss/1e6)*miss_price + (out/1e6)*out_price [Γ2 at peak]
Peak = Beijing time (UTC+8), MondayβFriday, 09:00β12:00 and 14:00β18:00, excluding statutory holidays. Everything else, plus weekends, is off-peak.
Unrecognised model ids are priced as deepseek-flash (or deepseek-v4-pro when the id contains
pro) and the UI marks the figure as an estimate. The table lives in PRICES at the top of
lib/client.js β fork and edit to follow a price change.
The fixed-date holiday list (CN_FIXED_HOLIDAYS, MM-DD) covers New Year, Labour Day and the
first three days of National Day. Lunar holidays (Spring Festival, Qingming, Dragon Boat,
Mid-Autumn) must be added by hand.
HTTP surface
The host half registers three exact, read-only routes on the DSH web server:
| Route | Returns |
|---|---|
GET /api/consumption/turns?sessionId=<id> |
per-turn {turn, hit, miss, out, time, model} plus per-message {id, turn, step, hit, miss, out, time, model} (the id is the finalized message id the badge receives as messageId); log-length cached |
GET /api/consumption/account |
current provider/model + whether the key is configured |
GET /api/consumption/rates[?force=1] |
{ok, base:"CNY", rates:{USD:β¦}, source, fetchedAt, stale} β 16 curated currencies, 6 h cache; stale:true means the built-in fallback table |
GET /api/consumption/balance[?force=1] |
{ok, available, currency, total, granted, toppedUp}; is_available:false renders a red "insufficient balance" |
Failures are {ok:false, code, detail, error}: code is language-neutral
(no-credentials, no-key, key-read-failed, no-subprocess, curl-empty, spawn-failed,
bad-response, balance-request-failed, missing-session-id, log-unavailable), detail is raw
text, error is an English fallback for logs.
These routes are unauthenticated. If your DSH web server binds 0.0.0.0, anyone on the
network can read the balance and token figures; keep it on 127.0.0.1 unless you need otherwise.
Prompt for your AI agent (copy-paste)
Paste this into Claude Code / Codex / DSH and let it do the install and verification:
Install the DSH plugin "dsh-consumption-badge" into my web profile and prove it works.
1. In $DSH_HOME/profiles/web run: `dsh plugin --profile web add dsh-consumption-badge`.
(For a local checkout use `dsh plugin --profile web add "file:<abs path to the package>"`.)
2. Check `dsh --profile web --dump-config` contains exactly ONE `id: consumption-badge` row.
`dsh plugin add` also appends the package to `dsh.profile.bundles`; if that row does not
actually mount after the restart, move it: remove it from `dsh.profile.bundles` and add
`- insert: [{id: consumption-badge, name: 'dsh-consumption-badge'}]` to the profile's
`cordis.patch.yml` instead. Never leave both.
3. Restart the DSH process (a browser refresh is NOT enough β host plugins are composed at boot).
4. Verify the host half: `curl -s http://127.0.0.1:<port>/api/consumption/account` must return
`{"ok":true,...}`. Verify the client half:
`curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:<port>/plugins/dsh-consumption-badge/client.js`
must return `200`.
5. Open any session and confirm the `β½ <tokens> | βΒ₯<cost> | Peak|Off-peak | Balance = Β₯<total>` pill in the assistant
action row; click it and confirm the modal shows the four cards, the dual-axis chart, the
balance, and that Esc closes it.
6. Keep Settings β Usage open, then switch the shell language between Chinese and English
(Settings β General β Language) and confirm every visible string in the badge, the modal, the
settings page and the personalization block switches without a reload.
7. On the Usage page switch the time range (Today / Last 7 days / Last 30 days / Last 12 months),
switch the workspace (All workspaces β a specific workspace β Unassigned), set the calendar range
and confirm both the chart and the footer totals update, and that the Token axis only appears
after clicking its legend chip.
8. If `dsh-plugin-personalization` is installed, open Settings β Personalization and confirm a
Usage popup block appears at the bottom; switch it to Glass / Solid color / Custom image and
confirm the β½ modal (click a badge) re-renders with that background.
9. Report which step failed and paste the boot log lines that mention `consumption-badge`.
Rules while doing this: never write to the DSH credentials file yourself (ask me for
DEEPSEEK_API_KEY), never bind a second server to the same port, and do not try to persist the
plugin by "running" it β a session-scoped dynamic plugin always dies with the process.
Notes for AI agents
- This is a profile-level plugin:
package.jsoncarriesdsh.bundle.patchanddsh.client(platform: web). It cannot be shipped as a dynamic/session plugin βharness.handleandhost.callare dynamic-runner-only and do not exist for a bundled plugin; the client half talks to the host half over the same-origin routes listed above. - The client bundle is hand-written in
window.__ModuleLoader__.load({id, factory})form and onlyrequire("react")β React must stay external (a second React instance breaks slot rendering). lib/index.jsimports nothing at all (no@deepseek-ai/cordis), so it does not depend on the profile being able to resolve Cordis; it exports plainname/inject/apply.- Do not hard-code user-visible English or Chinese in new JSX: call
tt('β¦')with the Chinese copy as the key and add the English entry toDICT_EN.check.mjsfails if anytt()key has no English translation or if a translation still contains Chinese. - Self-check without booting DSH:
node check.mjs(320 assertions over the route contract, the client bundle contract, the pricing/aggregation maths and the bilingual copy). It lives outside the publishedfileslist.
Known limitations
- All amounts are estimates β price table, peak/off-peak rules and model detection can drift from your actual invoice.
- One chart point per turn; multiple steps in a turn are summed.
- Balance parsing depends on DeepSeek's
/user/balanceresponse shape; failures only affect the balance card. - The chart covers turns present in the session log fold (whole log), while the pill's total uses
the shipped
tokenUsageprojection β both are whole-session, but a heavily-compacted session may differ by the tokens lost to compaction. - Only
zhandenare shipped; any other shell locale falls back tozh(the dictionary keys).
License
MIT