DeepSeek Harness plugin

dsh-cost-ledger

Cross-session persistent cost ledger for DeepSeek Harness: logs every LLM token usage to SQLite and exposes record/query/budget tools. Built-in DeepSeek pricing, overridable via config.

Jump to install

Source facts

Repository
suimi8/dsh-cost-ledger
Latest update
Aug 13, 2026
Category
Memory
GitHub stars
2
Format
plugin
Catalog evidence
Upstream dsh.bundle evidence
Evidence path
package.json#dsh.bundle
Checked against
0.1.0-rc.8
Upstream check date
2026-08-20

This evidence comes from the upstream catalog. This site has not installed, run, or security-reviewed the plugin.

Install

Start with a prompt that asks an agent to review the GitHub repository and source. Switch to the command if you want to install it yourself.

Copy this prompt into DSH, Codex, or another agent and ask it to review the GitHub repository and source first.

Do not install or run any commands yet. Read this plugin's GitHub repository, README, and relevant source code. Then answer the questions below clearly and directly so I can decide whether it fits my needs:

1. What is this plugin, and what problem does it solve?
2. Who is it for, and what are its typical use cases?
3. How is it used after installation? Include one minimal example.
4. What known limitations or privacy, security, compatibility, or maintenance risks does it have?
5. Give a clear recommendation: recommend, conditionally recommend, or do not recommend, with reasons.

Distinguish statements documented by the repository, inferences from source code, and unknowns. If evidence is insufficient, say so explicitly. Do not guess or simply repeat the README.

GitHub: https://github.com/suimi8/dsh-cost-ledger
Plugin: dsh-cost-ledger
Author: suimi8

Check the source files

Read the README and other files from this plugin directory before installing.

File explorer3 files
README.mdSource · read only

dsh-cost-ledger

> Cross-session persistent cost ledger for DeepSeek Harness. > Auto-logs every LLM token-usage event to SQLite and exposes record_cost / query_cost / set_budget agent tools. Built-in DeepSeek pricing, overridable via plugin config. Install-and-go, no extra wiring.

Status

Phase 1 (host side) — complete & live-verified in DSH 0.1.0-rc.6 (last verified 2026-08-13). 28 data-layer checks pass (pnpm selftest); the plugin loads and runs in a real dsh web profile.

The token-usage source and tool registration API are confirmed from DSH 0.1.0-rc.6 source and wired for real:

IntegrationAPIStatus
Token usage capturectx.on('llm/stream', (options, next) => AsyncIterable<StreamChunk>) — Cordis waterfall around every model call✅ wired
Tool registrationctx.tools.register(ToolDefinition) — real { name, description, parameters, output:{schema,render}, execute(args, exec) }✅ wired
Persistent storeSQLite via better-sqlite3 (prebuilt win32 binary, no compile)
PricingDeepSeek official (CNY/1M tok), cache-aware; config-overridable
Live load in dsh webinstalled via dsh plugin --profile web add ., host loads the bundle, apply() runs, SQLite opens✅ verified
End-to-end token capturedsh --profile headless "..." → real LLM call → llm/stream listener fires → usage chunk harvested → SQLite row written✅ verified

Live-verified: a one-shot dsh --profile headless "reply with exactly: hi" produced real rows in ledger.db ({model:"glm-5-2", inputTokens, outputTokens, cacheReadTokens}) — the full llm/stream → usage-chunk → SQLite pipeline works against the real host.

Module loading uses .ts extension specifiers (./store.ts) — the Cordis loader rewrites these and loads TypeScript directly, so no build step is needed; the plugin runs from source after install.

Two issues found & fixed during live integration:

  • { global: true } on ctx.on('llm/stream') — without it the listener only sees calls from its own fiber; the agent loop dispatches from a different scope. (Mirrors the host's own invariant.js usage.)
  • No uninjected service accessctx.session/ctx.workspace throw "cannot get property without inject" unless declared in inject. Session id now comes from the llm/stream event's options.sessionId instead.

Phase 2 (WebUI dashboard panel) — researched, not implemented. See [Phase 2](#phase-2-webui-panel).

---

What it does

  • Auto-logs token usage: subscribes to the llm/stream waterfall and writes {timestamp, session, project, model, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cost} per real provider call (including retries).
  • Computes cost: built-in DeepSeek pricing (deepseek-chat / deepseek-reasoner, CNY per 1M tokens). inputTokens is uncached input; billed input = input + cacheRead + cacheWrite, each priced at its own rate. Override or add models via config.
  • Three agent tools:

- record_cost — manually record a usage entry (backfill/test). - query_cost — total spend + per-model breakdown within a range (today/week/month/all/ISO); reports current daily-budget status. - set_budget — create/update/remove a spending budget (daily/weekly/monthly/never window, label-scoped: daily, model:<name>, project:<name>).

Install (community / standard)

dsh plugin --profile web add <path-or-package>

The host discovers the plugin from package.json (dsh.bundle.patch) — no absolute path, no manual wiring. The prepare script builds lib/ from source, so a git checkout installs self-contained. Restart dsh web after install.

> Git install note: pnpm ≥10 blocks a git dependency's prepare script until allowed. If the first add fails, copy the exact package key pnpm printed into the profile's pnpm-workspace.yaml (allowBuilds: dsh-cost-ledger: true) and re-run — exactly as the official docs describe.

Local development

pnpm install
pnpm selftest              # verify the data layer (28 checks)
pnpm typecheck
pnpm build                 # build host (lib/*.js) + client (lib/client.js)

# load into a running dsh web via the patch (absolute path, this machine):
dsh web --patch ./cordis.patch.yml

Pricing — built-in official rates

Built-in model pricing (元/百万 tokens, from each platform's official pricing page):

  • deepseek-v4-flash — input 0.10 / output 3.0 / cacheRead 9.0 (peak tier)
  • deepseek-v4-pro — input 0.30 / output 9.0 / cacheRead 27.0 (peak tier)
  • deepseek-chat (V3) / deepseek-reasoner (R1) — legacy aliases
  • glm-5-2 — default 0 (override in the dashboard ⚙️设置 tab or config; fill from Zhipu's pricing)

Unknown models record tokens only (cost = 0) until you set a price. Override anytime via the ⚙️设置 tab in the dashboard or the WebUI settings card — changes apply to new calls immediately.

Config

Edited in the dashboard ⚙️设置 tab (runtime, not persisted across restart) or under the WebUI plugin config card (durable) / cordis.patch.yml:

config:
  dbPath: 'dsh-cost-ledger/ledger.db'      # relative to DSH cwd
  defaultDailyBudget: 0                     # CNY; 0 = no limit
  pricing:
    deepseek-chat:                          # override or add any model
      input: 2
      output: 8
      cacheRead: 0.5
      cacheWrite: 8

Architecture

src/
  pricing.ts   built-in DeepSeek price table + computeCost()
  store.ts     SQLite ledger: insert + summaryByModel + spentSince + budgets
  tools.ts     three ToolDefs (pure handlers over store + config)
  config.ts    Schema (schemastery) config
  index.ts     apply(ctx): llm/stream listener + registerTools + cleanup
  client/      Phase 2 WebUI entry (stub)
cordis.patch.yml   local-dev profile patch (absolute path)
scripts/selftest.ts   standalone data-layer verification

Token capture (confirmed API)

llm/stream is a Cordis waterfall wrapping every streaming model call (retries included). The listener transparently forwards the underlying stream while harvesting the usage chunk:

ctx.on('llm/stream', (options, next) => (async function* () {
  let usage: TokenUsage | undefined
  for await (const chunk of next()) {
    if (chunk.type === 'usage') usage = chunk.usage
    yield chunk
  }
  if (usage) record(options, usage)   // persist one row
})())

Key billing facts (from official TokenUsage):

  • inputTokens = uncached input only. Billed input = input + cacheRead + cacheWrite.
  • reasoningTokens is already included in outputTokens — never double-count.
  • usage is not guaranteed (early abort/error) — absence is logged at debug.

The TokenUsage / StreamChunk / GenerateOptions types live in @deepseek-ai/dsh-llm (an unpublished host-injected package). This plugin declares minimal local types to typecheck standalone; swap for a real import type once the host package is resolvable.

Phase 2 (WebUI panel)

Researched against the dsh-web-ui reference. Key finding: DSH exposes no general-purpose sidebar-panel slot for external plugins. sidebar.workspaces / sidebar.settings are single-occupant and taken by the shell. Options:

  • DOM-level bypass (the dsh-task-board / dsh-ssh approach): MutationObserver + createRoot into [data-pane="sidebar"] / [data-pane="conversation"]. Most flexible.
  • Settings card via web-ui.plugin.item (lightest — a summary card under Settings → Plugins).
  • No chart-library precedent in the ecosystem. Recommended: inline SVG, or vendor recharts (strict CSP, no CDN).

Compatibility

  • DSH version: verified against 0.1.0-rc.6 (the API surface — ctx.on('llm/stream'), ctx.tools.register, ctx.inject, agentDefaultModel — is confirmed from DSH 0.1.0-rc.6 source).
  • Last verified: 2026-08-13.
  • Profiles: loads under both dsh web (full UI + HTTP API + dashboard) and dsh --profile headless (token capture + tools only; HTTP API and dashboard are web-profile-only and silently skipped).
  • OS: the SQLite backend ships a prebuilt win32 binary via better-sqlite3; other platforms build from source at install time (requires a C++ toolchain). Verified on Windows 11 / Node 22.
  • Pre-release caveat: DSH is in developer preview with expected compatibility-breaking changes. The token-usage source and tool registration API are confirmed against rc.6; a future mainline may rename them, which would require a plugin update.

Install

dsh plugin --profile web add <path-or-package>

The host discovers the plugin from package.json (dsh.bundle.patch) — no absolute path, no manual wiring. The prepare script builds lib/ from source, so a git checkout installs self-contained. Restart dsh web after install.

> Git install note: pnpm ≥10 blocks a git dependency's prepare script until allowed. If the first add fails, copy the exact package key pnpm printed into the profile's pnpm-workspace.yaml (allowBuilds: dsh-cost-ledger: true) and re-run — exactly as the official docs describe.

Uninstall

# Remove the bundle from the profile, then restart dsh web
dsh plugin --profile web remove dsh-cost-ledger

This removes the bundle from the profile's dsh.profile.bundles list and unlinks the package. The ledger database (dsh-cost-ledger/ledger.db) and any config overrides in cordis.patch.yml are not removed automatically — delete them manually if you want a clean sweep:

Remove-Item -Recurse -Force dsh-cost-ledger   # the data dir created under the DSH cwd

To disable temporarily without removing: comment out dsh-cost-ledger in the profile's cordis.yml bundles list and restart.

Quick start

# 1. Install into your web profile (see Install above)
dsh plugin --profile web add .

# 2. Restart dsh web, then just use the agent normally
dsh web

Every model call is now auto-logged. After a few prompts, ask the agent to query spend:

> 查一下今天花了多少钱(query_cost today)

Or open the dashboard panel in the Web UI (the cost-ledger tab) to see a live summary, per-model breakdown, and the budget/budget settings — no extra wiring.

Permissions & data

  • Files written: one SQLite database at dsh-cost-ledger/ledger.db (path configurable via dbPath, relative to the DSH working directory) plus its WAL/SHM sidecar files. Nothing else touches the filesystem.
  • Network: none. The plugin makes no outbound network calls. (The parse-prices endpoint calls the host's own ctx.llm.stream() — it reuses the model call path you already configured, not a new connection.)
  • Credentials: none read, stored, or transmitted. The plugin never touches your API keys; it only reads token counts and model names that the host already emits on the llm/stream event.
  • Data logged per model call: {timestamp, sessionId, project, model, provider, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cost}. No prompt content or completions are ever stored — only aggregate token counts and metadata.
  • HTTP API: when running under dsh web, the plugin registers read-only-ish JSON endpoints under /api/cost-ledger/*. They bind to the host's web server and are reachable from the same origin as the Web UI. The cleanup and parse-prices endpoints are write/POST endpoints intended for the dashboard UI.

Troubleshooting

  • Cannot find module './store.ts' / load error after a git pull: the Cordis loader resolves .ts specifiers directly, but a stale lib/ can interfere. Run pnpm build (or pnpm install to trigger prepare) and restart dsh web.
  • better-sqlite3 install fails on macOS/Linux: the prebuilt binary is win32-only; other platforms compile from source. Install a C++ toolchain (build-essential / Xcode CLT) and re-run pnpm install.
  • No cost rows appear: confirm the model call actually emitted a usage chunk (early aborts/errors may not). Token capture is best-effort — absent usage is logged at debug level. Also confirm dbPath is writable.
  • parse-prices returns 503 / "default model not configured": the agentDefaultModel service hadn't resolved yet, or no default model is set in Agent settings. Set a default model and retry.
  • pnpm blocks the prepare script on git install: add allowBuilds: dsh-cost-ledger: true to the profile's pnpm-workspace.yaml and re-run dsh plugin add.
  • Logs: plugin diagnostics go to the host log (dsh web console / dsh-run.log); the SQLite ledger itself is the system of record for cost data.
  • Rollback: dsh plugin --profile web remove dsh-cost-ledger + restart; optionally delete the dsh-cost-ledger/ data dir (see Uninstall).

Development

pnpm install
pnpm selftest              # verify the data layer (28 checks)
pnpm typecheck
pnpm build                 # build host (lib/*.js) + client (lib/client.js)

# load into a running dsh web via the patch (absolute path, this machine):
dsh web --patch ./cordis.patch.yml

react / react-dom and the @deepseek-ai/dsh-client-* runtime packages are external to the client bundle (resolved by the host's module loader at runtime), so they are correctly declared as devDependencies / peerDependencies, not dependencies. The only runtime dependency is better-sqlite3.

License & security

MIT — see [LICENSE](LICENSE).

Security reporting: this plugin has no network surface and stores no credentials, but if you find a vulnerability (e.g. unsafe SQL handling, path traversal via dbPath), please do not open a public issue. Report it privately via GitHub Security Advisories (Security → Report a vulnerability on the repo). All SQL uses parameterized statements (@named bind params) and all filesystem access is confined to the configured dbPath.