Skip to main content
·4 min read

Pricing tables that know their own age

Directive Labs·

Somewhere in your codebase there is a number like 0.15 with a comment saying it's the price of a million tokens. You wrote it once. You have no idea whether it's still true.

Neither did we. So we gave our rate tables a date.

import {
  ANTHROPIC_PRICING,
  ANTHROPIC_PRICING_AS_OF,
} from '@directive-run/ai/anthropic';

ANTHROPIC_PRICING_AS_OF;  // "2026-08-15"

That string means a person opened the provider's pricing page on that day and compared every row. Not "we published this table in August" — checked, that day, against the source.

OPENAI_PRICING_AS_OF, GEMINI_PRICING_AS_OF and OLLAMA_PRICING_AS_OF ship alongside their tables too.


Why a date is worth an export

Because a wrong rate is the quietest bug in a cost path.

Nothing throws. No shape changes. No model is missing. Every cost your app reports is simply off by a constant factor, in the same direction, for every user — and it stays that way until someone reconciles an invoice by hand.

Documentation can't fix this. Ours already said the rates "may not reflect the latest," and that sentence has never once stopped a program from billing against a stale number. A value can:

const daysOld =
  (Date.now() - Date.parse(ANTHROPIC_PRICING_AS_OF)) / 86_400_000;

if (daysOld > 60) {
  logger.warn('Cost estimates are based on a rate table from %s', ANTHROPIC_PRICING_AS_OF);
}

Now "how current are our cost figures?" is a question with an answer, at runtime, in production.


The part we like most: the claim expires

A date you never revisit is just a comment with a colon in it. So the date is enforced.

Each table is pinned by a digest of its own rates, and a table whose date is more than ninety days old fails its test — with a message that says what to do:

2026-05-01 is 107 days old. Re-check this table against the provider's
pricing page, correct any rate that moved, then update both the date
and the pinned digest.

The digest alone can only notice a rate changing. It cannot notice a rate being wrong, because it's computed from the table it checks — a fingerprint of the thing agreeing with itself. The age ceiling is the half that can fail on its own, and it's what turns "we should check this sometime" into a build that stops.

Ninety days is a working guess. The thing we'd defend is that the claim expires at all.


Also in 1.29.x

Current models. OpenAI's table had nine legacy entries and none of the gpt-5 family; all fourteen are in now, along with o1, o1-pro and o3-pro. Gemini gained the 3.x line and gemini-2.5-flash-lite.

Three rates corrected against the providers' published pages: claude-sonnet-5 ($2/$10 — the introductory price became the standard one), o3 ($2/$8), and gemini-2.5-flash ($0.30/$2.50).

Cached-input rates are populated for OpenAI and Gemini. Neither charges for cache writes — caching is automatic — so that field stays unset rather than quietly defaulting to the input rate and over-billing you for something free.

Retired models are gone. gemini-2.0-flash and -flash-lite shut down on 1 June. Naming one now throws an error that names the model, instead of handing back a price for something you can't call.

DEFAULT_GEMINI_MODEL is exported, and now points at gemini-2.5-flash. If you call createGeminiRunner({ apiKey }) without naming a model, that's what you get — and you can read the constant instead of guessing. A new test keeps every adapter's default honest by requiring it to be a model its own rate table prices.


Upgrading

npm install @directive-run/core@latest @directive-run/ai@latest

Worth doing if you bill against these tables, use withBudget, or call the Gemini runners without naming a model.

If you've pinned gemini-2.0-flash explicitly, that model is gone at the provider — you'll want gemini-2.5-flash or one of the 3.x line.


Two things we'd suggest stealing regardless of whether you use Directive: ship the date beside the data, and make the date expire. Any constant in your codebase that describes the outside world — a price, a rate limit, a regional tax, a statutory deadline — is a claim about a world that keeps moving. The date is how the claim admits it.


Related

  • Test doubles that can say no createMockAgentRunner now honours AbortSignal, so cancellation is something you can actually test. Plus race telling a cancelled agent apart from a failed one.
  • The PII screen sees more of your data now The personal-data guardrail keeps up with modules registered at runtime, and no longer skips a whole value because one member is awkward.
  • Swap a derivation while the system is running Replace a computed value's definition at runtime and settle() comes back like it should. Plus system.meta.revision(), an O(1) way to know when your metadata cache is stale.

Directive is free and open source. If this was useful, consider supporting the project.

Stay in the loop. Sign up for our newsletter.

We care about your data. We'll never share your email.

Powered by Directive. This signup uses a Directive module with facts, derivations, constraints, and resolvers – zero useState, zero useEffect. Read how it works