DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

KamChiHei /

KamChiHei/dsh-deepseek-usage-monitor

Verified

DeepSeek Harness (dsh) plugin: token usage accounting and account balance with a live status card in DSH Web

★ 1 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHubProject homepage
READMESource: main@ef267cbd

dsh-deepseek-usage-monitor

English | 简体中文

License: MIT test npm version npm downloads GitHub stars

A DeepSeek Harness (dsh) plugin: records token usage for every model call on the Host side, polls your DeepSeek account balance on a timer, and shows a draggable, resizable live status card in the bottom-right corner of DSH Web.

The plugin has two halves reading the same data:

  • Host side (index.js): listens to harness events for accounting, queries the balance on a timer, and serves the state endpoint;
  • Web side (client.js, loaded via the dsh.client declaration in package.json): polls the state endpoint and renders the bottom-right "Usage" card. The API key never leaves the Host process and is never sent to the browser.

Showcase

The "Usage" card in the bottom-right corner of DSH Web after installation (shown expanded, with total tokens, cache hit rate, balance, and model / provider groups):

Expanded "Usage" status card in the bottom-right corner of DSH Web

Features

Token accounting

  • Listens to session/event: assistant/message TokenUsage is the source of truth; usage recorded by assistant/chunk (chunk.type === "usage") is the fallback for failed requests, deduplicated by the session:turn:step key so a step is never counted twice; step/end and session/disposed flush chunk usage that never received a message confirmation.
  • Accepts both usage shapes: harness inputTokens / outputTokens / cacheReadTokens / cacheWriteTokens, and raw DeepSeek response fields prompt_tokens / prompt_cache_hit_tokens / prompt_cache_miss_tokens / completion_tokens ... (converted automatically; miss = prompt − hit when omitted).
  • totalTokens = input + output + cache read + cache write; reasoning tokens are already included in output — tracked separately but never double-counted.
  • Besides the grand totals, usage is grouped by model and provider; per-session details keep the most recent sessionLimit entries (sessionCount is the number of retained sessions). Routing info comes from request/header / request/context events; missing names fall into the unknown group.
  • Stats are persisted to a local JSON file (default ~/.deepseek-harness/deepseek-usage.json) and keep accumulating across restarts. Only numbers, group names, and timestamps are stored — never API keys, prompts, or model replies. Delete the file and restart DSH to reset the stats.

Balance queries

  • Polls the official DeepSeek GET /user/balance endpoint on a timer (default 60s), recording is_available and balance_infos amounts; timeouts (default 10s) and failures are recorded with the reason.
  • The API key is resolved per refresh and automatically reuses the DeepSeek key already configured in dsh (resolution order under "API key"); a key added after startup takes effect on the next balance refresh — no restart needed.
  • Background refreshes silently skip when no key resolves (the card shows "Not queried"); only a manual refresh marks "Query failed" — hover the balance row to see the reason (including diagnostics for a missing key). Token accounting never depends on the key and always works.

State endpoint

GET /plugins/deepseek-usage-monitor/state: the endpoint used by the web card; add ?refresh=1 to force a balance refresh; HEAD is supported. See "State endpoint response" below for the shape.

DSH Web status card

After installation a "Usage" card appears in the bottom-right corner of DSH Web and polls state every 5 seconds (polling pauses while the tab is hidden and refreshes once immediately when it becomes visible again):

  • Expanded view: total tokens, request count, cache hit rate, input (cache-miss), output tokens, DeepSeek API balance, model / provider group lists, and last-updated time; click "Refresh" to force a balance refresh immediately (equivalent to ?refresh=1).
  • Cache hit rate = cache read / (cache read + cache-miss input).
  • Model / provider groups are sorted by total tokens descending; only the top 4 entries are shown by default — click "Show all N" to expand and "Collapse" to fold; "No data" when empty.
  • Balance row states: Reading… / amounts (multiple currencies joined with ·) / No balance / Unavailable / Not queried / Query failed (hover for the reason).
  • Collapses to a title bar by default; click "+" to expand and "−" to collapse — the state is remembered.
  • Drag the title bar to move, drag the bottom-right handle to resize (minimum 232×96), double-click the title bar to reset to the default bottom-right anchor; position, size, and collapsed state persist in browser localStorage (key dsh-deepseek-usage-monitor:placement) across page reloads.
  • Collapsing hides the resize handle and returns the bar to its docking spot; the card pulls itself back into the viewport when expanded near an edge or when the window shrinks.
  • The status dot turns red when the state endpoint fails; the error message shows at the bottom of the card.
  • Styling is built on the official DSH design tokens (--dsw-* for backgrounds, borders, text levels, and state colors; --ds-* for motion) with fallbacks, adapting to dark/light themes automatically; responsive width on small screens (≤560px).
  • The card UI language follows the browser locale: Chinese for zh-* locales, English otherwise.

Requirements

  • Node.js ≥ 22.19

  • pnpm (dsh plugin essentially forwards pnpm inside the profile directory)

  • No global dsh install needed: every dsh command can run via pnpm dlx. This document uses:

    pnpm dlx @deepseek-ai/dsh@0.1.1-rc.2 <command>
    

    Replace 0.1.1-rc.2 with the dsh version you actually use (the package.json scripts are written the same way).

Install into a profile

Harness configuration and profiles live under ~/.dsh (C:\Users\<you>\.dsh on Windows); the web profile is ~/.dsh/profiles/web. dsh plugin forwards pnpm in that directory and automatically adds dependencies that declare dsh.bundle to the profile's bundle layer — no YAML editing needed.

Option 1: npm install (recommended, stable releases)

No cloning and no manual dependency installs — run from any directory:

pnpm dlx @deepseek-ai/dsh@0.1.1-rc.2 plugin --profile web add dsh-deepseek-usage-monitor
  • Plugin dependencies (@deepseek-ai/schemastery etc.) are installed into the profile's own node_modules, and the plugin joins dsh.profile.bundles automatically;
  • Update to the latest release: run the same command again;
  • Pin a version: plugin --profile web add dsh-deepseek-usage-monitor@0.1.0.

Option 2: GitHub direct install (tracks the latest commit)

The install source points straight at the GitHub repository, so you always get the latest main-branch code:

pnpm dlx @deepseek-ai/dsh@0.1.1-rc.2 plugin --profile web add github:KamChiHei/dsh-usage-monitor
  • ~/.dsh/profiles/web/package.json gains "dsh-deepseek-usage-monitor": "git+https://github.com/KamChiHei/dsh-usage-monitor.git" and the plugin joins dsh.profile.bundles;
  • Update to the latest commit: run the same command again;
  • Pin a version: use a tag reference such as github:KamChiHei/dsh-usage-monitor#v0.1.0.

Option 3: Local link install (for hacking on the source)

Run two steps inside the plugin directory:

cd C:\path\to\dsh-usage-monitor

# 1. Install the plugin's own dependencies (required first, see below)
pnpm install

# 2. Register the plugin into the web profile
pnpm dlx @deepseek-ai/dsh@0.1.1-rc.2 plugin --profile web add .

Or use the bundled one-liner (run inside the plugin directory, same effect as step 2 above):

pnpm run install:web

Why pnpm install first: pnpm registers the local directory as a link: dependency (a symlink) and does not install @deepseek-ai/schemastery and other dependencies for the plugin directory; Node resolves modules from the plugin's real path without going through the profile's node_modules, so the plugin directory needs its own node_modules.

Once installed:

  • ~/.dsh/profiles/web/package.json gains "dsh-deepseek-usage-monitor": "link:C:/path/to/dsh-usage-monitor" and the plugin joins dsh.profile.bundles;
  • Because link: is a live link, source changes take effect after restarting DSH — no reinstall needed.

Launch and verify

Launch (as usual):

pnpm dlx @deepseek-ai/dsh@0.1.1-rc.2 web

The Host startup log should show [deepseek-usage-monitor] loaded; web: /plugins/deepseek-usage-monitor/state, and the "Usage" card appears in the bottom-right corner.

Check that the plugin layer entered the composed config tree:

pnpm dlx @deepseek-ai/dsh@0.1.1-rc.2 --profile web --dump-config

The output should contain a # == dsh-deepseek-usage-monitor layer.

For other profiles, replace web with the profile name.

Fixing node_modules after moving the plugin directory

pnpm creates absolute-path symlinks under node_modules/@deepseek-ai/. Move or rename the plugin directory and they all dangle — dsh fails at startup with Cannot find package '@deepseek-ai/schemastery', and a plain pnpm install (Already up to date) won't fix it. In that case, inside the plugin directory run:

Remove-Item -Recurse -Force node_modules
pnpm install

Uninstall

pnpm run uninstall:web
# or
pnpm dlx @deepseek-ai/dsh@0.1.1-rc.2 plugin --profile web remove dsh-deepseek-usage-monitor

Debugging from local source

The official tutorial's --patch flow requires an absolute path to the plugin entry. This repository ships the template cordis.local.patch.yml with a hardcoded absolute path to index.js — after cloning or moving the repo, change it to your local path first.

Run from any directory (usually the plugin directory itself):

pnpm dlx @deepseek-ai/dsh@0.1.1-rc.2 web --patch "C:\path\to\dsh-usage-monitor\cordis.local.patch.yml"

Or use the one-liner inside the plugin directory (relative paths resolve against the current directory):

pnpm run dev:web

--patch loads the source entry and likewise requires a prior pnpm install in the plugin directory.

API key

Balance queries need a DeepSeek API key, but usually no extra configuration is required: the plugin reuses the key already configured in dsh — the credential store written by the web Models page (~/.dsh/.credentials.yaml). If you can chat with DeepSeek models in dsh, balance queries just work.

The key resolves in this order, stopping at the first hit:

  1. Plugin config apiKey (see the table below);
  2. The DEEPSEEK_API_KEY environment variable in the launching terminal ($env:DEEPSEEK_API_KEY = "sk-..." before starting dsh web; both of these are fixed at startup);
  3. The dsh credential service (ctx.get("credentials"), re-resolved on every refresh), layering: process environment → Models-page credential store → project .env → ~/.dsh/.env.

A key added on the Models page after startup takes effect on the next balance refresh (interval balanceRefreshMs) without a restart; changing the first two (config and launch-time env) requires a restart.

Configuration

Overrides go in the profile's cordis.patch.yml (~/.dsh/profiles/web/cordis.patch.yml). DSH patches replace whole lines, so keep name when overriding:

- replace:
    - id: deepseek-usage-monitor
      name: dsh-deepseek-usage-monitor
      config:
        balanceRefreshMs: 60000
        requestTimeoutMs: 10000
        recentLimit: 200

Options:

Option Default Description
apiKey "" (empty) Explicit DeepSeek API key; takes precedence over the environment variable and the dsh credential store; leave empty to reuse the key configured in dsh
baseUrl https://api.deepseek.com DeepSeek API base URL (trailing slashes stripped)
storePath ~/.deepseek-harness/deepseek-usage.json Stats file path (~ expansion supported)
balanceRefreshMs 60000 Balance refresh interval in ms (clamped to ≥ 5000)
requestTimeoutMs 10000 Balance request timeout in ms (clamped to ≥ 1000)
recentLimit 100 Recent calls retained and returned by the state endpoint (≥ 1)
sessionLimit 50 Recent sessions retained by last-request time (≥ 1)

Usage

After installing and restarting DSH Web, the "Usage" card in the bottom-right corner works automatically — no conversation needed; see "DSH Web status card" above for the interactions. Click "Refresh" to force a balance refresh immediately (equivalent to ?refresh=1).

The plugin listens to session/event in the background: assistant/chunk usage is the fallback source for failed requests; successful requests are merged with assistant/message.usage into a single record, avoiding double counting. Reasoning tokens are already included in output tokens and never added twice.

State endpoint response

GET /plugins/deepseek-usage-monitor/state returns:

{
  "generatedAt": "2026-08-22T00:00:00.000Z",
  "totals": {
    "requests": 15,
    "inputTokens": 21000,
    "outputTokens": 8000,
    "cacheReadTokens": 15000,
    "cacheWriteTokens": 1200,
    "reasoningTokens": 4000,
    "totalTokens": 45200,
    "lastRequestAt": "2026-08-22T00:00:00.000Z"
  },
  "sessionCount": 2,
  "models": [
    { "key": "deepseek-chat", "totals": { "requests": 12, "totalTokens": 45678 } },
    { "key": "deepseek-reasoner", "totals": { "requests": 3, "totalTokens": 12345 } }
  ],
  "providers": [
    { "key": "deepseek", "totals": { "requests": 15, "totalTokens": 58023 } }
  ],
  "balance": {
    "checkedAt": "2026-08-22T00:00:00.000Z",
    "isAvailable": true,
    "balanceInfos": [{ "currency": "CNY", "total_balance": "110.00" }]
  },
  "recent": [{ "timestamp": "…", "sessionId": "…", "turn": 1, "step": 1, "provider": "deepseek", "model": "deepseek-chat", "usage": { "…": "…" } }]
}

Notes:

  • models / providers are sorted by total tokens descending (name order breaks ties); recent is newest-first with at most recentLimit entries; sessionCount is the number of retained recent sessions (capped at sessionLimit);
  • a failed balance query adds an error field inside balance (with the reason) and sets isAvailable to false;
  • missing group names fall into unknown; store files from older versions without group data start from empty groups — no migration needed.

Verification and tests

No real API key needed. Pure-function tests cover usage normalization, reasoning deduplication, group keys, group accumulation, sorting, session pruning, and store-path expansion; integration tests cover UsageLedger accounting dedup, failed-request fallback, persistence round-trip, legacy store migration, write-failure recovery, and balance refresh (keys are stubbed):

pnpm test

Syntax check:

node --check index.js

The balance shape follows DeepSeek's official is_available / balance_infos response; token shapes follow the harness TokenUsage spec and DeepSeek's prompt-cache fields.

Project structure

File Purpose
index.js Host entry: event accounting, balance refresh, and the state endpoint
client.js Web entry: bottom-right status card UI and polling
usage-utils.mjs Pure functions: usage normalization, accumulation, grouping, sorting, session pruning, and store-path expansion (independently testable)
cordis.patch.yml Insertion entry declared via dsh.bundle when installing into a profile
cordis.local.patch.yml --patch source-debugging template (contains a hardcoded absolute path — edit after cloning)
tests/usage-utils.test.mjs Pure-function tests (node --test)
tests/usage-ledger.test.mjs UsageLedger integration tests: accounting dedup, persistence, balance refresh (node --test, no real key needed)
—/ 5

No ratings yet

Verified DSH bundle

Commit ef267cbdb212

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout