{"_id":"@achasoft/dsh-usage-info","name":"@achasoft/dsh-usage-info","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@achasoft/dsh-usage-info","version":"0.1.0","description":"Context occupancy and account balance for the DeepSeek Harness Web Client: a session-header readout, a balance capability seam, and a DeepSeek provider","license":"MIT","type":"module","main":"lib/index.js","exports":{".":{"types":"./types/index.d.ts","default":"./lib/index.js"},"./host":{"types":"./types/host/index.d.ts","default":"./lib/host.js"},"./deepseek":{"default":"./lib/deepseek.js"},"./client":{"default":"./lib/client.js"},"./remote":{"default":"./lib/remote.js"},"./typert":{"default":"./lib/typert.host.js"},"./cordis.patch.yml":"./cordis.patch.yml","./package.json":"./package.json"},"dsh":{"bundle":{"patch":"./cordis.patch.yml"},"client":{"platform":"web","inject":["@deepseek-ai/dsh-api-remotes","@deepseek-ai/dsh-client-locale","@deepseek-ai/dsh-client-ui-conversation","@deepseek-ai/dsh-client-ui-settings","@deepseek-ai/dsh-client-ui-settings-plugins"]}},"scripts":{"build":"tsc -p tsconfig.build.json && tsdown","prepare":"tsc -p tsconfig.build.json && tsdown","typecheck":"tsc --noEmit","test":"node scripts/check-typert.mjs && vitest run","check:typert":"node scripts/check-typert.mjs","regen:typert":"node scripts/regen-typert.mjs"},"peerDependencies":{"@deepseek-ai/cordis":"*","@deepseek-ai/dsh-credentials":"*","@deepseek-ai/dsh-settings":"*","@deepseek-ai/dsh-typert-protocol":"*","@deepseek-ai/schemastery":"*"},"dependencies":{"zod":"^4.4.3"},"devDependencies":{"@deepseek-ai/cordis":"link:../../deepseek-harness/vendor/cordis","@deepseek-ai/dsh-api-remotes":"link:../../deepseek-harness/packages/api/remotes","@deepseek-ai/dsh-client-locale":"link:../../deepseek-harness/packages/client/locale","@deepseek-ai/dsh-client-runtime":"link:../../deepseek-harness/packages/client/runtime","@deepseek-ai/dsh-client-ui-conversation":"link:../../deepseek-harness/packages/client/ui-conversation","@deepseek-ai/dsh-client-ui-primitives":"link:../../deepseek-harness/packages/client/ui-primitives","@deepseek-ai/dsh-client-ui-settings":"link:../../deepseek-harness/packages/client/ui-settings","@deepseek-ai/dsh-client-ui-settings-plugins":"link:../../deepseek-harness/packages/client/ui-settings-plugins","@deepseek-ai/dsh-client-ui-slots":"link:../../deepseek-harness/packages/client/ui-slots","@deepseek-ai/dsh-credentials":"link:../../deepseek-harness/packages/credentials/credentials","@deepseek-ai/dsh-settings":"link:../../deepseek-harness/packages/settings/settings","@deepseek-ai/dsh-token-meter":"link:../../deepseek-harness/packages/llm/token-meter","@deepseek-ai/dsh-typert-protocol":"link:../../deepseek-harness/packages/typert/protocol","@deepseek-ai/schemastery":"link:../../deepseek-harness/vendor/schemastery","@types/node":"^22.20.1","@types/react":"~18.3.1","lightningcss":"^1.30.1","react":"^18.2.0","tsdown":"^0.15.6","typescript":"^5.9.2","vitest":"^3.2.7"},"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/achasoft/dsh-usage-info.git"},"keywords":["deepseek-harness","dsh","dsh-plugin","context-window","tokens","balance"],"types":"./types/index.d.ts","engines":{"node":"^22.19 || >=24"},"gitHead":"d806899a9626c9814add2c5b9f8b61b92823271d","_id":"@achasoft/dsh-usage-info@0.1.0","bugs":{"url":"https://github.com/achasoft/dsh-usage-info/issues"},"homepage":"https://github.com/achasoft/dsh-usage-info#readme","_nodeVersion":"25.2.1","_npmVersion":"11.19.0","dist":{"integrity":"sha512-g4+FLh1k3SEEBHogbUYODgFFEeiFGzyJCn6rceZ3nYO1lITEAvEJ1s9VZgAksGdAtvCs5mT6QhoA4O51sZpvpw==","shasum":"1420b766645945c70ba97904fddb9ed03916906b","tarball":"https://registry.npmjs.org/@achasoft/dsh-usage-info/-/dsh-usage-info-0.1.0.tgz","fileCount":26,"unpackedSize":700346,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD+D9AHQEibLkYbEoH8iHn/XL5gduD94mBQpokDqzgi9AIgYNbY4P3xbK0RhdiULjOz7Tf8igcBd03PuNGAHIkli3c="}]},"_npmUser":{"name":"navid.kianfar","email":"navid.kianfar@outlook.com"},"directories":{},"maintainers":[{"name":"navid.kianfar","email":"navid.kianfar@outlook.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dsh-usage-info_0.1.0_1787711503834_0.23257697683851197"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-26T02:31:43.667Z","0.1.0":"2026-08-26T02:31:44.025Z","modified":"2026-08-26T02:31:44.278Z"},"maintainers":[{"name":"navid.kianfar","email":"navid.kianfar@outlook.com"}],"description":"Context occupancy and account balance for the DeepSeek Harness Web Client: a session-header readout, a balance capability seam, and a DeepSeek provider","homepage":"https://github.com/achasoft/dsh-usage-info#readme","keywords":["deepseek-harness","dsh","dsh-plugin","context-window","tokens","balance"],"repository":{"type":"git","url":"git+https://github.com/achasoft/dsh-usage-info.git"},"bugs":{"url":"https://github.com/achasoft/dsh-usage-info/issues"},"license":"MIT","readme":"# @achasoft/dsh-usage-info\n\nContext usage and account balance for the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) Web Client. A readout in the session header shows how full the model's context window is and what the account paying for it currently holds; clicking it opens a panel with the breakdown behind both numbers.\n\nBalance is a swappable capability. A DeepSeek provider ships with this package; any other billing backend can implement the same seam.\n\n## Requirements\n\n- A dsh installation with the Web Client (`@deepseek-ai/dsh-web-app`).\n- For context occupancy: nothing. It rides the token meter the harness already runs.\n- For the balance: one provider, configured below. Without one, the readout still shows context occupancy and the settings card explains what is missing — an unconfigured install loses no working half.\n\n## Install\n\n`dsh plugin` forwards to pnpm, so any pnpm source works:\n\n```bash\ndsh plugin --profile default add @achasoft/dsh-usage-info\n```\n\n<details>\n<summary>Other install sources</summary>\n\n```bash\ndsh plugin --profile default add ./achasoft-dsh-usage-info-0.1.0.tgz   # from `pnpm pack`\ndsh plugin --profile default add ./dsh-usage-info                       # a local checkout\ndsh plugin --profile default add github:achasoft/dsh-usage-info#<sha>   # from git\n```\n\nA git install fetches sources, not build output. This package ships a `prepare` script that builds them, but pnpm will not run it until you allow it — add the key pnpm names to your profile's `pnpm-workspace.yaml`:\n\n```yaml\nallowBuilds:\n  '@achasoft/dsh-usage-info': true\n```\n\nThat is permission to execute this package's code at install time. Prefer the npm or tarball forms, which need no such allowance.\n</details>\n\nThe bundle appends itself to your profile automatically. Verify with `dsh --profile default --dump-config`, which should show a `# == @achasoft/dsh-usage-info` layer.\n\n## Turn the balance on\n\nThe provider ships disabled, because no default can guess which endpoint to ask or which environment variable holds its key. Enable it from your profile's own `cordis.patch.yml` (`$DSH_HOME/profiles/<name>/cordis.patch.yml`). A patch replaces a row's entire `config`, so restate every key.\n\n```yaml\n- id: usage-info-deepseek\n  disabled: false\n  config:\n    baseURL: https://api.deepseek.com\n    apiKeyEnv: DEEPSEEK_API_KEY\n    timeoutMs: 15000\n```\n\n| Field | Meaning |\n|---|---|\n| `baseURL` | API prefix without the `/user/balance` suffix. |\n| `apiKeyEnv` | **Name of an environment variable**, never the key. Defaults to the same reference the harness's own DeepSeek adapter uses, so an installation that can already call the model can already read its balance. |\n| `timeoutMs` | Deadline for one reading. |\n\n**The key never reaches the browser.** That is the whole reason this plugin has a host half: the balance endpoint needs a bearer token, and a token shipped to a browser is a leaked token. The browser asks the host, the host asks the provider.\n\n**The key is addressed, never stored.** `apiKeyEnv` is a credential *reference*: the value is resolved from the harness credential seam at the start of every reading and never cached, so rotating it reaches the next reading with no restart — and rotating it also drops the cached balance, because a new key can mean a different account.\n\nOnly one provider may be mounted. A composition that mounts two fails loudly at load rather than silently preferring one.\n\n## Settings\n\nThe **Usage information** card on the plugin settings tab edits these live; the values below are the composition defaults.\n\n| Field | Default | Meaning |\n|---|---|---|\n| `showContext` | `true` | Show context occupancy for the current session. |\n| `showBalance` | `true` | Show the account balance. |\n| `refreshIntervalMs` | `300000` | How often each browser re-asks for a balance. |\n| `cacheTtlMs` | `240000` | How long one reading stays servable from the host's shared cache. |\n| `lowBalanceThreshold` | *(unset)* | Exact decimal string at or below which the readout warns. Blank disables the warning. |\n\n`refreshIntervalMs` is the poll cadence and `cacheTtlMs` is the request rate. Every open tab polls on its own timer, but a poll landing inside the cache window is answered from the host's stored reading, so the provider is asked at most once per `cacheTtlMs` no matter how many windows are open. Setting `cacheTtlMs` above `refreshIntervalMs` is refused at load: every poll would be served a reading already older than the cadence it was scheduled at.\n\n## How it works\n\n```\n                      ┌─ contextPressure  ─┐\nsession projections ──┤                    ├──→ ring + breakdown bar     (no request at all)\n                      └─ contextBreakdown ─┘\n                                                                          session header readout\nbrowser poll ── one unary RPC ──→ UsageInfoService.balance()               ↑\n                                        │                                  │\n                                   shared TTL cache ──→ ctx.accountBalance ─┴─ deepseek\n```\n\nFour decisions worth knowing:\n\n- **The two halves arrive by different routes, on purpose.** Context occupancy is already a durable session projection the harness's token meter publishes, and every session-scoped seat receives it through the standard kit. Routing it through this plugin's endpoint would add a round trip, a second copy of the same numbers, and a way for the two copies to disagree. The balance cannot work that way, because it needs a credential.\n- **Nothing here is model-facing.** No prompt, no tool, no session event. A balance reading is operator information that never enters a request, and the context figures are read from the log rather than written to it.\n- **Money is never a number.** Every figure stays the exact decimal string the provider sent, from the JSON parse through the wire to the display, and the low-balance comparison is digit-wise. `0.1 + 0.2` is why: a rounding artifact in a figure a person reads as their money is a defect no display formatting can undo.\n- **Failures cross the wire as values, not exceptions.** The RPC gateway erases a thrown error's classification, and the readout's next move depends on which class it was — \"configure a key\" is not \"try again\", and \"this endpoint publishes no balance\" means stop asking entirely, which is why a 404 halts the poll instead of retrying forever.\n\n### What the numbers mean\n\nOccupancy is anchored to the provider: it is the last reported prompt size plus a heuristic repricing of whatever the conversation gained or lost since that sample. That is what makes it answer for the *next* request and react the moment a compaction shortens the surface — a compaction reports no usage of its own, so the raw provider sample alone would keep showing a full context after one.\n\nThe three parts in the panel — system prompt, tool definitions, conversation — are a *composition*, not a total. They use the meter's fixed density estimate, which underprices CJK text and JSON schemas, so they will not sum to the anchored occupancy figure. Read them as proportions.\n\n## Extending it\n\n`ctx.accountBalance` is a capability, not a DeepSeek client. To bill against something else, implement the Service Definition this package exports and mount your plugin instead of `usage-info-deepseek`:\n\n```ts\nimport { AccountBalanceProvider, BalanceError } from '@achasoft/dsh-usage-info'\nimport type { AccountBalance, BalanceProviderInfo } from '@achasoft/dsh-usage-info'\n\nexport class MyBalance extends AccountBalanceProvider {\n  async read(signal: AbortSignal): Promise<AccountBalance> { /* … */ }\n  async describe(): Promise<BalanceProviderInfo> { /* … */ }\n}\n```\n\nThrow `BalanceError` with one of the capability's classified codes; every other rejection is a defect. Do not cache — the consumer owns that, because only it knows how many surfaces share one reading.\n\n## Development\n\n```bash\npnpm install        # builds both halves through `prepare`\npnpm run typecheck  # src, generated, and tests\npnpm test           # artifact check, then vitest\npnpm run build      # tsc emit → tsdown bundle\n```\n\n`generated/` holds the Typert RPC contract, which only the deepseek-harness generator can produce, so it ships as committed source rather than as build output. `pnpm test` refuses to pass when it drifts from `src/host/`: it compares the declared `@Remote` endpoints against the artifact's and checks a fingerprint of the Host surface. A stale artifact is not a build error but a silent wire mismatch — the browser would validate against schemas that no longer describe what the host sends.\n\nRegenerate it against a clean harness checkout:\n\n```bash\nnode scripts/regen-typert.mjs ../deepseek-harness\n```\n\nThe script stages this package's host sources inside that workspace, builds the harness's Host face, copies the artifacts back, and restores everything it touched. It refuses to run against a dirty tree, and it takes several minutes.\n\n**Run it alone.** It is the only thing in this repository that writes to the harness checkout, and it assumes exclusive access: it edits `tsconfig.base.json`, `tsconfig.host.json`, and `pnpm-lock.yaml`, then restores all three with `git checkout` in a `finally`. Two plugins regenerating at once will therefore clobber each other's staging — the second restore reverts the first's edits mid-build. Check that no other `regen-typert.mjs` is running, and that the harness tree is yours, before starting.\n","readmeFilename":"README.md","_rev":"1-3eaf9e91f32f309fb88021533c9d2645"}