Back to home@xyzs996

dsh-switch-cost

DeepSeek Harness (dsh) plugin to compare LLM API cost across models and providers: prices the current session on the model that ran it, then reprices the same token counts against 15 routes on file. DeepSeek peak/off-peak resolved per UTC hour, cache read and write priced separately, every rate with its source and check date.

Stars
0
Language
JavaScript
Created
Aug 24, 2026
Updated
Aug 24, 2026
GitHub repo

Introduction

dsh-switch-cost

English | 中文

Prices the current session twice: once on the route that actually ran it, and once on every other route in the shipped price table. It answers "what did this cost" like the other usage plugins do, and then the question they don't — "what would the same traffic have cost somewhere else."

The session's usage is folded into token buckets keyed by route and by UTC hour, so a provider that bills different rates at different hours is priced hour by hour rather than at whichever rate happened to be in force when you asked. The fold records when; the price table decides what that hour costs.

Install

dsh plugin add dsh-switch-cost

Composes into any assembly that provides sessionProjections and tools. Both registrations sit behind ctx.inject(['sessionProjections'], …) — an assembly that composes no projection registry gets neither the projection nor the tool, rather than a tool that always answers zero.

Config

switch-cost:
  alternatives: 8   # how many other routes to report, cheapest first

Service surface

This plugin registers no service. It registers one projection unit and one tool.

Projection: switchCost

Host-only (no wire), stateVersion: 1. State:

{
  routes: { [`${provider}/${model}`]: { byHour: { [`${utcDay}-${utcHour}`]: Buckets } } },
  route: string | null,
  last: { turn, step, routeKey, hourKey, buckets } | null,
}

Buckets is { input, cacheRead, cacheWrite, output }. Hour keys are sparse; utcDay is 0–6 with Sunday first.

A step reports its usage twice — once as an assistant/chunk of type usage, once on the assembled assistant/message. The fold keeps last-wins per (turn, step): the second sample replaces the first instead of adding to it, and the withdrawal is made from the hour the first sample was originally filed under, not the hour the replacement arrived in. A step that straddles an hour boundary therefore leaves no phantom bucket. Events the fold does not care about return the same state reference, as the projection drive requires.

Usage that arrives before any request/context is filed under unknown/unknown rather than dropped.

Tool: switch_cost

No parameters. Reads the projection state for the calling agent's session and returns:

{
  tokens,            // whole-session bucket totals
  actual: [{ route, tokens, cost, costText, ratesApplied, pricedAs, source, checkedAt, layer }],
  actualTotal, actualTotalText,
  alternatives: [{ route, cost, costText, versusActual, source, checkedAt, layer }],
  unpriced?: [{ route, tokens }],
  caveats: string[],
}

A call with no owning agent session is rejected. A route with no price on file is reported under unpriced and left out of the total — it is never silently priced as zero.

Sample rendered result:

Ran on deepseek-official/deepseek-v4-flash: $0.0416 (peak and off-peak hours both billed)

Same tokens, other routes:
  deepseek-official/deepseek-v4-flash-vision-exp  $0.0416  +0%
  openai/gpt-5.6-luna  $0.0513  +23%
  deepseek-official/deepseek-v4-pro  $0.1260  +203%
  zhipuai/glm-4.7  $0.1446  +247%
  anthropic/claude-haiku-4-5  $0.2452  +489%
  moonshotai/kimi-k2.6  $0.2707  +550%

The price table

dsh-switch-cost/prices exports the table. Every row carries source, checkedAt and layer.

Two layers, vendor first:

  • layer: 'vendor' — read off the provider's own pricing page on checkedAt.
  • layer: 'models.dev' — taken from the community catalog.

Vendor wins because the catalog lags and, more importantly, because it has no representation of time-of-use pricing at all: off_peak, peak and time_of_use appear nowhere in its schema, and its tiers field carries context-size tiers only. A provider that bills two rates a day cannot be expressed there, so a plugin that mirrored the catalog would price those sessions at a single rate that is neither of the two.

Time-of-use

A row may carry a tariff instead of a single flat block. DeepSeek's is the one shipped:

{ peakWindowsUtc: [[1, 4], [6, 10]], peakWeekdaysUtc: [1, 2, 3, 4, 5] }

Those are the published Beijing windows 09:00–12:00 and 14:00–18:00, weekdays, with weekends fully off-peak. The rule is resolved in UTC, and for these particular windows that is exact rather than approximate: every hour they cover satisfies hour + 8 < 24, so adding the Beijing offset never rolls the date over, and the UTC weekday equals the Beijing weekday for every hour that can be peak. A test asserts that property directly and fails if a future window ever reaches past 16:00 UTC — at which point resolving in UTC would stop being safe and the rule would need a real timezone.

Model Experience

What the model sees

One tool with an empty parameter schema, described as reporting session cost and the cross-route comparison. The result text names each route, its cost, whether both tariffs were billed, and the alternatives cheapest-first with a signed percentage against what was actually spent. The closing line restates that the comparison is of published rates at equal token counts.

Token effect

One small fixed schema. The result grows with the number of routes that ran plus alternatives rows — roughly 200–400 tokens at the default of 8.

KV Cache effect

None. The plugin prices usage that has already been logged; it never assembles or sends a provider request, and it contributes nothing to the system prompt.

What this does not claim

These ship inside every result, not just here, because a result read by a model will otherwise be presented as a bill.

  • Token counts are the ones the model that actually ran produced. A different model tokenizes differently and would not produce these counts. This compares published rates at equal token counts; it does not predict the other model's invoice.
  • The cache read/write split is carried over unchanged. Prompt caching differs by provider in what it caches, how long it holds it, and what it charges to write. A route with a very different caching model is the least reliable row in the table.
  • Every rate is pay-as-you-go list price. Subscription plans, prepaid credit packages and free tiers are not modelled.
  • Prices are a dated snapshot. Each row reports its own source and date; check them before acting on a number.

Where a provider publishes no separate cache-write price, those tokens are billed at the input rate — cache-write tokens are cache-miss tokens — and the result sets cacheWriteBilledAtInputRate so the imputation is visible rather than assumed.

Development

pnpm install
pnpm test

License

MIT