Some checks failed
CI / windows node 24 / wine blocking (push) Has been skipped
CI / node 22.19 (push) Has been skipped
CI / node 26 (push) Has been skipped
CI / python 3.10 / keyless SDK (push) Has been skipped
CI / python runtime / release-shaped Linux x64 (push) Has been skipped
CI / wine apt cache (push) Successful in 7s
CI / serial / linux (push) Has been skipped
Deploy documentation / build (push) Failing after 1m25s
Deploy documentation / deploy (push) Has been skipped
Landlock Run / Matrix (push) Successful in 5s
Release (vendor) / Pack npm tarballs (push) Failing after 2m47s
Release (dsh) / Pack npm tarballs (push) Failing after 1m56s
Sandbox / sandbox e2e (landlock, ubuntu-24.04) (push) Failing after 1m57s
Sandbox / sandbox e2e (bwrap, ubuntu-latest) (push) Failing after 1m19s
Release (vendor) / Publish to npm (push) Has been skipped
Release (dsh) / Publish to npm (push) Has been skipped
CI / serial / windows (self-hosted standby) (push) Has been cancelled
CI / larger-runner-benchmark (16, linux, dsh-ubuntu-24-04-16core, typecheck) (push) Has been cancelled
Landlock Run / darwin (no platform package — degradation proof) (push) Has been cancelled
Landlock Run / ${{ matrix.platform }} (push) Has been cancelled
CI / node 24 / static (push) Has been cancelled
CI / node 24 / coverage (push) Has been cancelled
CI / node 24 / snapshots and artifacts (push) Has been cancelled
CI / windows node 24 / native complete (push) Has been cancelled
CI / serial / linux (self-hosted standby) (push) Has been cancelled
CI / serial / macos (push) Has been cancelled
CI / larger-runner-benchmark (16, windows, dsh-windows-2025-16core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (32, linux, dsh-ubuntu-24-04-32core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (32, windows, dsh-windows-2025-32core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (4, linux, dsh-ubuntu-24-04-4core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (4, windows, dsh-windows-2025-4core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (64, linux, dsh-ubuntu-24-04-64core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (64, windows, dsh-windows-2025-64core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (8, windows, dsh-windows-2025-8core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (96, linux, dsh-ubuntu-24-04-96core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (96, windows, dsh-windows-2025-96core, production-site) (push) Has been cancelled
CI / consolidated-runner-benchmark (16, linux, dsh-ubuntu-24-04-16core, 16) (push) Has been cancelled
CI / consolidated-runner-benchmark (16, windows, dsh-windows-2025-16core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (32, linux, dsh-ubuntu-24-04-32core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (32, windows, dsh-windows-2025-32core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (4, linux, dsh-ubuntu-24-04-4core, 4) (push) Has been cancelled
CI / consolidated-runner-benchmark (4, windows, dsh-windows-2025-4core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (64, linux, dsh-ubuntu-24-04-64core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (64, windows, dsh-windows-2025-64core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (8, linux, dsh-ubuntu-24-04-8core, 8) (push) Has been cancelled
CI / consolidated-runner-benchmark (8, windows, dsh-windows-2025-8core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (96, linux, dsh-ubuntu-24-04-96core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (96, windows, dsh-windows-2025-96core, 2) (push) Has been cancelled
CI / all checks passed (push) Has been cancelled
Sandbox / sandbox e2e (seatbelt, macos-latest) (push) Has been cancelled
CI / larger-runner-benchmark (8, linux, dsh-ubuntu-24-04-8core, typecheck) (push) Has been cancelled
Sandbox / sandbox e2e (landlock, ubuntu-24.04-arm) (push) Has been cancelled
E2E (real DeepSeek API) / e2e (push) Failing after 1m24s
- new economy and maximum agent presets with three-role pipeline skill - new packages/extensions/tool-lab (home-lab ComfyUI/Docling/Whishper tools) - new packages/subagent/subagent-cursor provider - openrouter balance UI with on-demand refresh - session projection context-seed boundary fold - regenerate docs catalogs; keep local searxng benchmark scripts
119 lines
5.4 KiB
Markdown
119 lines
5.4 KiB
Markdown
# @deepseek-ai/dsh-openrouter-usage
|
||
|
||
English | [中文](README.zh.md)
|
||
|
||
OpenRouter spend tracking: a Typert Remote gateway exposing the account
|
||
balance plus the `openRouterCost` session projection, which prices logged
|
||
token usage against OpenRouter's model pricing table.
|
||
|
||
The package is inert without a resolvable `OPENROUTER_API_KEY` (the same
|
||
credential OpenRouter LLM routing already uses): no key means no fetch, an
|
||
empty pricing table (so every step stays unpriced), and an empty balance.
|
||
|
||
## Config
|
||
|
||
```yaml
|
||
- id: openrouter-usage
|
||
name: '@deepseek-ai/dsh-openrouter-usage'
|
||
config:
|
||
apiKeyEnv: OPENROUTER_API_KEY
|
||
baseURL: https://openrouter.ai/api/v1
|
||
syncEnabled: true
|
||
pricingRefreshMs: 21600000
|
||
balanceRefreshMs: 60000
|
||
```
|
||
|
||
| Key | Default | Meaning |
|
||
| --- | --- | --- |
|
||
| `apiKeyEnv` | `OPENROUTER_API_KEY` | Credential reference resolved per refresh. |
|
||
| `baseURL` | `https://openrouter.ai/api/v1` | OpenRouter API root; `/credits`, `/models`, and `/auth/key` are appended. |
|
||
| `syncEnabled` | `true` | Whether periodic pricing/balance refresh runs. |
|
||
| `pricingRefreshMs` | `21600000` (6h) | Pricing-table refresh interval in ms. |
|
||
| `balanceRefreshMs` | `60000` (60s) | Balance refresh interval in ms. |
|
||
|
||
The key resolves through the credentials seam (`ctx.credentials`), with the
|
||
launch environment as fallback, exactly like `dsh-web-search-deepseek`.
|
||
|
||
## Service contract
|
||
|
||
`ctx.openRouterUsage` is a Typert Remote gateway. The `snapshot()` method
|
||
returns a detached copy of the last successful balance snapshot: `balanceUsd`
|
||
is the available balance from `GET /credits` (`total_credits` minus the spent
|
||
`total_usage`, the figure OpenRouter's dashboard surfaces), plus `label` and
|
||
the monthly `usageTokens`/`limitTokens` budget from `GET /auth/key`,
|
||
`isFreeTier`, and an `updatedAt` epoch. Before any successful fetch it serves
|
||
an all-`null`
|
||
record; a failed refresh keeps the last-known snapshot and logs. The
|
||
`refresh()` method fetches the account NOW and resolves with the resulting
|
||
snapshot, for a user-initiated read that must not wait for the next scheduled
|
||
tick; concurrent callers share one in-flight fetch, and a failed fetch
|
||
resolves with the last-known snapshot rather than rejecting. The same key
|
||
also refreshes the model pricing table from `GET /models`
|
||
(`pricing.prompt`/`completion` USD per token, plus a flat `request` fee and
|
||
optional `input_cache_read`/`input_cache_write` when disclosed).
|
||
|
||
The `openRouterCost` projection folds each session's logged token usage
|
||
(`assistant/chunk` usage and `assistant/message` usage, deduplicated per
|
||
step) against the pricing table. Attribution prefers the assembled message's
|
||
own `provider`/`model`; a chunk-only (failed) step prices from the newest
|
||
`request/context` route. A step on a non-`openrouter` provider is outside the
|
||
domain and changes nothing; an OpenRouter step whose model has no pricing
|
||
entry counts as an unknown (unpriced) step.
|
||
|
||
Every value describes only its own session's work. A forked child session's
|
||
log opens with a verbatim copy of its parent's up to the header's
|
||
`seedLength`, and the fold skips that inherited prefix (reading only its
|
||
`request/context` route records, which carry no cost but attribute the
|
||
child's first chunk-only step). A session total plus its subagents' totals is
|
||
therefore a sum, never a double count. The projection's `steps` view
|
||
field maps each priced step to its cost in USD under `${turn}:${step}` keys,
|
||
so per-step surfaces can render spend without re-pricing; unpriced steps are
|
||
absent from the map.
|
||
|
||
## Extension points
|
||
|
||
Web surfaces read the `openRouterCost` projection and call the balance Remote
|
||
through the client assembly (`ctx.remote.openRouterUsage.snapshot()`); the
|
||
component stack ships in `dsh-client-ui-openrouter-usage`. The gateway
|
||
requires no session or agent wiring — everything rides the durable whole-log
|
||
projection and one cached fetch.
|
||
|
||
## Model Experience
|
||
|
||
### OpenRouter cost and balance readouts
|
||
|
||
#### What the model sees
|
||
|
||
Nothing. Cost and balance are client-facing read models only: neither enters
|
||
a model request, a tool schema, or any prompt. The agent loop is unchanged.
|
||
|
||
#### Token effect
|
||
|
||
No model tokens. Priced figures derive from usage already logged by the
|
||
existing `assistant/message` and `assistant/chunk` events.
|
||
|
||
#### KV Cache effect
|
||
|
||
None; the KV cache is unaffected because cost is a projection of existing
|
||
usage events, never a new model-visible input.
|
||
|
||
## Known Limitations and Deferred Work
|
||
|
||
- **Estimate, not itemized billing** — cost is `tokens × model pricing`,
|
||
not OpenRouter's itemized per-generation billing. Generation ids are not
|
||
durably logged, and the pi-ai adapter discards `usage.cost`, so the
|
||
projection reconstructs spend from token counts. Free-tier and
|
||
promotional pricing may differ from the model table.
|
||
- **Pricing as of the fold** — the projection prices a cell with the model
|
||
table current when that cell folds. Refreshing pricing only affects cells
|
||
folded afterward; already-folded history keeps its prior figures. A session
|
||
folded while the table was empty (no key yet, or a failed first fetch)
|
||
keeps its steps recorded as unpriced until a `stateVersion` bump refolds
|
||
them.
|
||
- **Per-token approximation** — cache-read/cache-write fall back to the
|
||
prompt rate when the API does not disclose separate cache rates, and the
|
||
flat request fee is charged once per step. Bills may differ by fractions
|
||
of a cent.
|
||
- **No settings card** — the plugin exposes config only through cordis.yml;
|
||
a settings-section UI is deferred.
|