lijunyu726/dsh-header-balance

dsh-header-balance

DSH Web GUI 会话页头部余额芯片:经宿主侧 Remote 用你的 API Key 查询 DeepSeek 账户余额,点击跳转充值,悬停可见赠送/充值拆分。API Key 不进浏览器。

Uncategorizedaccountbalancecredit

dsh-header-balance

English | 中文

A DeepSeek balance chip for the DSH Web GUI: shows your account balance in the conversation header, opens the recharge page on click, and reveals the granted / topped-up split on hover.

The API key and the network request live entirely on the host side — the key never reaches browser code.

Balance chip demo: the balance in the conversation header, clicking through to the recharge page


Features

CapabilityNotes
Balance displayA chip in the conversation header showing the total, matching the visual density of the built-in "Session log" button
Click to rechargeClicking the chip opens platform.deepseek.com/top_up; the Electron shell routes it to your system browser
Manual refreshThe ↻ button inside the chip (it stops propagation, so it cannot trigger the recharge jump)
Auto refreshOn mount, every 60 seconds, and whenever the window regains focus
Hover detailAccount balance plus the granted / topped-up split (that line is omitted when the grant is 0) and the recharge hint
Narrow screensPhones drop the word "余额" and keep only the amount
Unavailable warningWhen is_available is false it says so, instead of quietly showing ¥0.00
Readable failuresDistinct messages for a missing key, an invalid key, insufficient balance, rate limiting, timeout, no network, and a malformed response

Install

dsh plugin --profile web add dsh-header-balance

Restart DSH and the chip appears in the conversation header.

dsh plugin add reads the dsh.bundle.patch field in this package's package.json and inserts the plugin into the profile tree for you. No manual edit of cordis.patch.yml is needed. The entry it writes is shown below, for troubleshooting only:

- insert:
    - id: dsh-header-balance
      name: 'dsh-header-balance'

Installing through npm directly also works (for a hand-rolled profile, or offline distribution), but then you do add that entry yourself:

npm install dsh-header-balance

The plugin resolves the same credential reference as the official llm-deepseek adapter (default DEEPSEEK_API_KEY), so if your model works, the balance lookup works — no separate configuration.

Architecture (and why there is host-side code)

Balance is not a pure front-end widget; it needs the host:

Browser (lib/client.js)                  Host (lib/index.js)
  connection.rpc.call('/api',             reads settings['llm-deepseek']
    'accountBalance/balance', {} )   →    resolves the API key via credentials
        ↓                                  GET api.deepseek.com/user/balance
  receives normalized numbers only  ←──   parses the wire's snake_case strings

Three hard constraints, each documented with citations in ARCHITECTURE.md:

  1. The client must not use ctx.remote.accountBalance. The remote.<ns> namespace services are mounted only for the 15 official strict contributions; declaring one would park the plugin forever and take host startup down with it. Use the raw carrier connection.rpc.call.
  2. The host's inject must stay empty. It is a startup gate: a declared service that never arrives keeps the plugin pending and the whole app fails to boot. settings / credentials are read softly via ctx.get() with fallbacks.
  3. The Remote method signature is load-bearing. Gateway SRC discovery parses parameter names out of Function.prototype.toString(), so no destructuring, defaults, rest parameters, or duplicate names.

Wire endpoint: accountBalance/balance, payload { args: {} }.

Development

node scripts/link-host-deps.mjs   # symlink host-side peer deps into this repo (dev only)
npm run build:bundle              # regenerate the bundle's inlined logic from lib/balance.js
npm run check                     # syntax preflight + verify the inlined region matches the source
npm test                          # 36 tests
npm run verify                    # all of the above

The display logic inside lib/client.js is not hand-written: scripts/inline-balance.mjs inlines it from lib/balance.js at build time. DSH's bundle resolver only knows platform seed words and registered package ids, so requiring the package's own subpath always fails — and the logic cannot be hand-written into the bundle either, since then it could not be unit-tested in Node.

Three test layers

  • test/balance.test.js (12) — display formatting and copy. Pure functions, no dependencies.
  • test/host-service.test.js (19) — the host half. It really imports @deepseek-ai/dsh-typert-protocol and asserts that remoteMethods() reads the Remote marker back, so the SRC discovery path is genuinely exercised rather than stubbed. Covers the happy path, seven failure classes, credential precedence and fallback, and "the API key never appears in the return value".
  • test/client-contract.test.js (5) — the bundle contract. Actually executes lib/client.js in node:vm, with a require that only admits platform seed words — stricter than the real host.

The host-side tests need scripts/link-host-deps.mjs; without an installed DSH they skip automatically and say so in the report, leaving the pure-logic tests unaffected.

Known limits

  • Verified only against the DSH 0.1.5 slot contract (conversation.session.header.utilities, kind: list).
  • The granted / topped-up split lives in the hover tooltip only; the chip itself shows the total, deliberately, to keep the header control narrow.
  • No balance history, no usage statistics, no low-balance threshold alerts.
  • Amounts are never used in arithmetic — formatting only. Settlement happens server-side.
  • Depends on the host-side settings and credentials services; if either is absent it falls back to process environment variables, and reports "not configured" when neither yields a key.

License

MIT