dsh-client-ui-usage-hud
A sidebar HUD for DeepSeek Harness (DSH) that shows your account balance, gifted balance, cumulative spend and token usage — always visible in the left sidebar foot.
Styled after the Abyssal Maid Atelier skin (@smalltailqwq/dsh-client-ui-skin-maid-atelier,
深海女仆工坊): navy, gold and porcelain, Georgia serif, lace-edged glass panel. It reads
that skin's own published CSS variables and falls back to the same values when the skin
is not active, so it looks right either way.
┌──────────────────────────────────────┐
footer trigger → 🐋 ¥16.8287 │ 深海账户 · Token 用量 刷新 │
14,096万 tok │ 账户余额 ¥16.8287 │
│ 赠送余额 ¥0.0000 │
│ Token 总用量 14,096万 17 会话 │
│ 最近一次用量 39.3万 tok T4 │
│ 未命中 173 · 命中 354,688 · 输出 285 │
│ 累计消费 ¥9.3329 │
│ 金额来自 DeepSeek 平台账户接口 │
│ 查看官方用量 → │
└──────────────────────────────────────┘
Collapsed, the trigger shrinks to a 34px round whale button.
What it shows, and where each number comes from
| Row | Value | Source |
|---|---|---|
| Account balance | normal_wallets per currency |
platform GET /api/v0/users/get_user_summary |
| Gifted balance | bonus_wallets per currency |
same |
| Cumulative spend | total_costs per currency |
same |
| Total tokens | sum of all four token buckets + session count | session projection cache, tokenUsage.totals |
| Last request | uncached input / cache read / output | same, tokenUsage.last |
Every money figure is read from the platform. Nothing is estimated, and no price table is applied.
Why this plugin has to talk to the platform itself
DSH's account service does request GET /api/v0/users/get_user_summary, but its parser
projects only two of the fields:
balance: {
path: '/api/v0/users/get_user_summary',
parse: (value) => {
const parsed = summary.safeParse(value);
return {
status: 'ready',
value: parsed.data.normal_wallets,
bonusWallets: parsed.data.bonus_wallets, // total_costs is dropped right here
};
},
},
The platform's own docs say the same thing: "Host projects only ... normal_wallets / bonus_wallets currency/balance strings; response tokens and other fields are discarded." That is why the shipped UI never shows cumulative spend — the data is there, DSH throws it away.
So this plugin's host half obtains a platform credential and reads the same endpoint for
itself, parsing total_costs. Real response:
{"code":0,"data":{"biz_code":0,"biz_data":{
"normal_wallets":[{"currency":"CNY","balance":"16.8287497600000000"}],
"bonus_wallets":[{"currency":"CNY","balance":"0"}],
"total_costs":[{"currency":"CNY","amount":"9.3328969200000000"}]}}}
Amounts are decimal strings and are kept verbatim until formatting, so precision is never lost to binary floating point.
The credential API that actually works here
This is the one sharp edge worth knowing if you write something similar.
deepseekAccount.resolveToken(url) looks like the supported way to get a request
credential, and DSH's docs describe it as "the only sanctioned way a host consumer obtains
a request credential". But its implementation is:
async resolveToken(url) {
const destination = new URL(url);
if (destination.origin !== this.inferenceOrigin || /* ... */) return void 0;
// ...
}
inferenceOrigin is https://api.deepseek.com — the inference origin. The account
endpoints live on https://platform.deepseek.com. Passing the platform origin therefore
returns undefined with no error, which reads exactly like "signed out".
The correct accessor for the platform origin is getPlatformSession(), which returns
{ origin, token, userId, requestHeaders } and "exports the stored grant only when its
issuer matches platformOrigin". It also hands back the provider-owned client headers the
platform expects. This plugin uses that.
Data flow
client.js ──GET /api/dsh/usage-hud──► lib/index.js
├─ deepseekAccount.getPlatformSession()
│ └─ GET platform.deepseek.com
│ /api/v0/users/get_user_summary
│ → balance / gifted / cumulative spend
└─ <dshHome>/storages/session_projcache/sessions/*.json
record.rows.tokenUsage.val
→ total tokens / last request
Token totals come from the persisted projection cache, not from live sessions, so cold
and archived sessions count too. The route also wraps the same response envelope the
account service does (code / data.biz_code / data.biz_data) and honours the same
401 and 40003 invalidation paths, calling rejectToken when the grant is dead.
Nothing secret is exposed to the browser: the token lives only inside the host half, and the route rejects cross-site callers.
Requirements
- DSH with the
deepseek-accountplatform plugin enabled and an official account signed in. In API-key mode there is no platform grant, and the money rows say so instead of inventing a zero. - No third-party runtime dependencies. No build step —
lib/*.jsis hand-written, directly executable JavaScript. - Works in light and dark themes.
Install
A DSH bundle is a package whose package.json declares dsh.bundle.patch; DSH composes
its patch layer into the profile. Install it into a profile directory.
Option A — plugin manager (uses pnpm and git)
dsh plugin --profile desktop add <this-repo-url>
DSH enables a newly installed bundle by default. Restart DSH once.
Option B — manual (no pnpm, no git required)
Copy this directory into the profile's
node_modules:<dshHome>/profiles/<profile>/node_modules/dsh-client-ui-usage-hud/Append the package name to the profile's
package.json:{ "dsh": { "profile": { "bundles": [ "@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "dsh-client-ui-usage-hud" ] } } }Restart DSH. The loader applies this bundle's
cordis.patch.yml, which inserts the rowui-usage-hud, and serveslib/client.jsto the browser.Refresh the page.
Making the host half hot-reload
By default DSH watch roots are empty, so editing lib/index.js needs a restart. To iterate
without one, add this plugin's directory to the hmr row in the profile's
cordis.patch.yml:
- id: hmr
name: "@deepseek-ai/dsh-hmr"
config:
root:
- "<dshHome>/profiles/<profile>/node_modules/dsh-client-ui-usage-hud"
lib/client.js is polled by dsh-client-hmr already: a page refresh picks up changes.
Layout
package.json # dsh.bundle.patch + dsh.client (platform: web)
cordis.patch.yml # insert: ui-usage-hud
lib/index.js # host half: the GET /api/dsh/usage-hud route
lib/client.js # browser half: the sidebar widget
tests/client-test.mjs # renders the widget with a minimal React hooks shim
tests/host-e2e.mjs # drives the real route handler and calls the platform
The browser half is a browser artifact in DSH's lazy-CJS shape — it registers itself with
window.__ModuleLoader__.load({ id, factory }) and exports apply and inject. It uses
only the shared platform runtime (require("react")), mutates nothing outside its own
[data-usage-hud] scope, and removes its stylesheet on disposal.
Verifying
There is no build step, so the tests run the shipped files directly:
# renders the trigger and popover, asserting all five rows and the formatting
# (no credentials needed: it feeds a fixed payload through a fetch stub)
node tests/client-test.mjs lib/client.js
# drives the real route handler with a stub account service and calls the platform
node tests/host-e2e.mjs ./lib/index.js
tests/client-test.mjs loads the bundle exactly the way DSH does, provides a minimal React
hooks shim, opens the popover and prints its rendered text. It needs no account.
tests/host-e2e.mjs stubs resolveToken (returning nothing, like the real one does for the
platform origin) and getPlatformSession, then reports the amounts it parsed back from the
platform. It needs a real platform grant, taken from the first of:
DSH_PLATFORM_TOKEN— the token itself;DSH_CREDENTIALS— path to a DSH.credentials.yaml;~/.dsh/.credentials.yaml— the default DSH location.
The token is read in-process, never printed, and never written anywhere.
# read the grant from your DSH profile
node tests/host-e2e.mjs ./lib/index.js
# or hand it over explicitly, touching no file
DSH_PLATFORM_TOKEN=... node tests/host-e2e.mjs ./lib/index.js
Notes and limitations
- Money rows reflect the official DeepSeek account only. On third-party provider routes they are unavailable, and the popover offers the platform usage link instead.
- The widget refreshes every 20 seconds and whenever the page becomes visible again, so the account endpoint is polled periodically.
total_costsis whatever the platform reports; this plugin does not scope it by period. Compare against the platform's usage page for a per-day breakdown.- Amounts are formatted with up to 4 decimals, or 2 above 1000, and sub-cent values show
as
<0.01.
License
MIT — see LICENSE.
Not affiliated with DeepSeek. "DeepSeek Harness" and the Abyssal Maid Atelier skin belong to their respective authors.
No comments yet. Be the first to write one.