Skip to content

AI Gateway: Anthropic cache-creation tokens are not billed on the v4 protocol path (ai@7), but are on v3 (ai@6) #20414

Description

@moshi123456

Description

Summary

The same logical request, sent through AI Gateway with two different AI SDK majors, is
charged differently: the cache-creation ("cache write") term is missing entirely from
the gateway's returned cost when the request goes through the ai@7 /
@ai-sdk/gateway@4 path (specificationVersion: 'v4'). On ai@6 /
@ai-sdk/gateway@3 ('v3') the same term is charged correctly.

input, cache_read and output are all priced correctly on both paths. Exactly one
term — cache creation — is dropped on v4.

This is under-billing (Vercel charges less than the provider list price the docs
promise), so it is presumably not intentional.

Environment

arm A arm B
ai 6.0.159 7.0.52
@ai-sdk/gateway 3.0.96 4.0.41
GatewayLanguageModel.specificationVersion v3 v4
ai-gateway-protocol-version header 0.0.1 0.0.1 (unchanged)

Model: anthropic/claude-haiku-4.5 · finalProvider: anthropic · ZDR +
disallowPromptTraining enabled · cache_control: { type: "ephemeral", ttl: "1h" } on the
system prompt · Node 24 · same gateway API key / team for both arms.

Published Anthropic rates for Haiku 4.5: input $1/MTok, 5m write $1.25/MTok,
1h write $2/MTok, cache hit $0.10/MTok, output $5/MTok.

Observed

arm A — ai@6.0.159 + @ai-sdk/gateway@3.0.96 (spec v3) — CORRECT
Anthropic usage (providerMetadata.anthropic.usage):
  input_tokens                              13
  cache_creation_input_tokens           19,818
  cache_creation.ephemeral_1h_input_tokens  19,818
  cache_read_input_tokens                    0
  output_tokens                              4

gateway.inputInferenceCost   = 0.039649
  13 x $1/M  +  19,818 x $2/M  =  0.000013 + 0.039636 = 0.039649   <-- exact match
gateway.outputInferenceCost  = 0.00002   ( = 4 x $5/M )            <-- exact match
gateway.cost                 = 0.039669
arm B — ai@7.0.52 + @ai-sdk/gateway@4.0.41 (spec v4) — cache write billed $0
Anthropic usage (providerMetadata.anthropic.usage):
  input_tokens                              13
  cache_creation_input_tokens           19,819
  cache_creation.ephemeral_1h_input_tokens  19,819
  cache_read_input_tokens                    0
  output_tokens                              4

gateway.inputInferenceCost   = 0.000013
  13 x $1/M                                     =  0.000013        <-- exact match
  expected with cache write:
  13 x $1/M  +  19,819 x $2/M                   =  0.039651        <-- NOT charged
gateway.outputInferenceCost  = 0.00002
gateway.cost                 = 0.000033      (vs 0.039669 on v3, ~1,200x less)

Anthropic reports the cache-creation tokens in both arms, so the write really happens;
only the gateway's pricing of it differs.

Isolation: on v4, cache read is still priced correctly

Second call on the same cache key (v4 path), so it is a read instead of a write:

  input_tokens 13 · cache_read_input_tokens 19,819 · cache_creation 0

gateway.inputInferenceCost = 0.0019949
  13 x $1/M  +  19,819 x $0.10/M  =  0.000013 + 0.0019819 = 0.0019949  <-- exact match

So on the v4 path: input OK, cache-read OK, output OK, cache-creation dropped.

Why we suspect the v4 request path specifically

GatewayLanguageModel forwards the call options to the gateway verbatim — getArgs() is
just { ...optionsWithoutSignal } posted as the JSON body. So the only thing that changes
between the two arms on the wire is the call-options schema (LanguageModelV3CallOptions
vs LanguageModelV4CallOptions). The ai-gateway-protocol-version header is 0.0.1 in
both majors, and the client-side files that touch cost
(gateway-generation-info.ts, gateway-spend-report.ts) are unchanged between 3.0.96 and
4.0.41 apart from optional-headers handling — i.e. the cost is computed server-side.

That points at the server-side cost computation for the v4 payload shape having lost the
cache-creation term. Possibly related: AI SDK v7 split token usage into
inputTokenDetails.{noCacheTokens,cacheReadTokens,cacheWriteTokens} where v6 exposed
cachedInputTokens; a consumer-side instance of the same class of bug is
PostHog/posthog#63136 ("AI OTel mapping drops AI SDK v7 cache and reasoning usage,
producing incorrect costs").

Impact

Any team on ai@7 + Anthropic + prompt caching is under-charged by the full
cache-creation component of every request. For agent workloads (large cached system
prompts, many turns) cache creation is a large fraction of the true cost, so the
gateway-reported cost can be a small fraction of the provider list price.

Because it under-bills, it is also invisible to the usual "my bill is too high" reports,
and it silently corrupts anything downstream that trusts providerMetadata.gateway.cost
(cost dashboards, per-request budget caps, usage-based billing built on top of the gateway).

Also observed (separate, minor)

ai@6.0.159 accepts an instructions option but silently ignores it — in the reproduction below
the 19.8k-token system prompt was never sent (total input_tokens was 14) and no warning
was emitted. Flagging in case it is unintended in that major.

Question

Is this a bug, or an intentional discount / promotion on the v4 path?

If it is intentional, it would help to have it documented — the pricing docs currently
say AI Gateway charges the provider's list price with no markup, and the Automatic
Caching docs describe Anthropic cache writes as 1.25x the base input rate — and to know
whether the v3 path will be brought in line. If it is a bug, please say whether earlier
usage will be trued up, so downstream cost tracking can be adjusted accordingly.

Reproduction

Two directories, one dependency set each, identical probe:

// v6/package.json
{ "type": "module", "dependencies": { "ai": "6.0.159", "@ai-sdk/gateway": "3.0.96" } }
// v7/package.json
{ "type": "module", "dependencies": { "ai": "7.0.52",  "@ai-sdk/gateway": "4.0.41" } }
import { generateText } from "ai";
import { createGateway } from "@ai-sdk/gateway";

// Unique prefix so the call is guaranteed to be a cache WRITE, and long enough to be cacheable.
const filler = Array.from({ length: 900 }, (_, i) =>
  `Rule ${i}: when handling record ${i}, verify the checksum and log the outcome deterministically.`
).join("\n");
const system = `CACHE-KEY-NONCE ${Date.now()}\n${filler}`;

const gateway = createGateway({ apiKey: process.env.AI_GATEWAY_API_KEY });

const res = await generateText({
  model: gateway("anthropic/claude-haiku-4.5"),
  // NOTE: ai@7 rejects system messages inside `messages` ("Use the instructions option
  // instead"), while ai@6 silently ignores `instructions`. So each arm uses the form its
  // own major supports; the resulting upstream request is equivalent, as confirmed by
  // Anthropic reporting ~19.8k cache_creation tokens in both arms.
  //   ai@6: messages: [{ role: "system", content: system, providerOptions: {...} }, user]
  //   ai@7: instructions: { role: "system", content: system, providerOptions: {...} }
  instructions: {
    role: "system",
    content: system,
    providerOptions: { anthropic: { cacheControl: { type: "ephemeral", ttl: "1h" } } },
  },
  messages: [{ role: "user", content: "Reply with the single word: ok" }],
  maxOutputTokens: 16,
  temperature: 0,
  providerOptions: { gateway: { zeroDataRetention: true, disallowPromptTraining: true } },
});

console.log(JSON.stringify({
  usage: res.usage,
  gateway: res.providerMetadata?.gateway,
  anthropic: res.providerMetadata?.anthropic,
}, null, 2));

Run each directory once and compare gateway.inputInferenceCost against
input x base + cacheWrite x (1.25x or 2x base) + cacheRead x 0.1x base.

AI SDK Version

  • arm A (correct): ai 6.0.159, @ai-sdk/gateway 3.0.96, @ai-sdk/provider 3.0.8
  • arm B (cache write billed $0): ai 7.0.52, @ai-sdk/gateway 4.0.41, @ai-sdk/provider 4.0.5
  • Node 24

Code of Conduct

  • I agree to follow this project's Code of Conduct

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions