Add metadata (token usage and rate limits) to ChatGenerator in AI API Explorer
## Summary The **Chat Generation Explorer** (`ChatGenerator` plugin) in the AI API Explorer should also display the response **metadata** returned by the provider — specifically **token usage** and, when the provider exposes it, **rate limit** information. ## Problem When you run a chat through the explorer, the response object (`\Drupal\ai\OperationType\Chat\ChatOutput`) already carries this metadata, but the explorer throws it away. `ChatOutput` already exposes everything we need: - `getTokenUsage(): \Drupal\ai\Dto\TokenUsageDto` — `input`, `output`, `total`, `reasoning`, `cached` tokens. - `getRateLimits(): \Drupal\ai\Dto\ChatProviderLimitsDto` — `rateLimitMaxRequests`, `rateLimitMaxTokens`, `rateLimitRemainingRequests`, `rateLimitRemainingTokens`, `rateLimitResetRequests`, `rateLimitResetTokens`. The DTO has an `empty()` helper so the UI can hide the section when a provider does not return limits. Because this data is never surfaced, anyone using the explorer to understand a model's behaviour (cost per call, how close they are to a rate limit) has no visibility into it — they have to write custom code against the API instead, which defeats the purpose of the explorer. This is the direct analogue of #3549175 ("Add metadata to VectorDBGenerator in AI-API-Explorer"), but for the chat operation type. For now the actual styling is less important, we just need the values stored. ## Workaround None within the explorer UI. The information is only obtainable today by calling the chat API directly in custom code and reading the `ChatOutput` DTO. ## Affected modules / components - `ai_api_explorer` — `src/Plugin/AiApiExplorer/ChatGenerator.php` ### Notes / open questions - **Streamed responses:** in the streamed branch the normalized value is a `StreamedChatMessageIteratorInterface` and `ChatOutput::toArray()` returns `[]` for streams, so token usage / rate limits may not be available the same way. Suggest scoping this issue to the non-streamed path first and handling streaming separately if/where the data is available. <details> <summary>Contextual information for getting started</summary> ## Context The **AI API Explorer** (`ai_api_explorer`) is a development-only tool at `/admin/config/ai/explorers` that lets a site builder run an API call against a configured provider/model and see the response in the form. The change touches the **Chat Generation Explorer** (`src/Plugin/AiApiExplorer/ChatGenerator.php`). Today `ChatGenerator::getResponse()` immediately chains `->getNormalized()` on the provider's `chat()` call, which returns only the `ChatMessage` and throws away the surrounding `ChatOutput` — so the token-usage and rate-limit metadata it carries is lost. The fix keeps a reference to the full `ChatOutput`, then renders a new details section (like the existing "Tools Output" one) showing **token usage** (`$output->getTokenUsage()`) and, when present, **rate limits** (`$output->getRateLimits()`, hidden when `->empty()` is `TRUE`). Scope is the **non-streamed** path only. ## Why does this matter Anyone using the explorer to understand a model's behaviour — cost per call, or how close they are to a provider rate limit — currently has zero visibility into it from the UI, and has to write custom code against the API just to read the `ChatOutput` DTO, which defeats the point of the explorer. ## How to test Preconditions: an AI provider and a Chat model are configured. The **observation point** is the rendered explorer response form itself — the new details section is the thing you're verifying, so a provider that actually returns token usage (e.g. OpenAI) makes the difference visible immediately. Rate limits only appear for providers that return them; if yours doesn't, that section should simply be absent (not an empty/garbled box). 1. **Reproduce:** on your MR branch but with the change reverted (or `git stash`), go to `/admin/config/ai/explorers` → **Chat Generation**, pick a provider and chat model, enter a prompt (e.g. `Say hello in one sentence.`), submit, and confirm the response shows the message text but **no** token-usage section. 2. **Apply the change** and reload the form. 3. **Re-check:** submit the same prompt. Confirm a new details section now shows **Token usage** — input / output / total, plus reasoning and cached *only when the provider returns them* (nullable fields should be skipped, not shown as blank/0). If the provider exposes limits, a **Rate limits** section appears with the populated `rateLimit*` rows; if not, that section is hidden. 4. **Regression check:** confirm the message text and any existing "Tools Output" section still render as before, and that a **streamed** response (streaming toggled on) still works without errors — token usage may be unavailable there, which is expected and out of scope (`ChatOutput::toArray()` returns `[]` for streams). Further reading: [AI API Explorer module docs](https://git.drupalcode.org/project/ai/-/blob/1.0.x/docs/modules/ai_api_explorer/index.md). ## Acceptance After the change, a non-streamed chat run in the explorer surfaces the token usage (and rate limits where the provider returns them) that was previously discarded, with only populated rows rendered. The message text, code examples, and tools output must remain unchanged, and the streamed path must continue to work without errors even though the metadata isn't shown there. Worth flagging in the MR: streaming metadata is deliberately deferred — confirm that's an accepted scope boundary for this issue. </details>
issue