fix: map OpenRouter prompt_tokens_details to Anthropic cache fields in Usage

When routing Anthropic models through OpenRouter, cache token counts arrive in
the final streaming chunk as prompt_tokens_details.cached_tokens and
prompt_tokens_details.cache_write_tokens (OpenAI format). The Usage model was
not populating its Anthropic-style private fields (_cache_read_input_tokens,
_cache_creation_input_tokens) from these values, so cache tokens always showed
as 0 in cost calculations, logging, and downstream clients.

Changes:
- Usage.__init__: after Anthropic and DeepSeek mappings, add an OpenRouter/
  OpenAI-format mapping block that reads prompt_tokens_details.cached_tokens →
  _cache_read_input_tokens and prompt_tokens_details.cache_write_tokens →
  _cache_creation_input_tokens when the Anthropic native params are absent.
- PromptTokensDetailsWrapper: add explicit cache_write_tokens field (Optional[int])
  to properly type the OpenRouter extension field that carries cache write counts.

This fixes the Anthropic experimental pass-through adapter when using OpenRouter
as the backend: the streaming adapter already has the merge logic in
streaming_iterator.py, but it had no data to merge because _cache_*_input_tokens
was never populated from prompt_tokens_details.

Made-with: Cursor
This commit is contained in:
drexkooo 2026-03-17 02:25:53 +01:00
parent 278c9babc6
commit e44128c68c

View file

@ -1467,6 +1467,9 @@ class PromptTokensDetailsWrapper(
image_count: Optional[int] = None
"""Number of images sent to the model. Used for Vertex AI multimodal embeddings."""
cache_write_tokens: Optional[int] = None
"""Tokens written to the prompt cache in this request (OpenRouter extension for Anthropic models)."""
video_length_seconds: Optional[float] = None
"""Length of videos sent to the model. Used for Vertex AI multimodal embeddings."""
@ -1654,6 +1657,24 @@ class Usage(SafeAttributeModel, CompletionUsage):
):
self._cache_read_input_tokens = params["prompt_cache_hit_tokens"]
## OPENROUTER / OPENAI FORMAT MAPPING ##
# OpenRouter (and other OpenAI-compatible providers) return cache token counts
# in prompt_tokens_details rather than as top-level Anthropic fields.
# Populate the private Anthropic-style fields from prompt_tokens_details when
# the explicit Anthropic params (cache_creation_input_tokens,
# cache_read_input_tokens) were not provided, so that downstream consumers
# (cost calculators, streaming adapters, logging hooks) see the correct values.
_ptd = getattr(self, "prompt_tokens_details", None)
if _ptd is not None:
if not self._cache_read_input_tokens:
_cached = getattr(_ptd, "cached_tokens", 0) or 0
if _cached > 0:
self._cache_read_input_tokens = _cached
if not self._cache_creation_input_tokens:
_writes = getattr(_ptd, "cache_write_tokens", 0) or 0
if _writes > 0:
self._cache_creation_input_tokens = _writes
for k, v in params.items():
setattr(self, k, v)