Files
Coder 8719fa84f0
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
feat: add economy/maximum presets, tool-lab and subagent-cursor extensions
- 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
2026-08-23 22:12:30 +07:00

5.4 KiB
Raw Permalink Blame History

@deepseek-ai/dsh-openrouter-usage

English | 中文

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

- 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.