Add cache-write and tool-use prompt tokens to TokenUsageDto
## Problem/Motivation `TokenUsageDto` (`src/Dto/TokenUsageDto.php`) only models five token counts: ```php public ?int $input = NULL, public ?int $output = NULL, public ?int $total = NULL, public ?int $reasoning = NULL, public ?int $cached = NULL, ``` Two token-usage values reported by the major providers have nowhere to live in this DTO, so they are silently dropped before reaching logging/observability or any cost-reporting layer: ### 1. Cache **write** / cache creation tokens The current `cached` field maps only to **cache reads** (`prompt_tokens_details.cached_tokens` in `OpenAiBasedProviderClientBase::setChatTokenUsage()`). There is no field for **cache writes / cache creation**. Anthropic reports this separately as `cache_creation_input_tokens` and bills it at a *premium* (≈1.25× input for the 5-minute TTL, ≈2× for the 1-hour TTL). A request that creates a large cache entry therefore currently looks free, which makes accurate cost attribution impossible. ### 2. Tool-use prompt tokens Gemini reports `toolUsePromptTokenCount` — the input tokens consumed by function calling / code execution. There is no DTO field for these, so tool-heavy requests under-report their real input usage. ## Current mapping `OpenAiBasedProviderClientBase::setChatTokenUsage()` populates the DTO as: | DTO field | Source | |-------------|--------| | `input` | `usage.prompt_tokens` | | `output` | `usage.completion_tokens` | | `total` | `usage.total_tokens` | | `reasoning` | `usage.completion_tokens_details.reasoning_tokens` | | `cached` | `usage.prompt_tokens_details.cached_tokens` (cache **read** only) | ## Provider references - **Anthropic (Messages `usage`):** `cache_creation_input_tokens` (with a `cache_creation` breakdown of `ephemeral_5m_input_tokens` / `ephemeral_1h_input_tokens`); `cache_read_input_tokens` is what currently maps to `cached`. - **Google Gemini (`usageMetadata`):** `toolUsePromptTokenCount`. ## Proposed solution Add two nullable fields to `TokenUsageDto`. Because every constructor argument is optional and nullable, this is backward-compatible — providers that don't set them keep working unchanged. Suggested additions (names open to bikeshedding): - `cachedWrite` — cache creation / cache write tokens (Anthropic `cache_creation_input_tokens`). Also document that the existing `cached` is specifically cache **read**. - `toolUse` — tool/function-call prompt tokens (Gemini `toolUsePromptTokenCount`). Open question: should the cache-write TTL split (5m vs 1h) be modeled now, or left for a follow-up? It is provider-specific and may be better as a nested structure. ## Remaining tasks - [ ] Agree on the field names. - [ ] Add `cachedWrite` and `toolUse` (nullable) to `TokenUsageDto`. - [ ] Populate them in `OpenAiBasedProviderClientBase::setChatTokenUsage()`, the streaming iterator, and any other provider clients that build the DTO. - [ ] Update/extend unit tests and the `ai_observability` logging/metrics subscribers so the new counts are recorded.
issue