v0.40.0
lm-sys/RouteLLMv0.40.0Jul 11, 2026by github-actions[bot]
AI Summary
This release introduces a major overhaul of the hook system to version 2, making it composable middleware. It also includes breaking changes to tool execution, streaming, and provider architecture, alongside new features for structured output and video message support.
Key Highlights
- Hook system v2 with composable middleware
- Structured tool-execution results
- Provider consolidation (removing galadriel provider)
- Streaming overhaul to surface unmodeled provider output items
- Video helper constructors added
Breaking Changes
- structured tool-execution results
- hook system v2 — composable middleware
- surface unmodeled provider output items through the stream
- integrate hooks into AgentRun via a composable AgentRunner
- unify PromptResponse and FinalResponse into one type
- API paper cuts — duplicate names, hand-copied setters, dead types
- remove galadriel provider
- collapse remaining providers onto GenericCompletionModel
- migrate llamafile onto GenericCompletionModel
- delete unused evals module and experimental feature flag
New Features
- human-in-the-loop tool-call approval
- steer the model request per turn from a hook via Flow::OverrideRequest
- rewrite tool results from a hook via Flow::RewriteResult
- rewrite tool-call arguments from a hook via Flow::RewriteArgs
- preserve responses prompt cache parameters
- add video helper constructors + OpenRouter audio/video conversion tests
- add OutputMode to compose structured output with tools
Full Release Notes
### Added
- *(tool)* [**breaking**] structured tool-execution results ([#2015](https://github.com/0xPlaygrounds/rig/pull/2015)) (by @gold-silver-copper)
- *(agent)* [**breaking**] hook system v2 — composable middleware ([#2012](https://github.com/0xPlaygrounds/rig/pull/2012)) (by @gold-silver-copper)
- *(examples)* human-in-the-loop tool-call approval — examples + tests ([#1967](https://github.com/0xPlaygrounds/rig/pull/1967)) (by @gold-silver-copper)
- *(rig-core)* steer the model request per turn from a hook via Flow::OverrideRequest ([#1966](https://github.com/0xPlaygrounds/rig/pull/1966)) (by @gold-silver-copper)
- *(rig-core)* rewrite tool results from a hook via Flow::RewriteResult ([#1965](https://github.com/0xPlaygrounds/rig/pull/1965)) (by @gold-silver-copper)
- *(rig-core)* rewrite tool-call arguments from a hook via Flow::RewriteArgs ([#1963](https://github.com/0xPlaygrounds/rig/pull/1963)) (by @gold-silver-copper)
- *(openai)* preserve responses prompt cache parameters ([#1830](https://github.com/0xPlaygrounds/rig/pull/1830)) (by @Kade-Powell)
- *(streaming)* [**breaking**] surface unmodeled provider output items through the stream ([#1951](https://github.com/0xPlaygrounds/rig/pull/1951)) (by @gold-silver-copper)
- *(rig-core)* [**breaking**] integrate hooks into AgentRun via a composable AgentRunner ([#1945](https://github.com/0xPlaygrounds/rig/pull/1945)) (by @gold-silver-copper)
- *(message)* add video helper constructors + OpenRouter audio/video conversion tests ([#1942](https://github.com/0xPlaygrounds/rig/pull/1942)) (by @gold-silver-copper)
- *(agent)* add OutputMode to compose structured output with tools ([#1928](https://github.com/0xPlaygrounds/rig/pull/1928)) ([#1929](https://github.com/0xPlaygrounds/rig/pull/1929)) (by @gold-silver-copper)
### Fixed
- *(telemetry)* keep GenAI message span fields empty ([#2066](https://github.com/0xPlaygrounds/rig/pull/2066)) (by @gold-silver-copper)
- *(chatgpt)* preserve non-success response errors ([#2053](https://github.com/0xPlaygrounds/rig/pull/2053)) (by @gold-silver-copper)
- *(vertexai)* preserve signed thought text parts ([#2052](https://github.com/0xPlaygrounds/rig/pull/2052)) (by @gold-silver-copper)
- *(chatgpt)* fallback on empty SSE output ([#2001](https://github.com/0xPlaygrounds/rig/pull/2001)) (by @gold-silver-copper)
- *(openai)* preserve reasoning text content ([#1999](https://github.com/0xPlaygrounds/rig/pull/1999)) (by @gold-silver-copper)
- preserve OpenAI Responses instructions ([#1995](https://github.com/0xPlaygrounds/rig/pull/1995)) (by @gold-silver-copper) - #1995
- *(openai)* accept null Responses metadata ([#1993](https://github.com/0xPlaygrounds/rig/pull/1993)) (by @gold-silver-copper)
- *(postgres)* update sqlx and pgvector ([#1992](https://github.com/0xPlaygrounds/rig/pull/1992)) (by @gold-silver-copper)
- *(openai)* make Responses API strict tools opt-in ([#1991](https://github.com/0xPlaygrounds/rig/pull/1991)) (by @gold-silver-copper)
- *(agent)* stream concurrent tool results as they complete ([#1981](https://github.com/0xPlaygrounds/rig/pull/1981)) (by @gold-silver-copper)
- *(rig-core)* fix epub loader tests + prevent CWD-relative fixture-path regressions ([#1940](https://github.com/0xPlaygrounds/rig/pull/1940)) (by @gold-silver-copper)
- *(ollama)* preserve assistant reasoning from non-streaming responses ([#1926](https://github.com/0xPlaygrounds/rig/pull/1926)) ([#1927](https://github.com/0xPlaygrounds/rig/pull/1927)) (by @gold-silver-copper)
### Other
- Remove unused derive and core APIs ([#2087](https://github.com/0xPlaygrounds/rig/pull/2087)) (by @gold-silver-copper) - #2087
- add Bedrock cassette coverage ([#2084](https://github.com/0xPlaygrounds/rig/pull/2084)) (by @gold-silver-copper) - #2084
- Remove unused stream completion stdout helper ([#2085](https://github.com/0xPlaygrounds/rig/pull/2085)) (by @gold-silver-copper) - #2085
- Remove unused generation wrapper traits ([#2083](https://github.com/0xPlaygrounds/rig/pull/2083)) (by @gold-silver-copper) - #2083
- Remove unused Anthropic decoders ([#2082](https://github.com/0xPlaygrounds/rig/pull/2082)) (by @gold-silver-copper) - #2082
- *(agent)* [**breaking**] unify PromptResponse and FinalResponse into one type ([#2056](https://github.com/0xPlaygrounds/rig/pull/2056)) (by @gold-silver-copper)
- *(core)* [**breaking**] API paper cuts — duplicate names, hand-copied setters, dead types ([#2055](https://github.com/0xPlaygrounds/rig/pull/2055)) (by @gold-silver-copper)
- *(examples)* add force_tool_first_turn hook example ([#2014](https://github.com/0xPlaygrounds/rig/pull/2014)) (by @gold-silver-copper)
- *(auth)* add non-interactive oauth cassette coverage ([#2050](https://github.com/0xPlaygrounds/rig/pull/2050)) (by @gold-silver-copper)
- *(perplexity)* add cassette coverage ([#2049](https://github.com/0xPlaygrounds/rig/pull/2049)) (by @gold-silver-copper)
- *(providers)* [**breaking**] remove galadriel provider ([#2041](https://github.com/0xPlaygrounds/rig/pull/2041)) (by @gold-silver-copper)
- *(providers)* [**breaking**] collapse remaining providers onto GenericCompletionModel<Ext> (#2035 phases 2–4) ([#2040](https://github.com/0xPlaygrounds/rig/pull/2040)) (by @gold-silver-copper)
- *(providers)* [**breaking**] migrate llamafile onto GenericCompletionModel<Ext> (#2035 phase 1) ([#2038](https://github.com/0xPlaygrounds/rig/pull/2038)) (by @gold-silver-copper)
- *(core)* [**breaking**] delete unused evals module and experimental feature flag ([#2036](https://github.com/0xPlaygrounds/rig/pull/2036)) (by @gold-silver-copper)
- Flatten Tool metadata API ([#2029](https://github.com/0xPlaygrounds/rig/pull/2029)) (by @gold-silver-copper) - #2029
- *(gemini)* live cassette hook-system stress suite ([#2013](https://github.com/0xPlaygrounds/rig/pull/2013)) (by @gold-silver-copper)
- Add Groq agent tool cassette regressions ([#2011](https://github.com/0xPlaygrounds/rig/pull/2011)) (by @gold-silver-copper) - #2011
- Add Mistral agent tool cassette regressions ([#2010](https://github.com/0xPlaygrounds/rig/pull/2010)) (by @gold-silver-copper) - #2010
- Add DeepSeek agent tool cassette regressions ([#2009](https://github.com/0xPlaygrounds/rig/pull/2009)) (by @gold-silver-copper) - #2009
- Add xAI agent tool cassette regressions ([#2008](https://github.com/0xPlaygrounds/rig/pull/2008)) (by @gold-silver-copper) - #2008
- Gate Gemini image cassette tests on image feature ([#2007](https://github.com/0xPlaygrounds/rig/pull/2007)) (by @gold-silver-copper) - #2007
- Add OpenRouter agent tool cassette regressions ([#2006](https://github.com/0xPlaygrounds/rig/pull/2006)) (by @gold-silver-copper) - #2006
- Add ChatGPT Codex cassette regression suite ([#2005](https://github.com/0xPlaygrounds/rig/pull/2005)) (by @gold-silver-copper) - #2005
- *(gemini)* production-grade generateContent cassette suite ([#2004](https://github.com/0xPlaygrounds/rig/pull/2004)) (by @gold-silver-copper)
- *(anthropic)* production-grade Messages API cassette suite ([#2003](https://github.com/0xPlaygrounds/rig/pull/2003)) (by @gold-silver-copper)
- *(openai)* production-grade Responses API cassette suite + tool_choice and replay-ID fixes ([#2002](https://github.com/0xPlaygrounds/rig/pull/2002)) (by @gold-silver-copper)
- *(providers)* add provider implementation checklist ([#1997](https://github.com/0xPlaygrounds/rig/pull/1997)) (by @gold-silver-copper)
- *(deps)* bump assert_fs from 1.1.3 to 1.1.4 ([#1933](https://github.com/0xPlaygrounds/rig/pull/1933)) (by @dependabot[bot])
- *(deps)* bump trybuild from 1.0.116 to 1.0.117 ([#1935](https://github.com/0xPlaygrounds/rig/pull/1935)) (by @dependabot[bot])
- *(deps)* bump chrono from 0.4.44 to 0.4.45 ([#1934](https://github.com/0xPlaygrounds/rig/pull/1934)) (by @dependabot[bot])
- *(deps)* bump uuid from 1.23.3 to 1.23.4 ([#1975](https://github.com/0xPlaygrounds/rig/pull/1975)) (by @dependabot[bot])
- *(deps)* bump scylla from 1.6.0 to 1.7.0 ([#1932](https://github.com/0xPlaygrounds/rig/pull/1932)) (by @dependabot[bot])
- *(anthropic)* add null citation streaming cassette ([#1978](https://github.com/0xPlaygrounds/rig/pull/1978)) (by @gold-silver-copper)
- update agent and contribution guidance ([#1974](https://github.com/0xPlaygrounds/rig/pull/1974)) (by @gold-silver-copper) - #1974
- *(openai-compat)* genuinely exercise the #1958 tool-call eviction string-leak (+ live cassette) ([#1962](https://github.com/0xPlaygrounds/rig/pull/1962)) (by @gold-silver-copper)
- *(rig-core)* [**breaking**] remove the experimental pipeline module ([#1941](https://github.com/0xPlaygrounds/rig/pull/1941)) (by @gold-silver-copper)
- run doctests and stop rig-sqlite opting out of them ([#1939](https://github.com/0xPlaygrounds/rig/pull/1939)) (by @gold-silver-copper) - #1939
- *(rig-core)* replace nanoid with fastrand for internal IDs ([#1938](https://github.com/0xPlaygrounds/rig/pull/1938)) (by @gold-silver-copper)
- *(examples)* migrate to a package-per-example layout ([#1937](https://github.com/0xPlaygrounds/rig/pull/1937)) (by @gold-silver-copper)
- add Archestra to "Who is using Rig?" section ([#1925](https://github.com/0xPlaygrounds/rig/pull/1925)) (by @arsenyinfo) - #1925
### Contributors
* @gold-silver-copper
* @dependabot[bot]
* @Kade-Powell
* @arsenyinfo
### Changed
- *(agent)* [**breaking**] `max_turns` and `default_max_turns` now bound the exact total number of model calls, including the initial call, tool continuations, and retries. A budget of `0` makes no model call, while `1` permits only the initial call. Unconfigured tool-then-answer flows now need an explicit total budget of `2`. To preserve the former maximum allowance of an explicit old budget `n`, account for the old effective `n + 2` calls; otherwise, set the intended literal total.
- *(tool)* [**breaking**] flatten `Tool` / `ToolDyn` metadata: tool authors now implement `description()` and `parameters()` directly, and `Tool::definition(prompt)` / `ToolDyn::definition(prompt)` are removed. `ToolDefinition` remains a provider/request artifact generated from registered tools, with `Tool::NAME` / `Tool::name()` / `ToolDyn::name()` as the single source of truth for advertised and dispatched tool names.
- *(providers)* [**breaking**] migrate `llamafile` onto the shared `GenericCompletionModel<Ext>` / `GenericEmbeddingModel<Ext>` path, deleting its hand-rolled completion model, request types, message flattening, and streaming profile. `llamafile::CompletionModel` / `llamafile::EmbeddingModel` are now type aliases for the generic models; the provider-specific `StreamingCompletionResponse` type is replaced by the shared OpenAI one. Requests now serialize messages in the shared OpenAI shape (single-text user content still flattens to a string; system/multi-part content is sent as a content-part array, which llama.cpp-family servers accept).
- *(openai)* [**breaking**] new `OpenAICompatibleProvider` trait (mirroring `AnthropicCompatibleProvider`) is now required by `GenericCompletionModel`'s `Ext` parameter; it carries the telemetry provider name (so minimax/zai/xiaomimimo spans stop reporting as "openai") and an `EMITS_COMPLETE_SINGLE_CHUNK_TOOL_CALLS` flag for llama.cpp-style streaming tool calls.
- *(providers)* [**breaking**] migrate the remaining OpenAI-chat-compatible providers onto `GenericCompletionModel<Ext>` — groq, deepseek, mistral, together, moonshot (OpenAI side), perplexity, hyperbolic, mira, azure, and huggingface all lose their hand-rolled `CompletionModel` structs, request types, and `TryFrom<message::Message>` conversions; `CompletionModel` in each module is now a type alias for the generic model. Provider wire dialects live in `OpenAICompatibleProvider` hooks: an associated `Response` type, `completion_path` (Azure deployment URLs, `/v1`-prefixed routes), `prepare_request` (Groq native-tool folding, Moonshot `required` tool-choice coercion, HuggingFace Fireworks model ids, Perplexity/Mira tool stripping), `finalize_request_body` (DeepSeek string content + thinking-gated tool choice, Mistral `"any"` tool choice + `prefix` field + reasoning stripping, Mira raw-message flattening), and `SUPPORTS_RESPONSE_FORMAT` / `STREAM_INCLUDE_USAGE` consts. Provider-specific `StreamingCompletionResponse` types are replaced by the shared OpenAI one.
- *(openai)* [**breaking**] `ToolChoice` gains a `Function { name }` variant serializing OpenAI's `{"type":"function","function":{"name":...}}` form, so `message::ToolChoice::Specific` with one function is now supported instead of erroring; `CompletionRequest` fields are now public; `OpenAIRequestParams` gains a `supports_response_format` field.
- *(openai)* the shared `TryFrom<message::ToolResult>` conversion now prefers `call_id` over `id` for `tool_call_id` (matching provider-issued call ids); the shared streaming delta accepts `reasoning` as an alias for `reasoning_content` (Groq), and the deprecated `function_call` finish reason maps to tool-call handling.
- *(providers)* behavior notes from the migration: `max_tokens` is now forwarded by deepseek, together, hyperbolic, and azure (previously silently dropped); together's streaming request uses standard `stream`/`stream_options` instead of `stream_tokens`, and a rig-level `ToolChoice::Required` now serializes as `required` instead of erroring; perplexity's non-streaming endpoint drops its stray `/v1` prefix (matching its streaming path and the real API); mira's preamble is sent as a `system` message instead of `user`; `response_format` derived from `output_schema` is deferred while tools are pending a result (groq/mistral/azure previously applied it unconditionally); groq's streaming usage no longer falls back to the legacy `x_groq.usage` envelope.
- *(openrouter)* [**breaking**] de-fork OpenRouter's parallel message model (issue #2035 phase 4): `openrouter::{Message, UserContent, ImageUrl}` are now re-exports of the shared OpenAI types, and the fork's `FileContent`/`VideoUrlContent` are replaced by shared `FileData`/`VideoUrl`. To support this, the shared OpenAI types gain OpenRouter's optional extensions — `UserContent::Video`, `ImageUrl.detail` becomes `Option<ImageDetail>` (OpenAI still sends `"detail":"auto"`), and `Message::Assistant` gains a skip-when-empty `reasoning_details` field, an inbound-only `images` field (never serialized back into requests), plus a deserialize-only `role: "model"` alias. `ReasoningDetails`/`ResponseImage` move into the openai module (re-exported from openrouter). OpenRouter-specific message conversion now goes through `openrouter::messages_from_rig_message`; `TryInto<Vec<openrouter::Message>>` resolves to the plain shared conversion. OpenRouter keeps its own request/response/streaming layer (provider preferences, cost accounting, reasoning-details grouping, generated-image extraction) as a documented exception.
- *(openai)* the `UserContent` audio part now serializes its tag as `input_audio` (matching OpenAI's actual API); `audio` is still accepted when deserializing.
- *(openai)* `StreamingCompletionResponse` is now generic over the provider's streaming usage payload (`StreamingCompletionResponse<U = Usage>`, selected via `OpenAICompatibleProvider::StreamingUsage`), so Mistral's cached-token fallbacks and DeepSeek's cache hit/miss counters survive streaming instead of being narrowed to OpenAI's usage shape.
- *(providers)* pre-migration request filtering is preserved where provider support is unverified: hyperbolic still drops `tools`/`tool_choice`/`output_schema` with warnings, and perplexity flattens text-only message content back to plain strings (mixed multimodal content is passed through for sonar models). llamafile keeps the current mapping of `output_schema` to a `json_schema` response format (as on the shared path since the llamafile migration; modern llama.cpp servers support it).
- *(llamafile)* the chat cassettes are now recorded against an actual llama.cpp `llama-server`, confirming the shared OpenAI wire shape (content-part arrays, tool calls, tool results) against the real llamafile-family server rather than an OpenAI-compatible proxy.
- *(openai)* the assistant tool-call echo now serializes `call_id` (falling back to `id`) so it stays consistent with the tool-result side when history recorded via the Responses API is replayed through chat completions; streaming delta `content` tolerates content-part arrays (Mistral reasoning models) instead of dropping the chunk.
- *(providers)* review fixes: mira and perplexity no longer send `stream_options` (their APIs never received it pre-migration); moonshot rejects a specific forced tool client-side again; openrouter serializes plain assistant reasoning under its documented `reasoning` key; azure telemetry spans report `azure.openai` again; mira usage math saturates instead of overflowing; perplexity strips tool-exchange remnants from shared histories.
- *(providers)* second review round: openrouter tool-result messages prefer the provider-issued `call_id` (matching the assistant echo side); Azure's deployment URL stays pinned to the model the handle was created with (a per-request `model` override only changes the body, as pre-migration); shared streaming spans record `gen_ai.system_instructions` again; providers without tool support (perplexity, mira, and now hyperbolic) sanitize tool-exchange remnants from shared histories via one shared helper that also preserves strict role alternation (tool-call-only assistant turns are dropped and consecutive assistant turns merged); openrouter's dead pre-migration `ToolChoice` type is removed, and `ToolChoice::Specific` with multiple function names now errors client-side for openrouter (the old fork serialized a non-standard array).
- *(moonshot)* [**breaking**] reasoning-only assistant history turns are no longer preserved: the shared conversion drops assistant messages with neither text nor tool calls. Reasoning attached to text or tool-call turns still round-trips via `reasoning_content`.
- *(providers)* [**breaking**] responses with empty assistant content and no tool calls now surface the shared path's "empty response" error for hyperbolic, perplexity, and huggingface (previously they returned an empty text completion).
- *(providers)* [**breaking**] additional removed public items: the raw response types of perplexity, hyperbolic, and huggingface (each module keeps a `CompletionResponse` alias to the shared OpenAI payload; `Message`/`Choice`/`Usage`/`Delta`/`Role` companions are gone), `together::ToolChoice`/`ToolChoiceFunctionKind`, `moonshot::ToolChoice`, `groq::send_compatible_streaming_request` and `deepseek::send_compatible_streaming_request` (use `openai::send_compatible_streaming_request`), and openrouter's `UserContent` builder helpers (`image_url`, `file_base64`, `video_url`, ...) — construct the shared `openai` content variants directly.
- *(openai)* [**breaking**] sending rig `Video` user content to providers on the shared conversion now serializes a `video_url` content part (an OpenRouter/gateway extension) instead of returning a client-side conversion error; providers without video support will reject it server-side.
- *(providers)* [**breaking**] telemetry: migrated providers' streaming spans are now named `chat` with `gen_ai.operation.name = "chat"` (previously `chat_streaming`). GenAI message-content span fields (`gen_ai.input.messages` / `gen_ai.output.messages`) are intentionally left empty instead of recording serialized request/response messages, preserving the privacy/cardinality behavior from #2065; the public `SpanCombinator::record_model_output` helper is removed. `gen_ai.request.model` reports the per-request model override when one applies.
- *(providers)* third review round: history sanitization treats `refusal` parts as text when flattening and, for alternation-strict perplexity, merges consecutive same-role turns (dropping a tool exchange could previously leave `user/user` adjacency its API rejects); streaming no longer overwrites caller-supplied `stream_options`; openrouter's encrypted reasoning details now correlate with the wire tool-call id and its non-streaming usage uses the reported `completion_tokens` (no underflow); base64 videos with unrecognized MIME types round-trip as data-URI URLs instead of failing conversion.
- *(providers)* fourth review round — agent structured output: `GenericCompletionModel` no longer claims native structured output composes with tools for every provider; it now follows `SUPPORTS_RESPONSE_FORMAT`. Agents with tools plus an output schema on deepseek/together/moonshot/huggingface/hyperbolic/perplexity/mira fall back to tool-mode schema enforcement as their pre-migration models did (the migration had silently dropped the schema entirely); groq/mistral/azure now compose natively like openai.
- *(openai)* new `OpenAICompatibleProvider::SUPPORTS_TOOLS` const (default true): perplexity, hyperbolic, and mira set it false and `tools`/`tool_choice` are dropped with a warning during request conversion — before tool-choice validation, so a multi-name `ToolChoice::Specific` no longer errors client-side on providers that ignored it pre-migration.
- *(openai)* streaming robustness: `include_usage` is inserted into caller-supplied `stream_options` instead of being skipped (or clobbering the caller's keys, the pre-migration behavior); a delta carrying both `reasoning_content` and `reasoning` no longer fails as a serde duplicate-field error that dropped the whole chunk; streaming tool-call `index` defaults to 0 when omitted (Mistral marks it optional); `CompletionResponse.object`/`created` are defaulted on deserialization for gateways that omit them (HuggingFace router sub-providers).
- *(openrouter)* non-streaming usage falls back to `total - prompt` (saturating) when the gateway omits `completion_tokens`; streaming spans follow the shared telemetry behavior of leaving GenAI message-content fields empty.
- *(openai)* [**breaking**] `ToolChoice` is now `#[non_exhaustive]`; `GenericCompletionModel`'s `strict_tools`/`tool_result_array_content` fields are private (use the `with_*` builder methods) and the redundant `with_model` constructor is removed (use `new`).
### Removed
- *(derive)* [**breaking**] remove the unused public `rig_derive::ProviderClient` derive macro and its `deluxe` dependency; `Embed` and `rig_tool` are unchanged, and no replacement is provided.
- *(core)* [**breaking**] remove unused `Extractor::{get_inner, into_inner}` and the always-failing `TryFrom<String> for Nothing`; no direct replacements are provided.
- *(core)* [**breaking**] remove the unused public `streaming::stream_completion_to_stdout` helper; use the high-level `agent::stream_to_stdout` helper instead.
- *(core)* [**breaking**] remove the unused public `AudioGeneration<M>`, `ImageGeneration<M>`, and `Transcription<M>` wrapper traits; use the corresponding `AudioGenerationModel`, `ImageGenerationModel`, and `TranscriptionModel` APIs and request builders directly.
- *(core)* [**breaking**] remove the unused `evals` module (`Eval` trait, judge metrics, and builders) along with the `experimental` feature flag that gated it
- *(anthropic)* [**breaking**] remove the unused public `providers::anthropic::decoders` module; Anthropic streaming uses the shared SSE machinery.
- *(providers)* [**breaking**] remove the Galadriel provider integration (`providers::galadriel`), including its client, model constants, environment-variable support, and ignored live tests.