dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
余额与费用查询插件,支持多账号查询,实时token实时统计,中英双语,界面交互直观、开箱即用
A balance & cost plugin for DeepSeek Harness:
Simplified Chinese · Screenshots
llm-pi-ai settings whose baseURL points at DeepSeek,llm-deepseek route,deepseek derives the credential ref
DEEPSEEK_API_KEY, colliding with the official llm-deepseek default),
they are folded into a single row — the row keeps its first entry and shows
a chip for every route sharing the key (tooltip: "shares API key with …").
The balance query runs once per distinct key, so the same account is never
displayed twice with different names.apiKeyEnv via the host
credentials service and calls the official
GET https://api.deepseek.com/user/balance (proxied by the host — keys never
reach the browser). Each row shows total_balance / granted_balance
(+ topped-out flag) or the failure reason. Results are cached in memory for 60s;
"Refresh" bypasses the cache.$DSH_HOME/dsh-get-balance.json.api.deepseek.com) — non-official keys
show a "not billed" chip instead of an amount. Multiple official keys (several
routes pointing at the official API) each get their own group and their own bill.request/context provider, and each
group carries a source chip (pi-ai route / official route) plus the
official/non-official chip (alias routes are judged by baseURL hostname, so a
pi-ai route pointing at api.deepseek.com bills as official), so providers
never mix. The Last question entry prices every sample with its own model
(per-sample model from request/context), not a session-wide model.provider field of request/context events → that
provider's baseURL in host settings → hostname equals api.deepseek.com
(trailing slash / case normalized; lookalike domains such as
api.deepseek.com.xx.com are not official).tokenUsage projection). Today
entries: on-demand scan of dshHomePath('sessions') logs (.jsonl and
.jsonl.zstd, frame-wise zstd decode, per-file memoized).Model version (colspan=2) + one column per model; three metric groups (input
cache-hit / cache-miss / output, each rowspan=2) + off-peak/peak rows;
only the price cells are input boxes (peak red, off-peak green).deepseek-v4-flash /
deepseek-v4-pro / deepseek-v4-flash-vision-exp). Old flat-format
config migrates automatically on first read (legacy built-in defaults upgrade to
the official three tiers).conversation.session.header.utilities, showing
Session xxM | ≈¥xx (both green, digits roll vertically odometer-style) —
the current session's total tokens (compact K/M/B/T/P format) and its
estimated cost.assistant message node — request-level,
not per streamed token), so a turn with several requests updates after each one.
Only completions that hit the official DeepSeek API (api.deepseek.com) also
force-refresh the footer balance (bypassing its 60s cache) — requests to
non-official endpoints update tokens & cost only, without a balance query.ds-self 268K | ≈¥0.41, non-official rows show "not billed").sidebar.footer.action Balance button: the label and a period dot on the
left, the amounts right-aligned ("Balance ¥110.00 | ¥99.50") — currency symbol
prefix, digits in green and rolling vertically odometer-style on change.
One segment per provider (account), separated by |; an account whose
balance could not be fetched (no API key configured / query failed) shows a
red -- placeholder (hover shows the reason). The period is a
small dot (red for peak hours / green for
off-peak, half price) whose hover tooltip shows the full info
"Currently peak hours · full price billing" / "Currently off-peak hours · half
price billing" (full price in red / half price in green); the amounts come from
the balance API.localStorage key
dsh-get-balance.show-in-menu): when off, the entry button renders nothing at
all (no placeholder, and no balance polling behind it). The same switch sits at
the top of the modal's Prices tab and at the top of the host
Settings → Balance section page (settings.section, order 42,
which stays reachable and is then the only way back in; both switches share one
store, so flipping either updates the other immediately.$DSH_HOME/dsh-get-balance.json and survives restarts.Config (no required options) + a plugin-owned config file
$DSH_HOME/dsh-get-balance.json: extra keys, price config and the auto-refresh
interval are persisted there (JSON, versioned), never into the host's
settings.yaml. Existing data stored in the old dsh-balance settings
namespace is migrated automatically on first run.dsh.bundle + dsh.client(web) manifests; the official
deepseek-harness project is never modified — everything rides existing slots
and the HTTP / command channel.Location: $DSH_HOME/dsh-get-balance.json (next to settings.yaml).
Contents (all fields optional on read; invalid values fall back to defaults):
{
"version": 1,
"extraKeys": [ { "id": "k1", "label": "main", "apiKey": "sk-..." } ],
"prices": {
"tiers": [
{ "id": "deepseek-v4-flash", "name": "deepseek-v4-flash", "currency": "CNY",
"match": "deepseek-v4-flash",
"peak": { "input": 2.0, "cacheRead": 0.04, "cacheWrite": 0, "output": 8.0 },
"offPeak": { "input": 1.0, "cacheRead": 0.02, "cacheWrite": 0, "output": 4.0 } }
],
"timezoneOffsetMinutes": 480,
"peakWindows": [ { "start": "09:00", "end": "12:00" }, { "start": "14:00", "end": "18:00" } ],
"weekendOffPeak": false
},
"autoRefreshSeconds": 0
}
It is read on every query and written atomically (temp file + rename) on save,
so hand-editing takes effect immediately (no restart). A corrupted file is
renamed to dsh-get-balance.json.bak-<timestamp> and defaults are used.
├── src/host/*.ts # host half: index.ts, providers.ts (enum + official check),
│ # balance.ts, cost.ts (fold + today scan + period pricing +
│ # official filter), ops.ts, config-file.ts (plugin config
│ # file read/write + legacy settings migration),
│ # fence.ts, types.ts
├── src/client/* # browser half: plugin.tsx (slots + timer), BalanceModal.tsx,
│ # HeaderButton.tsx, FooterButton.tsx, rpc.ts, store.ts,
│ # i18n.ts, styles.ts, logo.ts
├── lib/index.js # host bundle (tsdown ESM), committed for git installs
├── lib/client.js # browser bundle (__ModuleLoader__ factory), committed
├── lib/types/ # declarations (tsc -b)
├── scripts/ # verify-client.mjs
├── tsdown.config.ts # tsdown config (host + client banner wrap)
├── tsconfig.json # solution: tsconfig.host.json / tsconfig.client.json
├── cordis.patch.yml # bundle patch
├── package.json # dsh.bundle + dsh.client(web) manifests + peerDependencies
├── README.md # this file (English, default)
└── README.zh-CN.md # Simplified Chinese docs
# published: npm / tarball / GitHub
dsh plugin --profile web add dsh-get-balance
dsh plugin --profile web add ./dsh-get-balance-0.1.0.tgz
dsh plugin --profile web add github:you/dsh-get-balance#<sha>
dsh --profile web --dump-config # inspect the plugin layer
dsh --profile web # start
No static config required: extra keys, price config and the auto-refresh interval
are edited in the modal and persisted to $DSH_HOME/dsh-get-balance.json.
Toolchain: tsc + tsdown (no vite): tsc -b type-checks and emits
declarations; tsdown (Rolldown core) bundles the host half
(lib/index.js, ESM) and the browser half (lib/client.js, single-file CJS
__ModuleLoader__ factory). Dependency manager: pnpm 10.
pnpm install # per pnpm-lock.yaml
pnpm run build # clean lib → tsc -b (types + declarations) → tsdown (both halves)
pnpm run verify # simulate the host seed to validate lib/client.js (optional)
pnpm publish # or pnpm pack / git push origin main (lib/ committed → git installs need no build)
Pushing a v* tag (pnpm run release bumps the patch version, rebuilds and
tags) triggers .github/workflows/publish.yml:
pnpm install --frozen-lockfile →
pnpm run check → pnpm run build → pnpm pack → create GitHub Release;NPM_TOKEN repo secret.Requirements: Node ≥ 26 + pnpm 10 (pinned via the packageManager field).
pnpm install # devDependencies: typescript, tsdown, @types/react, @deepseek-ai/* type packages…
pnpm run check # full-tree TypeScript check (tsc -b)
pnpm run build # rebuild both bundles after source changes (tsc -b && tsdown)
pnpm run verify # validate lib/client.js against a simulated host seed
Attach the local checkout to a dsh instance (from the plugin repo):
cd dsh-get-balance
dsh plugin --profile web add ./
The host loads
index.jsas native Node ESM, so@deepseek-ai/schemastery,@deepseek-ai/dsh-tools,@deepseek-ai/dsh-settings,@deepseek-ai/dsh-home-pathsmust resolve from the plugin directory (runpnpm installthere;node_modulesis gitignored). Host-half (src/host/) changes need a dsh restart; browser-half (src/client/) changes apply on a page refresh.
src/host/; browser half in src/client/;window.__ModuleLoader__.load factory wrap of lib/client.js is generated
by tsdown's banner/intro/footer; external deps (react etc.) resolve through the
host module table (seed) at runtime./dsh-balance/api (POST JSON, host
webServer + trust fence) with a ctx.remote.commands.execute fallback;
errors carry a code that the client localizes.credentials service is looked up lazily per request
(not captured at apply time), avoiding a "no credential" state when the host
service starts late; the providers op returns credentialsPresent and per-entry
keySource diagnostics.(uncachedInput × p_input + cacheRead × p_cacheRead + cacheWrite × p_cacheWrite + output × p_output) / 1e6 per million tokens, priced
by each event's time (peak/off-peak).request/context provider → baseURL in host settings →
hostname == api.deepseek.com; non-official tokens are counted only
(per-provider four buckets), never billed.dshHomePath('sessions')/<projectKey>/<sessionId>/session.jsonl(.zstd),
decoded frame-wise via zstdDecompressSync from node:zlib.@deepseek-ai/cordis, dsh-tools, schemastery, dsh-settings,
dsh-commands, dsh-session, dsh-api-remotes, client runtime / ui-slots /
ui-settings / cordis-client-runner, react) are resolved by the host at install.deepseek-harness project is never modified; everything uses
existing slots (sidebar.footer.action, settings.section, shell.overlay,
conversation.session.header.utilities) and the HTTP / command channel..dshb-*
with one deliberate exception — :where(div:has(> [data-slot="sidebar.footer.action"] > .dshb-footer-group)){flex-direction:column},
which stacks the host footer container (the host lays it out as a flex row, so
several plugin entries would squeeze onto one line). It can only match a container
that already holds this plugin's own entry, and :where() drops its specificity
to 0 so the host can always override it. Keyframe names are dshb--prefixed and the
style tag is marked data-plugin-css="dsh-get-balance/settings.css"; no other global
selector, no :root/body/* rule, no body-style mutation.