Skip to content

@deepseek-ai/dsh-token-meter provides ctx.tokenMeter, a single replay-aware measurement service that answers two questions from the durable session log: what will the next request cost (request pressure) and how much model-visible content is live right now (surface). It never needs live adapter state — everything is derived from session events, so it is deterministic, restart-proof, and projection-friendly.

Package

PackageDescription
@deepseek-ai/dsh-token-meter"Replay-aware token measurement service (ctx.tokenMeter)"

It peers with dsh-llm, dsh-session, dsh-compaction, dsh-session-projection, dsh-invariants, and cordis; the only non-workspace dependency is zod@^4.4.3.

What is measured

The service measures tokens, not money. "Usage accounting" in this harness is split across two complementary views:

  1. Provider-reported usage — the exact usage chunk from each model call, stored on the session assistant/message event and folded into the tokenUsage projection (these are real token counts the provider returned).
  2. Heuristic pressure/surfaceTokenMeter.measure(session, requestHeader?) replays the log and prices a "surface" (the ordered set of model-visible messages) plus a "baseline" anchor. This is a prediction of the next request's cost, used for compaction and long-context decisions.

There is no per-token price table or dollar cost estimate anywhere in the meter — billing/cost is out of scope for the seam.

The data model

ts
// packages/llm/token-meter/src/types.ts
export type TokenMeasurementBaseline =
  | { readonly kind: 'none'; tokens: 0 }
  | { readonly kind: 'estimated'; tokens: number }      // full heuristic price
  | { readonly kind: 'usage'; tokens: number; usage: Readonly<TokenUsage> }  // provider-anchored

export interface TokenMeasurement {
  logRevision: number          // = next unread event seq
  baseline: TokenMeasurementBaseline
  surfaceDeltaTokens: number   // signed repricing vs. the baseline anchor
  totalTokens: number          // non-negative current request+response pressure
  surfaceTokens: number        // heuristic total across the current surface
  nodes: readonly TokenSurfaceNode[]  // ordered (seq, tokens) per surface position
}

Baselines

measure() picks a baseline for the request header (the canonical request envelope, i.e. provider/model/system/tools):

  • usage — when the latest successful call's canonical envelope matches the provided header and its provider total is at least the full heuristic price of that call (conservative-anchor rule). The provider usage is reused as-is, and only the signed surface delta since that call is repriced.
  • estimated — otherwise, the envelope (system prompt + tool schemas) and the current surface are heuristically repriced.
  • none — before any request with an empty surface.

This avoids paying a heuristic on every read while keeping correctness: a big new message since the anchor is added; a compaction that shrank the surface is subtracted.

The fixed heuristic estimator

packages/llm/token-meter/src/estimate.ts is a deliberately simple, fixed-density estimator (shared verbatim with the contextBreakdown projection so both surfaces agree):

ElementPrice
density4 characters per token (CHARS_PER_TOKEN)
structural overhead per block4 tokens (BLOCK_OVERHEAD)
role-field framing per message4 tokens (ROLE_OVERHEAD)
system promptceil(system.length / 4) + ROLE_OVERHEAD
tool schemasceil(JSON.stringify(tools).length / 4) + BLOCK_OVERHEAD
unknown blockconservative structural JSON price

estimateMessage, estimateHeader, estimateContent are exported pure functions, so callers can price a single message without a session.

Events and how the loop feeds it

There is no dedicated "usage event". The meter eavesdrops on ordinary session events:

EventRole in measurement
request/headerestablishes the canonical request envelope (system, tools, config)
step/start / step/enddelimit one model step (validates assistant events belong to an open step)
assistant/chunkraw stream chunks; a usage chunk provides an early provider sample
assistant/messagefinalizes the step; carries the definitive usage (the meter's primary anchor)
surface events (user/message, assistant/message, …)fold into the token-priced surface

The agent loop (packages/core/agent-loop/src/agent.ts) streams chunks via ctx.llm, appending each as assistant/chunk, then appends assistant/message (with usage and sourceEventSeqs) at step end — which is exactly what the meter replays. BlockAssembler is reused by the meter to reassemble provider output from cited chunk seqs so it can compare provider usage against the heuristic price without a second stream.

Projections (where usage surfaces)

Through the optional ctx.sessionProjections registry, the meter registers three pure replay projections:

ProjectionKeyView output
Token usagetokenUsage{ uncachedInputTokens, outputTokens, cacheReadTokens, cacheWriteTokens } totals, deduped per turn/step
Context pressurecontextPressure{ contextWindow?, pressureTokens?, projectedTokens? } — prompt-side occupancy from the newest usage sample plus surface movement
Context breakdowncontextBreakdownper-source pricing of the current surface (instructions/catalog/snapshot/… rows)

tokenUsage is what the UI reads for cumulative model usage, and contextPressure for context-window occupancy. Both are last-wins and accumulate without double-counting: a repeated usage for the same turn/step replaces the earlier value rather than adding again.

Where usage appears in the UI (brief)

  • packages/client/ui-settings-models and the session header surface use session.models and sessionModelSelect from the API gateway, not the meter directly.
  • The trajectory/session header consumes the tokenUsage and contextPressure projections (delivered as session/projection frames on the mux event stream and seeded by the history tail's projections block).

The meter itself is host-side; the client sees it through the session-projection pipeline and the forwarded settings/document-updated/llm/adapters-updated invalidation events.

Packages

Package
@deepseek-ai/dsh-token-meter
@deepseek-ai/dsh-llm (defines TokenUsage)
@deepseek-ai/dsh-session-projection (registry)

Further reading

  • The LLM layer — the usage StreamChunk, TokenUsage, and the assistant/chunk/assistant/message events the meter replays.
  • DeepSeek provider — how adapter mapUsage produces disjoint counts the meter consumes.
  • API gateway — how the client receives usage via session/projection frames and session.models.
  • packages/llm/token-meter/src/index.tsTokenMeter, measure, _sync, the baseline selection.
  • packages/llm/token-meter/src/estimate.ts — the fixed heuristic estimator.
  • packages/llm/token-meter/src/usage-projection.ts — the tokenUsage/contextPressure projection folds.