Commit graph

55363 commits

Author SHA1 Message Date
shrey
2f84296ad2
fix(azure): map context_length_exceeded error code to ContextWindowExceededError (#48616) 2026-10-28 17:43:27 +00:00
shrey
0f49db5c3b
fix(proxy): store boolean settings as JSON booleans on /config/field/update (#48595) 2026-10-28 17:39:01 +00:00
moe-berri
12deb60e27
fix(responses): forward top-level moderation param to OpenAI (#48708)
* test: cover Responses moderation forwarding

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(responses): forward moderation parameter

Co-authored-by: Poppy Huang <ruihong_huang@foxmail.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test: remove Perplexity drop_params moderation case

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(responses): preserve typed moderation forwarding

Co-authored-by: Poppy Huang <ruihong_huang@foxmail.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Poppy Huang <ruihong_huang@foxmail.com>
2026-10-28 10:33:11 -07:00
moe-berri
9da926b90f
fix(ui): show reasoning in the Playground for /v1/responses (#48850)
* test(ui): cover Responses API reasoning events in the playground

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(ui): show reasoning for current Responses API reasoning events

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-28 10:16:52 -07:00
moe-berri
fd9a37d233
fix(ui): match simple auto router models to advanced setup (#48851) 2026-10-28 10:16:37 -07:00
moe-berri
471918c30b
fix(refuseless): update GLM 5.3 and DeepSeek V4.1 Flash prices (#48844)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-28 10:08:24 -07:00
tin-berri
97a13ce676
fix(router): isolate child tier pins in user turn mode (#48701) 2026-10-28 10:00:48 -07:00
moe-berri
3d7b94b34f
fix(ollama): forward embedding credentials, headers and timeout (#48707)
* test(integration): cover ollama embedding key, header and timeout forwarding

* fix(ollama): forward api_key, headers and timeout on native embeddings

Co-authored-by: Mrun25 <165297479+Mrun25@users.noreply.github.com>

* test(ollama): disable fixture key for keyless proxy guard

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Mrun25 <165297479+Mrun25@users.noreply.github.com>
2026-10-28 09:32:47 -07:00
moe-berri
4ce2602548
fix(huggingface): stop forcing truncate=false on TEI rerank and pass truncate params through (#48706)
* test(huggingface): cover TEI rerank truncate passthrough and server default

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(huggingface): stop forcing truncate=false on TEI rerank and pass truncate params through

Co-authored-by: Luke Schaefer <225965040+luke-e-schaefer@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(huggingface): keep TEI truncate options provider-specific

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(huggingface): use MockTransport for rerank truncation

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Luke Schaefer <225965040+luke-e-schaefer@users.noreply.github.com>
2026-10-28 09:27:26 -07:00
yujonglee
323d3956d6
feat(rust): one typed decline from core up to each host (#47772)
* feat(rust): one typed decline from core up to each host

Rust core says it cannot serve a request with one Decline that travels
unchanged through llms and inference up to the host. The Python bridge
raises RustBridgeDeclined for it in route_failure before the public
exception mapping, so dispatch falls back to legacy Python instead of
failing the call, and the gateway answers 501. Every route collapses
errors after the send with Error::after_send, which keeps a provider
answer and a response fault as they are and nests anything else as the
source of an invalid response, so a billed call can never read as a
decline. Response-side cases that used to decline are now
ErrorDetail::Unhandled response faults.

The rust-error skill documents declined() as the first question both
boundaries ask, and the Python suites pin that a native decline reaches
the dispatcher before any provider IO.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(rust): keep post-provider hook failures terminal

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-28 09:22:11 -07:00
yujonglee
7b60fd061a
refactor(rust): adopt one error discipline across litellm-rust with source chains and Report (#48042)
* build(rust): upgrade reqwest to 0.13 and move MCP out of litellm-http

The workspace moves to reqwest 0.13, so litellm-http no longer carries a
second reqwest just for rmcp. The mcp feature, its pool and its proxy
conversion go away, and the gateway hands rmcp a pooled NoRedirect client
that already carries the TLS, proxy and timeout settings

The Client test helpers collapse into for_test, a no-redirect client
without TLS or proxy settings, and wrap_for_test for the few tests that
need a hand-built reqwest client

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(http): honor transport settings for Rust WebSockets

The Responses WebSocket connected through tokio-tungstenite with its own
native-roots TLS, so it ignored the configured CA bundle, verify flag,
client certificate, proxies and timeouts. The handshake now goes through
a pooled reqwest client (ClientVariant::WebSocket, HTTP/1.1, no
redirects) and upgrades the response, so it shares the settings every
other outbound request uses

The upgrade response is checked for the Sec-WebSocket-Accept key, the
Upgrade and Connection headers and a subprotocol that was offered

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(http): build outbound requests as reqwest::Request

OutboundRequest was a hand-rolled copy of reqwest::Request. Routes now build
a reqwest::Request directly and RequestSigner reads the body bytes back out
of it, so the signer still covers exactly what is sent. SigV4 signing moves
onto HeaderMap and takes the method, which drops the BTreeMap plumbing and
the header casing table

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(http): make litellm_http::Error the one opaque error

transport::Error and media::Error fold into litellm_http::Error, an
Arc<Repr> newtype whose private variants keep the typed reqwest, io,
rustls and tungstenite causes and own every message. Callers can no
longer build an error with a message of their own. They ask through
dispatched, is_request, status, upstream_response and tls_source

One From<reqwest::Error> now classifies connect, network and timeout
and strips the URL. The WebSocket handshake reports each failed check
as its own variant and owns its connect timeout. OCR drops the media
variants it mirrored, and inference::Error maps a header the signer
computes to Unsupported for every route, not only chat

The error rules move out of AGENTS.md into the rust-error skill, which
also records the decisions behind this design

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(rust): split rust-error skill into thin SKILL.md with references

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(rust-error): render causes once and ask predicates at the boundaries

The four WebSocket handshake variants collapse into Handshake(HandshakeFault), and media download failures no longer answer dispatched() since they happen before the provider call

ErrorDetail in llms and auth-types, the inference error and the OCR error now store their cause in a field named cause with no #[source], so to_string() carries it exactly once and source() is None. Tests that walked the chain now destructure the variant and downcast the cause

The gateway asks is_unsupported() and is_missing_credential() on the inference and OCR errors instead of matching through nested enums, and litellm_auth::Error answers is_missing_credential() for both

The rust-error skill gains axum and pyo3 references, boundaries.md describes the real predicate order and the known divergences, the testing example asserts dispatched() correctly, and shape, classification, conversions, messages-and-causes and recipes agree with the code again

* docs(rust): focus the rust-error gateway page on the error flow and add a rust-gateway skill

The former axum page toured gateway-inference: its status match, the envelope
table, an extractor, the unsupported route and the Sse closure. Those details
belong to the gateway crates and go stale with them. The page is now gateway.md
and follows an error from the provider failure through each From into
IntoResponse, where it is classified once and rendered by surface and framing.

The gateway-structure material moves to a new rust-gateway skill: envelopes per
API surface, extractors and path registration, SSE relay, and the router test
harness.

* test(inference): pin provider URL redaction on every route's transport error

Chat, Messages, Responses and transcription each get a route-level test that
fails if a reqwest error reaches the caller with its URL. The Messages stream
decoder also takes body errors through litellm_http::Error like every other
reqwest error

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(rust-error): adopt std source chains and add litellm-error Report

Messages describe their own layer and the cause is the source, boundaries
render the chain once with litellm_error::Report. Corrects skill claims
the audit found stale against the code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(rust-error): keep causes as sources across http, auth, secrets, cache and utility crates

Messages describe their own layer and every cause is a typed source,
predicates match exhaustively, and boundaries render with
litellm_error::Report. Media downloads no longer count as dispatched,
Error::upstream bounds the provider body itself, and a forwarded
header SigV4 cannot sign is the caller's fault.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(rust-error): keep causes as sources in llms and the gateway crates

ErrorDetail loses its string variant, OCR provider variants become data,
host faults stop counting as the caller's, a tunnelled transport error
classifies the same on every stream path, and each gateway crate has one
Error with one status() and extractors that reject in its envelope.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(rust-error): nest llms errors in inference and ask predicates in route tests

litellm_inference::Error nests litellm_llms::Error instead of mirroring
its variants, host driver faults get their own variant, every predicate
matches exhaustively, and route tests ask predicates instead of matching
transport variants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(rust-error): map bridge and gateway-inference errors once and render them with Report

RustUpstreamError has one constructor that always sets headers, argument
checks raise typed errors mapped once, OCR asks upstream_response(), the
Responses stream error event comes from the error type, gateway-inference
loses its string variants, and auth faults classify through one
litellm_auth predicate on every route.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(rust-error): carry OCR provider headers as a HeaderMap and keep outages off the caller

The OCR provider error and get_error_class take reqwest's HeaderMap, the
bridge turns it into Python's (name, value) pairs once. Credential
acquisition and provider SDK failures are no longer the caller's fault,
the UI hides auth and backend causes, and chat keeps a provider answer
as is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(rust-error): correct skill claims against the aligned code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(rust-error): fix the remaining sweep findings in gateway, llms, auth and cache crates

Auth rejections hide their backend cause on every inference renderer,
path parameters reject in the surface envelope, Textract's unsupported
document and Azure DI's unparseable poll URL get typed variants, the
credential chain exposes its last failure as the source, gateway-mcp
asks predicates over rmcp errors, and the S3 and Azure blob transports
classify through litellm_http::Error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(rust-error): fix the remaining sweep findings on the Python side

host-python keeps a native error's chain and returns blocking-pool
cancellation instead of panicking, Vault transport failures convert
through litellm_http::Error so no URL reaches Python, the bridge asks
predicates instead of matching three levels into Vault errors, and the
last string-built PyErrs become typed errors mapped once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* build(deps): refresh compatible dependencies flagged by OSV

* refactor(rust): remove redundant client construction comment

* test(rust): cover configured MCP proxy and CA bundle

* fix(http): preserve native WebSocket trust and optional protocols

* fix(http): allow plain WebSockets without native roots

* fix(http): normalize provider URLs before building outbound requests

* fix(rust): preserve OCR download status and reduce error size

* fix(rust): preserve upstream bodies and bound diagnostics only

* test(rust): preserve bridge serialization boundary in body regression

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-28 09:22:11 -07:00
yujonglee
424e5c069c
refactor(http): build outbound requests as reqwest::Request (#48041)
* refactor(http): build outbound requests as reqwest::Request

OutboundRequest was a hand-rolled copy of reqwest::Request. Routes now build
a reqwest::Request directly and RequestSigner reads the body bytes back out
of it, so the signer still covers exactly what is sent. SigV4 signing moves
onto HeaderMap and takes the method, which drops the BTreeMap plumbing and
the header casing table

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(http): normalize provider URLs before building outbound requests

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-28 09:22:10 -07:00
moe-berri
ed5fe048ff
fix(proxy/auth): apply max_end_user_budget_id to end users without a row on cached keys (#48704)
* test(proxy/auth): cover default budgets on cached virtual keys

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(proxy/auth): use in-memory cache in cached budget tests

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(proxy/auth): apply max_end_user_budget_id to end users without a row on cached keys

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(proxy/auth): share cached end-user parameter update

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-28 09:19:36 -07:00
devin-ai-integration[bot]
3afe779125
fix(cost-map): rolling provider pricing, capability and retirement audit (#48188)
* fix(cost-map): registry audit 2026-10-24, add provider retirement dates and fix mistral-large-4 and azure_ai sonnet 5.5 prices

Co-authored-by: Hannes Brantner <desktop.stilted-9t@icloud.com>
Co-authored-by: Aasif-Simpplr <aasif.multani@simpplr.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(mistral): derive mistral-large-4 cost expectations from the registry row instead of pinned prices

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(cost-map): add fireworks spot routers and reprice together Qwen3.7-Max

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(cost-map): absorb open registry PRs and update azure_ai mistral-small-2503 retirement date

Co-authored-by: kerry <kerry@berri.ai>

Co-authored-by: Aryan <aryan.m@kelpglobal.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* chore(cost-map): credit absorbed registry PR authors

Co-authored-by: kerry <kerry@berri.ai>
Co-authored-by: Aryan <aryan.m@kelpglobal.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(cost-map): add azure gpt-6.1-terra rows and fireworks models/rune path

Co-authored-by: ceng-p <christianpetersen98@gmail.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(cost-map): add azure_ai gpt-6.1-terra global standard row

Co-authored-by: Ahmet Selim FEDAKAR <122402435+selimfedakar@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(cost-map): price mistral-large-4 cache reads at the published $0.14

Co-authored-by: zoveki <349028329+zoveki@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* revert(cost-map): leave mistral-large-4 rows to #48825

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(cost-map): refresh provider pricing and retirement metadata

Absorb the verified GPT-5.6 Terra and Luna price fields from #48709. Add official Groq caching, xAI batch pricing, Moonshot metadata, and retirement dates without replacing existing model entries.

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Hannes Brantner <desktop.stilted-9t@icloud.com>
Co-authored-by: Aasif-Simpplr <aasif.multani@simpplr.com>
Co-authored-by: Aryan <aryan.m@kelpglobal.com>
Co-authored-by: kerry <kerry@berri.ai>
Co-authored-by: ceng-p <christianpetersen98@gmail.com>
Co-authored-by: Ahmet Selim FEDAKAR <122402435+selimfedakar@users.noreply.github.com>
Co-authored-by: zoveki <349028329+zoveki@users.noreply.github.com>
2026-10-28 16:07:22 +00:00
ryan-crabbe-berri
db5b940143
fix(vertex_ai): send Vertex Claude labels as X-Vertex-AI-Labels header instead of the request body (#48753)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-28 15:41:50 +00:00
yujonglee
cfc651dcd5
fix(http): honor transport settings for Rust WebSockets (#47968)
* fix(http): honor transport settings for Rust WebSockets

The Responses WebSocket connected through tokio-tungstenite with its own
native-roots TLS, so it ignored the configured CA bundle, verify flag,
client certificate, proxies and timeouts. The handshake now goes through
a pooled reqwest client (ClientVariant::WebSocket, HTTP/1.1, no
redirects) and upgrades the response, so it shares the settings every
other outbound request uses

The upgrade response is checked for the Sec-WebSocket-Accept key, the
Upgrade and Connection headers and a subprotocol that was offered

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(http): preserve native WebSocket trust and optional protocols

* fix(http): allow plain WebSockets without native roots

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-28 07:32:41 -07:00
yujonglee
6c0585b8f1
build(rust): upgrade reqwest to 0.13 and move MCP out of litellm-http (#48039)
* build(rust): upgrade reqwest to 0.13 and move MCP out of litellm-http

The workspace moves to reqwest 0.13, so litellm-http no longer carries a
second reqwest just for rmcp. The mcp feature, its pool and its proxy
conversion go away, and the gateway hands rmcp a pooled NoRedirect client
that already carries the TLS, proxy and timeout settings

The Client test helpers collapse into for_test, a no-redirect client
without TLS or proxy settings, and wrap_for_test for the few tests that
need a hand-built reqwest client

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* build(deps): refresh compatible dependencies flagged by OSV

* refactor(rust): remove redundant client construction comment

* test(rust): cover configured MCP proxy and CA bundle

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-28 07:32:40 -07:00
zoveki
38b4d31447
test(scim): audit #47544 (#48791)
* test(scim): audit #47544

Add integration cells for the SCIM group delete that now runs through the team delete's bulk path: an admin group delete revokes its members' admin key, a delete sent with the IdP's SCIM token revokes member keys and audits them under that token, a delete that times out on a locked member row changes nothing so a retry works, and two racing team deletes write one tombstone and one team audit row

* test(scim): read the racing team delete's audit rows after the proxy drains them

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 06:08:30 -07:00
tavren27
356069d2ed
test(mcp): audit #47422 (#48821)
Adds a real-proxy case where the MCP follow-up call fails on every router retry, checking that the one failure row bills the first call of every attempt on chat completions and /v1/responses

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 06:02:40 -07:00
norkelbyte
3507781735
test(proxy): audit #47352 (#48804)
* test(proxy): audit #47352

* test(proxy): audit #47352 lazy load during a cold docs build

* test(proxy): route the overlapping chat cell to the rig upstream

* test(proxy): keep the root path cell from rewriting the packaged UI

* test(proxy): prove the lazy and chat cells overlap the cold docs build

* test(proxy): warm the chat path before the overlapping chat cell

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 05:54:40 -07:00
rj-cairn
295d79fc7d
feat(cli): keep the ChatGPT login with lite codex --subscription (#48741)
* feat(cli): keep the ChatGPT login with lite codex --subscription

`lite codex --subscription` points Codex at a `litellm_subscription` provider with requires_openai_auth on and no env_key, so Codex keeps its own ChatGPT login and the LiteLLM key is sent only in the x-litellm-api-key header from LITELLM_PROXY_API_KEY. OPENAI_API_KEY is left exactly as the user had it, and the pre-launch key check sends the key the same way

`codex_subscription` is a new `lite config` key that turns the flag on by default, like `claude_subscription` does for `lite claude`. The Claude `--settings` overlay now applies to Claude Code only

* fix(cli): leave the Codex Stop hook out of subscription launches and load tomli on Python 3.10

* fix(cli): leave OPENAI_BASE_URL as the user had it with lite codex --subscription

* fix(cli): give lite codex --subscription its own Codex provider id

* test(cli): type the Codex config override helpers

* fix(cli): leave the Codex model list to Codex in subscription mode

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 05:36:26 -07:00
Mateo Wang
0ceca31081
feat(proxy): per-provider and per-model rpm/tpm requirements for enforce_project_model_quota (#47693)
* feat(proxy): per-provider and per-model rpm/tpm requirements for enforce_project_model_quota

* test(proxy): cover per-provider and per-model enforce_project_model_quota policies

* fix(proxy): keep enforce_project_model_quota reads typed

* fix(proxy): apply the quota policy to model add, follow router wildcard rules, keep old setting values

* fix(proxy): keep team model names off quota model entries, read the provider behind api_base, match patterns like the router

A team admin picks a team model's public name, so a name matching an exempt model entry no longer skips its provider's limits. A mapping with no policy key keeps requiring both limits as before, the project create permission check runs before the setting is read, and an unprefixed model behind a known OpenAI-compatible api_base resolves to that provider

* fix(proxy): re-check team projects on team model writes and validate quota provider ids

* fix(proxy): check added limits on model updates and match team models by the model they call

* fix(proxy): re-check team projects by every name a team model serves and check renamed names

* fix(proxy): follow the router's order for tied team patterns and annotate quota tests

* fix(proxy): match a team model's called model through JSON provider prefixes too

* fix(proxy): keep model updates that keep their route as on main

A PATCH or POST model update now checks only the limits its new route adds over the stored one, so removing or zeroing a limit on an unchanged route answers 200 again, under true and under a policy
2026-10-28 05:24:41 -07:00
quietjules
ca24b396da
test(nvidia_riva): audit #41021 (#48801)
* test(nvidia_riva): audit #41021

Adds an owned Riva ASR gRPC server and provider integration cells for the riva_offline request-body ban, the offline and streaming paths, the per-deployment caller opt-in, and the 413 and 408 error mappings

* test(nvidia_riva): check word timing requests and audio samples in the Riva double

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 05:24:08 -07:00
michelligabriele
11559ed895
fix(streaming): exact-match the SSE [DONE] marker in BaseModelResponseIterator (#47832)
* fix(streaming): exact-match the SSE [DONE] marker in BaseModelResponseIterator

* test(streaming): cover reasoning text and error frames that mention [DONE]

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 05:21:31 -07:00
norkelbyte
940b865424
refactor(fallback-generalizations)!: remove the legacy extends and mixed-rule shim (#48737)
* refactor(fallback-generalizations): remove the legacy extends and mixed-rule shim

Main's remote cost map, the bundled backup, and the root map all ship the split
routing/capability rule format, so the shim for the old format is dead code. A rule
mixing litellm_provider with other model_info keys is now warned about and skipped.

* test(fallback-generalizations): fail when a shipped cost map rule would be skipped at install

* test(fallback-generalizations): cover ignored extends and annotate the shipped-rules test

* fix(fallback-generalizations): warn about old-format rules with a pointer to a current cost map and skip extends rules

* fix(fallback-generalizations): tell local cost map users to replace the bundled backup in the old-format warning

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 05:17:28 -07:00
tavren27
826859588c
test(router): audit #41368 (#48794)
* test(router): cover silent_model shadow fan-out and streaming end to end

Proxy cells check that each silent_model target gets one request in the
caller's stream mode, that the caller sees only the primary, and that every
shadow lands its own spend row under its own model group. SDK cells check the
sync Router.completion thread path streams each shadow and fires its success
callback.

* test(router): flatten the spend row lookup with itertools.chain

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 05:14:59 -07:00
norkelbyte
93056ef939
test(proxy): audit #47358 (#48784)
* test(proxy): audit #47358

Add integration cells for pass-through bodies and settings-endpoint mappings across workers

* test(proxy): wait for every worker to hold the saved mapping before probing

* test(proxy): compare passthrough bodies to the client body and probe each worker on its own connection

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 04:55:44 -07:00
norkelbyte
64d545ba7c
test(router): audit #47376 (#48792)
* test(router): audit #47376 batch create routing and supported_endpoints listing

* test(router): make the batch mode cells deterministic and assert concurrent creates

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 04:48:30 -07:00
tavren27
889d8aec15
test(decisions): audit #47508 (#48777)
* test(decisions): audit #47508

* test(decisions): price the Databricks decision cell from its cost-map row

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 04:38:21 -07:00
mika-lt
9546f9a69f
test(router): audit #47498 (#48799)
Pin the queue route contracts #47498 changed: the priority header on streamed answers, the 400 for a request with no priority to queue at, a provider bad request keeping its own status, and a deployment-id request served ahead of the queue

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 04:36:50 -07:00
tin-berri
8e2e99670d
fix(router): continue Responses mid-stream fallbacks on models without assistant prefill (#47672)
* fix(router): avoid assistant prefill in Responses fallbacks

* fix(router): keep prefill for Claude fallbacks that take it and skip constrained continuations

A Responses fallback to a Claude model that takes assistant prefill now gets
the partial response as prefill again, so it continues mid-word without the
prompt-shape drift. Every other fallback keeps the request to continue.

A stream cut after text no longer continues when the request constrains its
output (a json_schema or json_object text format, or a forced tool_choice),
since a continued JSON document or tool call would not parse. The client gets
the primary's error, as on the chat path.

The fallback's re-sent leading space at the seam is dropped, and the prefill
shaping runs per deployment after the encrypted reasoning strip, so stripped
reasoning is never replayed.

* fix(router): guard SDK text_format, narrow forced tool_choice, read Claude base_model, and cap Responses continuation output

Cut Responses streams that asked for structured output through the SDK's text_format now surface the primary's
error instead of stitching two models' JSON. Only a required or named tool_choice counts as forced, so allowed_tools
in auto mode and hosted tool choices still continue. A Claude deployment named only through base_model keeps its
prefill continuation, and the continuation's max_output_tokens is lowered by what the cut stream already emitted.

* fix(router): quote the partial's ending in the continue request and surface the primary's error after a cut tool call

* fix(router): stop Responses fallbacks from continuing content-filter cuts and client-run tool calls

A content filter that cuts a /v1/responses stream mid-answer now returns its error, as on chat, instead of letting a content_policy_fallbacks backup finish the filtered text. Shell, apply_patch, computer, local_shell, and MCP approval items count as a started tool call. Chat continuation goes back to treating any tool_choice object as constraining, as on main

* fix(responses): count a stream of tool arguments as a started tool call

* fix(router): let Responses fallbacks continue under allowed_tools auto and after server-run tool calls

A Responses tool_choice of allowed_tools in auto mode no longer blocks the continuation, since the model may still answer in text. Hosted MCP calls and the bridge's server-run tool search no longer count as a started client tool call.

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 04:34:40 -07:00
paveln83
7f44cff274
test(anthropic): audit #47478 (#48803)
Covers the json_schema envelope edge cases #47478 fixed on the Anthropic json tool path: a key listed only in
`required`, a tool-named wrapper on a stream, non-ASCII and lone-surrogate answers, a stream cut before the tool
block closes (proxy and sync SDK, Anthropic and Bedrock Invoke), and a string `output_format` with and without
`drop_params` on Anthropic and Vertex chat and /v1/messages. The scripted Anthropic peer now refuses a non-object
`output_format` the way Anthropic does

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 04:34:21 -07:00
simonp9
e838957b1d
test(token_counter): audit #47586 (#48800)
* test(token_counter): audit #47586

* test(token_counter): check streamed usage equals the local token count

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 04:30:19 -07:00
norkelbyte
8033f311f2
fix(anthropic): let drop_params drop speed on claude-opus-4-7 after Anthropic removed its fast mode (#48764)
* fix(anthropic): let drop_params drop speed on claude-opus-4-7 after Anthropic removed its fast mode

* test(anthropic): cite the Claude Code fast-mode page for the Opus 4.7 removal date

* test(anthropic): cover the drop_params speed drop for claude-opus-4-7

* test(anthropic): type the model parameter of the speed drop test

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 04:27:58 -07:00
zoveki
53c6939ad4
test(anthropic, bedrock): audit #47489 (#48782)
* test(anthropic, bedrock): audit #47489 thinking replay across Claude routes and Bedrock Converse

* test(bedrock): compare the whole replayed Converse turn in the merged-message and cache point cells

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 04:24:50 -07:00
zoveki
5a18e6b6da
test(files): audit #40621 (#48788)
* test(files): audit #40621

Pin the raw cloud storage file id gate on /v1/files content and delete against a running proxy: twice-encoded gs:// ids stay blocked for non-admin keys, proxy admin keys read s3:// objects, and ordinary provider ids keep working

* test(files): check the full SigV4 signature on the admin raw s3 read

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 04:13:03 -07:00
m6atlas
a0c490ea54
fix(bedrock): retry without strict when Bedrock rejects strict tool schemas under drop_params (#48733)
* fix(bedrock): retry without strict when Bedrock rejects strict tool schemas under drop_params

* test(bedrock): cover the strict tools 400 retry under drop_params

* fix(bedrock): anchor the strict tools rejection match and strip toolSpec strict

* test(bedrock): return captured requests from the strict tools test helper

* fix(bedrock): retry without strict only when a tool sent strict to Bedrock

A strict-limit 400 on a request whose tools carry strict: false, or on a model litellm never sends strict to, was sent again with an identical body. The fallback now takes the model and returns nothing unless a tool puts strict on the wire. Also types the fallback test parameter and records the mock transport's requests on the recorder instead of a mutated list

* fix(bedrock): decide the strict retry on the profile's base model and retry grammar timeouts

An application inference profile with base_model sends strict on the wire, so the retry check now resolves the
capability model the way the request transformation does. Bedrock's "Grammar compilation timed out" 400 is a
strict-grammar rejection too, so it takes the same single retry under drop_params.

* test(bedrock): annotate the strict-tools retry test helper's values as Final

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 04:04:39 -07:00
m6atlas
e2d78b69cd
test(proxy): audit #46815 (#48768)
* test(proxy): audit #46815

Add integration tests for every count route #46815 changed, and count a Claude
subscription caller's tokens with their forwarded token when the deployment has
no configured key, which #46815 turned into a 401

* test(proxy): accept the forwarded subscription token in the count endpoint mocks

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 03:55:25 -07:00
rj-cairn
6ba77b7013
test(spend): audit #47353 (#48785)
A DeepSeek V4 stream that ends without a usage block now has a spend-row cell proving its prompt_tokens are counted with DeepSeek's tokenizer, the one path #47353's own cells did not reach

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 03:50:50 -07:00
norkelbyte
91db681532
test(responses): audit #47504 (#48787)
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 03:50:24 -07:00
tessellark
de40c0aa39
test(llms): audit #47535 (#48786)
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 03:38:21 -07:00
Aidan Singh
b97fcf1af0
fix(router): let affinity pins yield to deployments that already failed this request (#32309)
* fix(router): apply weighted-failover exclusion before deployment-affinity callback

* fix(router): let affinity pins yield to deployments that already failed this request

Moving the weighted failover exclusion ahead of the affinity check also changed what tag, order, plugin and managed-file filters saw. Restore the original filter order and have the affinity check ignore session and API key pins that point at a deployment already excluded or retry-skipped in this request. previous_response_id pins still win.

* fix(router): release affinity pins on deployments that failed this request

Delete a session or API key pin whose deployment already failed the request, so
the deployment that serves it claims the pin. Prompt caching pins skip failed
deployments too, and the proxy strips the internal failed-id fields from client
bodies.

* fix(router): keep affinity pins in place and skip failed origins in encrypted content affinity

A pin now only yields for the request whose pinned deployment failed. Deleting it moved pins after one 429 or a request-caused 400 and added a Redis call on the routing path. Encrypted content affinity also ignores deployments that failed this request, so a failed origin takes the strip-and-dispatch path.

* fix(router): keep encrypted reasoning when a retry can only go back to its origin

The encrypted content check now drops failed deployments the way the router does: a weighted failover exclusion always holds, and a retry skip that would leave nothing falls back to the whole group. The proxy also drops client-sent metadata._failover_excluded_ids, and the encrypted content tests use a real Router

* fix(router): keep encrypted reasoning when previous_response_id binds the failed origin

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 03:24:26 -07:00
tavren27
5dc18fe909
test(proxy): audit #47418 (#48766)
* test(proxy): audit #47418

* test(proxy): hold the stream open past its first chunk and require cost headers in the audit cells

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 03:23:34 -07:00
Max Azatian
ff65785717
fix(router)!: count an explicit allowed_fails of 3 and keep fallback deployments out of cooldown (#46183)
* fix(router): count an explicit allowed_fails equal to the default as set

Router(allowed_fails=3) stored the same value as an unset Router, so
_is_allowed_fails_set_on_router treated it as "not set" and kept the
rate-based cooldown logic. Keep the passed value in explicit_allowed_fails,
like _explicit_timeout, and count it as set.

* chore(router): drop the comment on explicit_allowed_fails

* fix(router): keep the fallback deployment out of cooldown when no deployment can be picked

When every deployment of the requested model is in cooldown, the router raises
"No deployments available" and schedules the failure callbacks on the request's
logging object. By the time they run, the fallback has already written its own
deployment into that object, so the 429 counted as a failure of the healthy
fallback deployment and cooled it down too, leaving the model group with nothing
to serve.

Tag the selection error and skip cooldown accounting for it, the same way
advisor orchestration failures are skipped.

* test(router): annotate the new cooldown test variables with Final

* test(router): pin that a saved allowed_fails of 3 stays unset and type the fallback helper

* test(router): use a fixed start time in the fallback helper

* test(router): cover a fallback group staying up after the primary's selection fails

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 03:19:26 -07:00
islomjon-aiand
f6f49d16cc
feat: add ai& (aiand) provider (#38958)
* feat: add aiand provider

* feat: expose aiand provider in admin UI

* feat(aiand): add ai& model pricing to the cost map

* feat(aiand): list ai& models for aiand/* wildcards and mark /v1/messages supported

* feat(aiand): send Anthropic /v1/messages to ai& natively

* fix(aiand): drop the retired max_tokens field and regenerate endpoint support

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 03:15:56 -07:00
norkelbyte
d84a2d4452
test(spend): audit #47815 (#48774)
Adds an integration cell proving a failed /v1/responses request counts its input from the newest compaction item on its failure spend row

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 03:04:11 -07:00
tessellark
4b0aa0f446
fix(cost-map): drop the gpt-5-nano priority input price OpenAI does not sell (#48763)
gpt-5-nano and gpt-5-nano-2025-08-07 carried gpt-5's priority input price, 50x the nano standard rate. OpenAI sells no priority tier for nano and answers a nano priority request with service_tier default, so proxy spend on real calls was already billed at the standard rate. The wrong price showed in /model/info, in the tier pricing a custom-priced nano deployment inherited, and in SDK cost estimates like litellm.cost_per_token with service_tier priority. The key is removed, and a new test keeps every priority rate between 1x and 5x of its standard rate in both cost map files
2026-10-28 03:01:40 -07:00
Mateo Wang
94e4ebd63d
test(proxy): pin the max_parallel_requests slot release when a streamed /v1/messages MCP auto-execute ends (#47788)
* test(proxy): cover the max_parallel_requests slot a streamed /v1/messages MCP auto-execute keeps

A streamed /v1/messages request that auto-executes MCP tools replays its answer
as a stream after its model rounds already logged success. The stream path frees
the request's max_parallel_requests slot only on a client disconnect, so after
message_stop the slot stays taken and the key's next request answers 429

The test streams such a replay to the end and expects the slot to be free. It
fails until the fix lands

* fix(proxy): free the max_parallel_requests slot when a replayed /v1/messages MCP stream completes

A streamed /v1/messages request that auto-executes MCP tools runs its model
rounds without streaming and replays the final answer as a stream. The first
round claims the request's only success log, and its limiter callback keeps the
slot because the tool call reused it afterwards. The stream path released the
slot only on a client disconnect, so after message_stop the key stayed at its
max_parallel_requests limit and its next request answered 429

Stream cleanup now releases the stashed slot when the stream completed and its
logging object already claimed the success log, which only happens when the
stream replays a response that was logged before it started. A real stream
still releases through its own success callback

* test(mcp): free the key's only parallel slot after a streamed auto-execute ends

Run a streamed MCP auto-execute on a key with max_parallel_requests 1 on
every LLM surface, then send the next request on that key and expect 200.
On main both /v1/messages surfaces answer 429 here, while /v1/responses and
/v1/chat/completions already pass

* test(proxy): keep only the MCP stream regression tests, since #48628 already frees the slot at stream end

* test(proxy): require the MCP stream regression test's response to stream and reach its end event
2026-10-28 02:47:27 -07:00
zoveki
d8a43a7cd3
test(gemini): audit #47542 (#48758)
* test(gemini): audit #47542

Proxy integration cells for the Gemini and Vertex default output budget: the env budget reaches Google on chat and responses, turns on long decode, yields to the caller's own limit, caps per deployment, skips resumes, and a camelCase two-candidate request is never continued

* test(gemini): annotate the budget wire module's pytestmark as Final

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 02:42:45 -07:00
rj-cairn
000d4f6bb3
test(sagemaker): audit #47495 (#48767)
Checks that a SageMaker stream failing after its first chunks reaches /v1/chat/completions, /v1/messages and /v1/responses callers as an error with no success end event, and that each writes a failed spend log row

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 02:40:45 -07:00