Problem/Motivation
Prompt caching has two billable token types: cache read (cheap) and cache write / cache creation (typically more expensive than ordinary input — 1.25x base input on Anthropic). This module prices cache reads but has no concept of cache writes at all, so it systematically understates spend on any site using prompt caching.
There is no cache-write dimension anywhere: not in the schema (ai_metering.install:26-133), not in AiPostGenerateSubscriber::extractTokens() (:234-253) or extractTokenDetails() (:271-280), not in LiteLLMPricingSource (:60-63), not in ModelsDotDevPricingSource (:65-68), and not in the PricingSourceInterface contract (:34, :51).
This is two gaps with different owners, and they should be treated separately.
(a) The token count is blocked upstream. drupal/ai's TokenUsageDto (ai/src/Dto/TokenUsageDto.php:26-32) exposes only input / output / total / reasoning / cached. There is no cache-write field, so this module cannot record a count that has no carrier. Closing this half needs a TokenUsageDto change upstream and is worth raising with the ai maintainers.
(b) The price is ours and is actionable today. Both upstream feeds already publish the rate, verified against the live feeds rather than from memory:
- LiteLLM publishes
cache_creation_input_token_cost(3.75e-06 for claude-sonnet-4-5; present on 243 of 2988 models) - models.dev publishes
cost.cache_write(3.75 for claude-sonnet-4-6)
Both parsers read the adjacent cache-read key and skip the write key on the same JSON object. That is roughly one line per parser plus an interface addition.
Concrete impact. On a Sonnet call priming a 2000-token cache (12 uncached input, 2000 cache-creation, 500 output), this module records $0.007536 while Anthropic bills $0.015036 — the dashboard shows about 50% of actual cost. The gap is worst exactly where an operator is deliberately using caching.
Secondary defect, same area. ai_metering.views.inc:215 tells operators that cached tokens "reduce billable cost". With writes billed above base input and entirely invisible here, that string is affirmatively misleading rather than merely incomplete, and should be corrected regardless of how the rest is scheduled.
Steps to reproduce
- Configure a provider supporting prompt caching (Anthropic, or OpenAI) and issue a call that primes a large cache — a long stable system prompt on its first use.
- Compare the recorded
estimated_cost_usdfor that priming call against the provider's billed cost. The cache-creation tokens are absent from both the recorded token counts and the cost.
Environment
- Drupal version: n/a — not reproduced on a running site, see AI assistance note
- ai_metering version: 1.0.x at commit a39fea9
- AI provider module + version: most visible on ai_provider_anthropic (cache writes at 1.25x base input); applies to any provider billing cache creation separately
- AI feature module tested: none — found by code inspection; feeds verified against live LiteLLM and models.dev data
Proposed resolution
Split the work along the ownership line:
- Price half (this module, now): add
cache_write_per_tokentoPricingSourceInterface, mapcache_creation_input_token_costinLiteLLMPricingSourceandcost.cache_writeinModelsDotDevPricingSource, and extendTokenEstimator::calculateCost()to charge it. - Count half (needs upstream): once
TokenUsageDtocarries a cache-write count, read it inextractTokens(). The existingtoken_detailsblob column (ai_metering.install:78-82) can hold it with no schema change. - Copy fix (now): correct
ai_metering.views.inc:215.
Also worth addressing while here: extractTokenDetails() (:277-278) hardcodes only reasoning and total rather than sweeping the DTO, so it will silently drop any field upstream adds later — including the cache-write count when it arrives.
Remaining tasks
- Implement the pricing half and cover it with tests.
- Correct the misleading Views help text.
- Raise the
TokenUsageDtogap with theaimaintainers; track the count half as blocked until then. - Consider making
extractTokenDetails()forward-compatible so future DTO fields are not silently dropped.
User interface changes
Recorded costs will rise on caching sites once cache writes are priced — note this is the opposite direction to the correction in #3615553, and the two partly offset. The Views help text changes.
API changes
Adding cache_write_per_token to PricingSourceInterface is an API change for any third-party pricing source plugin implementing that interface. Worth a default so existing implementations keep working.
Data model changes
None required. The token_details blob can carry the count when upstream provides it; a dedicated column is optional.
AI assistance
AI-Generated: Yes (Claude Code — multi-agent code analysis identified the gap, and a verification pass checked the live LiteLLM and models.dev feeds to confirm the rates are actually published rather than assumed; this issue text was drafted with AI assistance). No module code was generated. Every claim is code-cited and was reviewed before filing.
Issue fork ai_metering-3615562
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
codeitwisely commentedComment #6
codeitwisely commentedComment #8
codeitwisely commented