Commit graph

56207 commits

Author SHA1 Message Date
devin-ai-integration[bot]
c3c8c934d4 fix(cost-map): point the GitHub Copilot and Azure AI claude-fable-latest aliases at Fable 5.2 2026-11-10 18:45:11 +00:00
devin-ai-integration[bot]
d47c03175b
fix(responses): keep unset-effort gpt-5.4/5.5 tool calls on chat completions (#50968)
* fix(responses): keep unset-effort gpt-5.4/5.5 tool calls on chat completions

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

* test(responses): explicitly route bridge fixtures to responses

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

---------

Co-authored-by: kenneth <kenneth@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-10 18:33:27 +00:00
devin-ai-integration[bot]
5299914964
test(http2): drain client traffic after sending GOAWAY (#50972)
Co-authored-by: mateo <277851410+mateo-berri@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-10 18:31:57 +00:00
Kerry Lu
43a4b465e9
fix(cost-map): give the Gemini 4 Argon rows the shared Gemini 3 search free tier (#50959) 2026-11-10 18:30:00 +00:00
shrey
e6db6206d1
fix(mcp): accept comma-separated vendor scope grants on the connect check (#50717) 2026-11-10 10:23:50 -08:00
yuneng-jiang
3539c0afc9
test(integration): cap the OAuth metadata lookup in the mixed capabilities subscription case (#50867)
The second server in this case is a plain MCP SDK server registered as
oauth_delegate. The gateway's first catalog listing waits for that server's
OAuth metadata lookup, which never resolves and runs for the full
MCP_METADATA_TIMEOUT of 10 seconds. The MCP SDK client also gives
server/discover 10 seconds, so the case passed or failed on about a tenth
of a second of head start.

Setting LITELLM_MCP_METADATA_TIMEOUT to 5 for this case keeps the lookup
well inside the client limit. The case still asserts that subscription
changes are delivered and tools are refreshed across both servers.
2026-11-10 18:11:16 +00:00
paveln83
90f37e113c
fix(proxy): send a prompt-only call_endpoint token count to every provider count API (#50783)
* fix(proxy): count a prompt-only call_endpoint request with the provider on every counter

POST /utils/token_counter?call_endpoint=true with only a prompt sent the
prompt to the provider count API on Gemini and Vertex only. Anthropic,
OpenAI, Bedrock and Azure AI (Claude) deployments answered a local
estimate instead. Every counter now gets the prompt as one user message,
so a provider error surfaces the same as the messages form, and the
framed stand-in after a failed provider count frames the prompt as one
user turn, matching the messages form

* fix(proxy): count a call_endpoint prompt like a user message on providers without a count API

* fix(proxy): count a call_endpoint prompt as a turn only on chat deployments

A prompt-only /utils/token_counter request with call_endpoint=true now goes
to the count API as one user message only when the deployment serves chat
(mode chat, responses, or unknown). Embedding rows and models with no
deployment keep the raw local count.

The Anthropic, Azure AI, and OpenAI count calls now honor the deployment's
timeout, and a count error with an empty body names its status code.

* fix(proxy): fall back to router_settings.timeout for count calls and answer 408 on a count timeout

Count calls to Anthropic, Azure AI, and OpenAI now pick their timeout the way
a chat call to the same deployment does: the deployment's timeout, else
router_settings.timeout, else request_timeout. A count call that times out
answers 408 instead of a 500 processing error.

* fix(proxy): count a prompt as a turn only where a count API exists and give count calls chat's timeout order

* fix(proxy): read the count call timeout through the router settings snapshot

The token counter now asks llm_router.settings().timeout_for() for the chat timeout order instead of a new Router member, so the router surface does not grow

* fix(router): answer timeout_for on every router settings snapshot

The proxy reads the count call timeout through llm_router.settings().timeout_for(), but only the Rust core's snapshot had that method, so a plain RouterSettings (the unit tests' router stub) raised AttributeError. timeout_for now lives on RouterSettings and reads the core's _core_deployment_call_timeout, like the snapshot's other members

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-11-10 18:09:19 +00:00
Kerry Lu
c1abd490b2
feat(customers): reject unknown fields on /customer/new and /customer/update instead of dropping them (#50965)
* feat(customers): reject unknown fields on customer create and update instead of dropping them

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

* fix(customers): derive the budget hint from the budget request fields and fix the create example

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-11-10 18:08:21 +00:00
Kerry Lu
c229851924
feat(ui): customer portal (beta) surfacing the existing customer endpoints and customer usage (#50807)
* feat(ui): Customer Portal (Beta) surfacing the existing customer endpoints and customer usage

Adds a Customer Portal mode to the dashboard (product switcher entry, Customers and
Chargebacks nav) built only on endpoints and data the proxy already serves.

Customers: list ranked by this month's spend from the customer daily-activity
aggregate, status (active, over budget, blocked), search, Create Customer through
/customer/new (id, alias, monthly max_budget, tpm_limit, rpm_limit), Edit alias and
budget through /customer/update, Block and Unblock. Customer detail shows the keys
that sent traffic as that customer this month from the api_key_breakdown of the
same aggregate, read only.

Chargebacks: per month, grouped by customer, customer and key, or customer and
model, with CSV export.

All strings go through i18n. Pure helpers (customerForms, customerPortalData,
chargebackRows) carry unit tests, and the ViewSwitcher integration test covers
entering and leaving the portal.

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

* fix(ui): refresh the customers list after editing or blocking from customer detail

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

* test(ui): name the expected objects in the customer portal tests so the inline-object lint budget holds

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

* test(ui): mock next/navigation in the navbar test now that ViewSwitcher routes with useRouter

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

* fix(ui): format customer portal money and limits through the format keys instead of inline formatters

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

* fix(ui): note that customer blocking needs blocked_user_check and neutralise formula prefixes in the chargeback CSV

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-11-10 10:00:20 -08:00
devin-ai-integration[bot]
3e3842046d
test(budgets): expect per-model tpm and rpm limits in budget info (#50967)
Co-authored-by: mateo <277851410+mateo-berri@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-10 17:59:48 +00:00
Nate Armstrong
461d3407bf
fix(router): hold the registry lock across router-core writes and mirror reads (#50933)
* fix(router): hold the registry lock across router-core writes and the mirror reads at their positions

A registry lookup returns positions in router-core's list and the host reads its mirror at them,
while an upsert wrote router-core and read the mirror back unlocked. A read on another thread in
between got an IndexError past the mirror, or after a removal the neighbouring model's entries

* test(router): cover the registry lock's read and write paths

Move the concurrent-read regression into the registry writes characterization, drop the
explanatory lock comments and the unused litellm-model lookup
2026-11-10 17:58:46 +00:00
Kerry Lu
0b070d25d3
feat: add claude mythos 5.2 to vertex ai cost map (#50940)
* Add Claude Mythos 5.2 to Vertex AI cost map

Copies the vertex_ai/claude-fable-5-2 and @default rows as
vertex_ai/claude-mythos-5-2 and @default. Anthropic's Mythos 5.2 model
page lists Google Cloud as a platform and says it has the same
specifications and pricing as Fable 5.2, and Google publishes no Mythos
row yet, so the new rows cite Anthropic's page as their source.

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

* Bill Mythos 5.2 on Vertex through the cost path in tests

Adds cost_per_token checks with cache reads, 5m and 1h cache writes and
the regional uplift so the Vertex Mythos 5.2 rows are proven to charge
what Fable 5.2 charges, and annotates the new test locals with Final.

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

* Read the regional_endpoint_uplift_multiplier key in the Vertex Mythos uplift test

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-11-10 09:35:37 -08:00
tavren27
1035971a05
fix(decisions): keep box questions on a deployment when a same-model sibling turns them off (#50724)
* fix(decisions): keep a same-model deployment's false box-question flag off the shared cost-map row

* fix(router): take a withdrawn supports_box_questions true off the shared row on update and delete

A deployment's true reached the shared backend row, but a later false no longer
overwrites it, so updating or deleting that deployment left box questions on for
its unflagged siblings until restart. The row now gets the catalog's value back
unless another served deployment of the model still sets true.

* fix(router): count every live router and set_model_list when withdrawing a shared box-questions true

* fix(router): match box-questions siblings by their cost-map row and withdraw on retire

A sibling spelled with its provider registers onto the same litellm.model_cost row, so the withdrawal now compares rows instead of backend keys. Retiring a router takes back the true its deployments put on rows no other live router still sets. Withdrawals are deduped per backend so a large set_model_list stays cheap.

* fix(router): take back a box-questions true an aborted set_model_list registered

A set_model_list that fails partway rolls back the deployment list, but a true the
aborted build already wrote to the shared cost-map row stayed there, so unflagged
same-model deployments kept sending box questions. The rollback now withdraws the
attempted entries too

A withdrawal now only rewrites rows that still hold true, so an entry the failed build
never reached gets no row written for it, even on a model the catalog does not list

The delete wire test now uses a decisions-mode catalog backend whose catalog lists no
box questions, since an unflagged deployment only reads the shared row for a
decisions-mode model

* fix(router): take back an env-named box-questions true a failed set_model_list registered

The rollback took flags back from the raw model_list entries, so a deployment whose model came from os.environ/ was looked up under the reference instead of the model it registered onto, and its true stayed on the shared row

It now takes them back from the entries the failed build reached, whose os.environ/ values are already read

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-11-10 17:26:46 +00:00
devin-ai-integration[bot]
c2d7a99ecd
test(cost): sync vertex 3.1 pro price fields and bedrock converse usage shapes (#50956)
Co-authored-by: kerry <kerry@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-10 09:24:50 -08:00
Kerry Lu
4714aac42b
feat(rate-limit): enforce per-customer per-model tpm and rpm limits in the v3 limiter (#50857)
* feat(rate-limit): enforce per-customer per-model tpm and rpm limits in the v3 limiter

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

* refactor(rate-limit): build model scope settlement targets from one owner tuple

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-11-10 09:22:18 -08:00
zoveki
901f914ba2
fix(responses): replay Claude tool search on follow-up turns (#50821)
* fix(responses): replay Claude tool search on follow-up turns

Claude tool search on /v1/responses now returns a tool_search_output after each tool_search_call, streamed and not, listing the tools the search loaded as the caller sent them

A stateless follow-up sends the search back to Anthropic as the server_tool_use and tool_search_tool_result Claude produced, and the Anthropic request drops a replayed search whose loaded tools are no longer sent, so a changed tool list no longer answers 400

The guardrail write-back for SDK output items reads part text through a typed helper, so the new output item type adds no attribute errors

* fix(responses): emit Claude's tool search output in stream order

The streamed tool_search_output item was announced at the end of the stream with an output index reserved earlier, so output_item.added events arrived as 0, 2, 1 and the OpenAI SDK's stream helper raised IndexError. The item is now emitted when Claude's search result arrives.

* fix(anthropic): keep the caller's tool names in tool search results

A tool name Anthropic cannot take gets rewritten on the request, so Claude's
tool_search_tool_result named the rewritten tool. The response now maps those
tool references back to the caller's names, streamed and not, and a replayed
search result maps them forward again on the next turn.

* fix(responses): replay Claude's tool search only on routes that run it

A follow-up sent to a route that cannot run tool search, a Claude model without it included, now leaves out the replayed search the way it did before, instead of sending Claude search blocks that model cannot take. The stateless gate and the stored-turn filter now use the same check that already decides whether the tool_search tool itself is kept

A streamed tool_search_call now gets its output_item.done when Claude's search result arrives, so it finishes before its tool_search_output. Clients that build history from done events, like Codex, kept the output ahead of its call

* fix(responses): keep a replayed search when the caller sent Anthropic's own search tool

A Claude model the cost map does not flag for tool search still runs it when the request carries Anthropic's own tool_search_tool_regex or bm25 tool, which is how turn 1 already decides to keep it. Both replay gates now follow that rule, so a stored follow-up on such a model keeps Claude's search as it did before, and a stateless one replays it

* fix(responses): replay a BM25 tool search under its own name

* fix(responses): keep a replayed tool search visible to guardrails and prune only missing tools

A replayed Claude tool search now reaches pre-call guardrails and the Purview DLP scan with its
search arguments, a stale search drops only the tool references the follow-up no longer sends,
and the stored previous_response_id path keeps the base gate for routes the cost map does not flag

* fix(responses): replay Claude's search on a stateless follow-up only when it still sends a search tool

A store false follow-up that sent no search tool still got the search replayed, and Claude then said it called a tool it no longer had

* fix(responses): replay a stored Claude search only when the follow-up still sends a search tool

A previous_response_id follow-up that sent no search tool still got the stored search, the same gap a store false follow-up had. The stale search pruning now logs the tools it leaves out at debug level.

handler.py reads the reported speed from the usage chunk, and the guardrail write-back skips tool search outputs, so the branch adds no type errors.

* fix(responses): leave out a stored Claude search kept in server_tool_calls

Main stores Claude's server-side tool search on the assistant message in
provider_specific_fields.server_tool_calls and tool_results, not in
tool_calls. The previous_response_id path only looked at tool_calls, so a
follow-up that sent no search tool still replayed the stored search.

The session handler now finds the search in either place and drops it from
both server_tool_calls and tool_results when the follow-up cannot run a
search.

* test(responses): give the replayed search guardrail fixture its own call id

The guardrail test reused toolu_01Weather, an id that streaming and bridge
tests already put in the process-wide TOOL_CALLS_CACHE. When one of those ran
first in the same worker, the guardrail write-back took its rebuild path and
the test failed for a reason unrelated to the replayed search.

* fix(anthropic): keep Claude's replayed tool search on renamed tools and stored follow-ups

A search-only assistant turn now gets its search result renamed to the sanitized
tool names too, so the stale-tool trim no longer drops it. Stored follow-ups on
Claude routes replay the search as base did, trimmed to the tools still sent, and
Bedrock invoke sends the tool search beta whenever the history holds a search

* test(responses): pin that guardrails scan a replayed Claude search on a follow-up without a search tool

* fix(anthropic): keep Anthropic's tool names in chat tool search results

A chat history whose search result used the request's tool names (weather.get) got
Anthropic's 400 when replayed on a proxy still on the old code, during a rolling deploy or a
rollback. Chat completions now returns Claude's search result as main does, and the Responses
bridge maps a rewritten name back to the request's tool when it builds tool_search_output.
That also fills tool_search_output on Bedrock invoke streams, which carry no name map

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-11-10 17:13:40 +00:00
quietjules
97b4de224a
perf(streaming): reuse the upstream choice for plain litellm HTTP-handler stream chunks (#50786)
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-11-10 09:09:18 -08:00
Kerry Lu
20be20bf45
fix(cost-map): give the Gemini 4 Carbon rows the shared Gemini 3 search free tier (#50951)
Gemini API and Vertex both include 5,000 grounding requests a month shared across
Gemini 3 and newer, so gemini/gemini-4-carbon, vertex_ai/gemini-4-carbon and
vertex_ai/gemini-4-carbon-cyber-permissive now carry the same search_free_requests
allowance as the Gemini 3.x grounding rows in both catalog copies.

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-10 17:09:09 +00:00
tavren27
d70855ca82
fix(proxy): open the Logs drawer by request_id without scanning for litellm_call_id (#50789)
* fix(proxy): open the Logs drawer by request_id without scanning for litellm_call_id

The detail lookup matched request_id OR litellm_call_id, which Postgres runs as a full table scan when the litellm_call_id index is missing or invalid. Read by request_id, the primary key, first and by litellm_call_id only on a miss, both under the spend log statement timeout.

* test(proxy): type the call ID index test helpers

* fix(proxy): limit the Logs drawer call ID lookup to start_date and answer 400 for a malformed date

* fix(proxy): let spend log reads wait for a busy connection like a plain query

* test(proxy): let the session pages read double take max_wait

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-11-10 17:03:21 +00:00
Mateo Wang
82ef50bacb
refactor: read include_cost_in_streaming_usage directly, type MaskedHTTPStatusError init, drop unused encrypted reasoning check (#50617)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-10 08:55:08 -08:00
devin-ai-integration[bot]
bece938ccc
feat(cost-map): add vertex_ai/gemini-4-carbon and gemini-4-carbon-cyber-permissive from the Vertex pricing page (#50942)
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-10 16:39:08 +00:00
norkelbyte
c90b5e057f
fix(openai): download PDF URLs off the event loop and transform async chat messages once (#49176)
* fix(openai): transform async chat messages once, whatever configs were built before

* fix(openai): keep tool-result PDF downloads off the event loop and stop Mistral rewriting retried messages

* fix(openai): pre-download PDF URLs into the sync transform so async requests match sync

Async chat requests now download every PDF URL in user and tool messages at once, then run the sync transform_request on a worker thread that reads those downloads. The request, filename, logged messages, and download count match a sync request, and a failed download raises the sync error with no second attempt. DeepSeek joins the configs that flatten content and skip the up-front download.

* fix(openai): skip up-front PDF downloads past a file part the transform rejects

* fix(openai): keep system-message PDF downloads off the event loop and stop downloads after a dead PDF URL

* fix(openai): let PDF downloads after a dead URL finish and retry rate-limited ones one at a time

Cancelling in-flight downloads after a dead PDF URL could abort a proxy worker on uvloop, so the downloads now run to the end in the background. A URL whose up-front download got a 429 downloads again inside the transform one at a time, as on main. A content part whose type is not a string is no longer read as a PDF part, so it reaches the provider instead of raising a TypeError.

* fix(openai): download a request's PDF URLs one at a time and stop at the first dead one

* fix(openai): prefetch the PDF URLs the sync transform downloads, after role mapping and up to the first unreadable part

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-11-10 16:37:00 +00:00
Emerson Gomes
f8933ecae4
fix(redis): free each request's Redis batch scope once the request ends (#50751)
* fix(spend): respect use_redis_transaction_buffer when selecting the SpendLogs ledger

* revert: keep the SpendLogs ledger independent of use_redis_transaction_buffer

Measured on a live proxy, ledger on and ledger off grow RSS at the same rate per request,
so gating the ledger on the Redis buffer moved deployments off it without fixing the growth.

* fix(redis): free each request's Redis batch scope once the request ends

The native request batch keeps a copy of the context it is built in. It was built inside the
request scope, so that context pointed back at the scope, and the cycle collector cannot see
through the native object. Every request's batches, spend counter batches and native Redis
state stayed alive, growing worker RSS about 40 KiB per request and slowing each request as
the heap grew. Build the native batch in the context the scope was opened from instead.
2026-11-10 16:14:12 +00:00
devin-ai-integration[bot]
a6b61a2c3b
feat(cost-map): add gemini/gemini-4-carbon from the Gemini API pricing page (#50931)
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-10 16:06:25 +00:00
tavren27
1c68ec429a
fix(proxy): bill an OpenAI pass-through background response once across repeated reads (#50841)
* fix(proxy): bill an OpenAI pass-through background response once across repeated reads

A finished background response now bills only on the first pass-through call
that sees it finished with usage. The claim is an insert on the new
LiteLLM_BackgroundResponseSettlement table, keyed by response id, so it holds
across workers and replicas. Every later retrieve, cancel, streamed create, or
resumed stream logs like an unbilled read. With no database or a failed insert
the call still bills.

* fix(proxy): keep pass-through background billing fail-open without prisma and skip foreground stream re-parse

* fix(proxy): claim background responses on failed streams and bound the claim

A resumed stream that fails after response.completed billed its usage through
the partial-usage path without claiming the response id, so a later read billed
it again. That path now claims the id and logs $0 when an earlier read paid.

The claim uses create_many(skip_duplicates=True) so a repeat read no longer
raises a unique violation in Postgres, and it waits at most 2 s before billing,
since the claim now runs before the client gets its response.

* fix(pass_through): keep the background response claim and its spend write together under cancellation and managed ids

* test(pass_through): cover a failed stream whose failure logging cannot be scheduled

* fix(proxy): start the background response settlement migration with a lock timeout

* fix(passthrough): leave a failed background stream's charge to its next read

A stream that failed after response.completed claimed the response id while its failure row billed $0, so no later read paid. The failure path is back to main's behavior, so the next finished read claims and bills once

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-11-10 07:33:25 -08:00
devin-ai-integration[bot]
7bcc46246b
feat(cost-map): rolling registry update (claude-mythos-5-2, Azure Mythos 5.2, Friendli GLM-5.1 date, GovCloud Fable 5.2 token counting) (#50777)
* fix(cost-map): drop unsupported Mantle token_count_model from the Bedrock GovCloud Claude Fable 5.2 row

* feat(cost-map): add claude-mythos-5-2, absorb Azure Mythos 5.2 rows and Friendli GLM-5.1 deprecation

Co-authored-by: Lee-Si-Yoon <76679207+Lee-Si-Yoon@users.noreply.github.com>
Co-authored-by: Emerson Gomes <emerson.gomes@thalesgroup.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Lee-Si-Yoon <76679207+Lee-Si-Yoon@users.noreply.github.com>
Co-authored-by: Emerson Gomes <emerson.gomes@thalesgroup.com>
2026-11-10 13:31:56 +00:00
yuneng-jiang
433163a8e2
test(integration): empty the shared client cache before the tier pin batch test (#50854)
The test stores its recording Redis client in litellm's shared client
cache with the default ttl. When earlier tests in the same process have
filled that cache with longer lived clients, the next insert evicts the
recording client, the pin is read through a fresh client, and the test
sees no commands on the wire.
2026-11-10 00:34:27 -08:00
Rick
f700e21e32
fix(ui): keep the request log model cell to one model (#49619)
Some checks are pending
unit / endpoints-and-responses (push) Blocked by required conditions
unit / enterprise-routing (push) Blocked by required conditions
unit / guardrails-hooks (push) Blocked by required conditions
unit / key-generation (push) Blocked by required conditions
unit / logging-misc (push) Blocked by required conditions
unit / mcp-oauth (push) Blocked by required conditions
unit / proxy-runtime (push) Blocked by required conditions
unit / proxy-server-core (push) Blocked by required conditions
unit / root (push) Blocked by required conditions
unit / OpenAI 3.0.0 compatibility (push) Blocked by required conditions
unit / router (push) Blocked by required conditions
unit / rust-bridge-harness (push) Blocked by required conditions
unit / auth-checks (push) Blocked by required conditions
unit / budgets (push) Blocked by required conditions
unit / custom-logging (push) Blocked by required conditions
unit / db-and-spend (push) Blocked by required conditions
unit / jwt-and-keys (push) Blocked by required conditions
unit / proxy-utils (push) Blocked by required conditions
unit / ui-unit (push) Waiting to run
unit / docs (push) Waiting to run
unit / helm (push) Waiting to run
unit / OpenAI 2.20.0 compatibility (push) Blocked by required conditions
unit / OpenAI 3.25.0 compatibility (push) Blocked by required conditions
unit / redis-py 5.3.1 compatibility (push) Blocked by required conditions
unit / redis-py 6.4.0 compatibility (push) Blocked by required conditions
unit / redis-py 7.4.1 compatibility (push) Blocked by required conditions
unit / redis-py 8.0.1 compatibility (push) Blocked by required conditions
unit / coverage (push) Blocked by required conditions
unit / unit passed (push) Blocked by required conditions
GitHub Actions Security Analysis / zizmor (push) Waiting to run
* fix(ui): keep the request log model cell to one model

A session row rendered every model the conversation ever used, comma
joined. A long conversation produced a huge string that the auto-layout
table turned into a very wide Model column, pushing the rest of the
columns off screen.

Render only the model of the row's own call (the newest call in the
session) and show an overflow count. The full model list stays in the
hover popup, including a note when the server capped the list.

Co-Authored-By: AaronHowell <111272993+AaronHowell@users.noreply.github.com>

* fix(ui): use the shared Badge and an i18n key in the request log model cell

The "+N" overflow marker now uses the shadcn Badge instead of a hand-styled span, and the
"More models not shown" note reads from en.json so the dashboard's i18next/no-literal-string
lint rule passes.

---------

Co-authored-by: AaronHowell <111272993+AaronHowell@users.noreply.github.com>
Co-authored-by: Yuneng Jiang <yuneng@berri.ai>
2026-11-10 00:05:02 -08:00
devin-ai-integration[bot]
52cd9ef0e4
fix(proxy): let the OS assign Prisma engine ports (#50850)
* fix(proxy): let the OS assign Prisma engine ports

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

* fix(proxy): clear Prisma connection state after failed startup

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

---------

Co-authored-by: mateo <277851410+mateo-berri@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-10 07:28:55 +00:00
ryan-crabbe-berri
87be092af7
fix(proxy): send the whole Codex model catalog to Codex 0.166.0-alpha.1 and newer (#50736)
* fix(proxy): send the whole Codex model catalog to Codex 0.166.0-alpha.1 and newer

Codex removed its 1 MiB limit on model_catalog_url responses in openai/codex#56525,
first shipped in 0.166.0-alpha.1 (0.165.0 stable was cut without it). Older or unknown
Codex versions still get the catalog cut to 1 MiB, since they drop a larger one.

* fix(proxy): order patched Codex alphas like 0.166.0-alpha.5.1 when gating the catalog

Codex tags patch releases of an alpha as X.Y.Z-alpha.N.M, which PEP 440 rejects, so those
clients counted as too old for the uncut catalog, their custom reasoning levels and stock
entries. Map them to aN.postM so they sort between alpha.N and alpha.N+1 as semver does.

* test(proxy): type the Codex catalog byte-limit test

* refactor(proxy): gate the uncut Codex catalog on the 0.166 release line

Read only the major and minor numbers of client_version instead of a shared
semver comparison, and leave the reasoning level and stock entry version
checks as they are on main.

* refactor(proxy): read every Codex client_version with one helper

Codex sends semver tags such as 0.166.0-alpha.5.1 that the generic version
parser rejects. The catalog byte limit, the per-model minimum version and the
custom reasoning level checks now share one reader for Codex's format, which
also bounds digit counts so an absurd version can't fail the listing.
2026-11-10 07:10:30 +00:00
devin-ai-integration[bot]
b3f75884a6
test(accounting): verify delivery continues while maintenance is locked (#50843)
Co-authored-by: mateo <277851410+mateo-berri@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-10 06:52:48 +00:00
Kerry Lu
d1a0cc6b24
feat(cost-map): add meta/muse-ember-1.0 with long-context rates from the Meta pricing page (#50847)
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-10 06:36:06 +00:00
Mateo Wang
ac08eb5663
fix: make scheduled key, user, and team spend resets idempotent (#50298)
* fix: make scheduled spend resets idempotent

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

* test: cover reset roundoff with shared Redis workers

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

* test: isolate reset workers and type raw adapters

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

* fix: keep updated_at moving on zero-spend budget resets

* fix: never leave spend below zero after a budget reset

* fix(proxy): keep the stored budget reset time when budget_duration is resent unchanged

Key, user, and team updates recomputed budget_reset_at whenever the body carried budget_duration, and the Admin UI key form sends it on every save. Under the guarded reset, a resend during a sweep skipped that period's reset, and a resend after the boundary skipped it on main too. An unchanged duration now keeps the stored reset time

* fix(proxy): keep a resent budget reset time only once it is due

A save that resends the same budget_duration keeps the stored reset time
only when that time has already passed, so the scheduled sweep still
resets the period it is due for. A row that is not due yet realigns its
reset time on save, the same as before.

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-09 22:21:43 -08:00
Kerry Lu
3daeb8daf2
fix(transcription): stop billing a FLAC with no sample count as 576 trillion seconds (#50842)
* fix(transcription): treat libsndfile's unknown frame count as no duration

* refactor(transcription): inline the unknown frame count check

* fix(transcription): read the frame count once to keep the type gate flat

* test(transcription): annotate the new passthrough test's locals as Final

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-10 06:19:33 +00:00
devin-ai-integration[bot]
84b0a4297a
fix(ui): preserve credential actions across dashboard renders (#50845)
Co-authored-by: mateo <277851410+mateo-berri@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-10 06:17:41 +00:00
Kerry Lu
64d84e557e
feat(budgets): add per-model tpm and rpm limit maps to budgets and customers (#50797)
* feat(budgets): add per-model tpm and rpm limit maps to budgets and customers

Budgets gain model_tpm_limit and model_rpm_limit (model name to whole number, 0 or more).
/budget/new, /budget/update, /budget/info and /customer/new carry them, and the auth
path loads a customer's maps onto UserAPIKeyAuth as end_user_model_tpm_limit and
end_user_model_rpm_limit so the limiter can enforce them in a follow-up.

* fix(budgets): keep org, team and project per-model limits in metadata; lock_timeout and type gate fixes

Organizations, teams and projects store model_tpm_limit / model_rpm_limit in their own metadata,
which the limiter reads, so the shared budget allowlist leaves those two fields to customers.
Migration starts with SET LOCAL lock_timeout. Mapping annotations and rebind-ok reasons satisfy
the type discipline gate. Customer info tests expect the two new budget keys.

* fix(organizations): keep stripping budget_id and flattening nested per-model maps on update

The update path pops every budget field from the organization row again, so a caller's budget_id
never changes the attached budget, and a nested litellm_budget_table still flattens the two maps
into org metadata. Drops the explanatory comment on CUSTOMER_ONLY_BUDGET_FIELDS.

* fix(types): keep basedpyright and ruff strict counts flat for the new budget fields

Reads the per-model maps off the end-user params with membership checks instead of .get on an
untyped dict, writes the inline customer budget through the client's typed jsonify_object, and
builds budget rows in handle_budget_for_entity with model_validate so the two Mapping fields never
receive object-typed kwargs.

* chore: drop unrelated reformatting from project endpoints

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-11-09 22:15:18 -08:00
yuneng-jiang
07be67603c
test: update the spend tests that still expected the pre-ledger accounting queues (#50814)
* test: update the spend tests that still expected the pre-ledger accounting queues

#49872 made the SpendLogs ledger the default accounting path on purpose. A batch with no billable request now writes no cost row, a call the guardrail fails closed during a vendor outage keeps one spend row of either status while its key is still charged once, and charges left at a graceful shutdown are applied by the next start. The daily spend batch shutdown tests exercise the old queue, so they pin use_spend_logs_ledger to false.

* test(integration): price the guardrail relay model so the outage test can assert the charge

The relay model had no price, so every stream cost $0 and the outage test could not tell a charged call from an uncharged one. Both rigs now price it, and the test expects each served stream to cost 9 input and 4 output tokens and the key to be charged for all sixteen.

Also notes the skipped batch cost row case in the e2e rules file, next to the other explicit skips.

* test: repair four tests that fail for reasons outside the product

The OpenAPI docs gate test rebuilds the Rust extension with uv, and the integration lane blocks outbound traffic, so uv could not refresh its PyPI index for maturin. The test now runs uv offline against the cache the dependency install already filled.

The response cache test expected a chat reply blocked with empty content to be cached. Since #50057 a reply with no output is never cached, so that case now expects the repeat to reach the upstream and the real answer to be cached.

The Gemini cache hit e2e test gave the model 16 tokens, which its thinking can use up, and a reply with no text is not cached. It now allows 512 and checks the first reply has text.

The Together cache read e2e test picks the cheapest model with a cache read price. Since #50491 that is a -latest alias, whose target reports no cached tokens. Aliases are now left out of the pick.
2026-11-09 22:07:42 -08:00
joshua-berri
9a7f5eeec4
fix(complexity_router): hold the default model for a user_turn ask whose classifier failed (#50793)
* fix(complexity_router): user_turn continuations no longer replay an earlier ask's pin

A user_turn pin was stored per session, so when a new ask could not be
pinned (classifier timeout, housekeeping excluded) its tool continuations
replayed the previous ask's decision and could silently drop a hard task
to the cheap tier

Each pin now records a fingerprint of the ask that wrote it, and a
continuation ignores a pin from a different ask. A new ask that cannot be
pinned also overwrites the session pin with an empty one, which covers
continuations whose history lives server side (previous_response_id) and
carry no ask to fingerprint. Pins without a fingerprint keep replaying so
rolling deploys stay consistent

Fixes #44250

* fix(complexity_router): a new user_turn ask clears the previous ask's pin

Replace the per-ask fingerprint with a simpler rule. In user_turn mode every
new ask owns the session pin: a pinnable decision writes it as before, and
one that cannot be pinned clears it, so the ask's continuations classify
instead of replaying an earlier ask's model. This also covers continuations
whose history lives server side (previous_response_id)

Housekeeping calls never clear the pin. The check reads the classified
decision before the health, modality and cache gates, which rewrite the
cause of a failed-over title call

Fixes #44250

* fix(complexity_router): hold the default model for a user_turn ask whose classifier failed

Replace the clear-on-new-ask rule with a smaller change. In user_turn mode a
classifier failure now pins the default model for that ask, so its tool
continuations replay the default model instead of the previous ask's pin.
The next new ask classifies again, so the hold ends with the ask, and
session_affinity mode keeps never pinning a failed classification

Fixes #44250

* fix(complexity_router): a plan-mode request never replays a pin without a tier

A held default-model fallback has no tier when the default model sits
outside the tier pools, so replaying it skipped plan_mode_min_tier. A
plan-mode request now classifies instead of replaying a tier-less pin,
which is what it did before the fallback was held

---------

Co-authored-by: Joshua Valluru <326636767+joshua-berri@users.noreply.github.com>
2026-11-09 21:56:29 -08:00
moe-berri
50f2c0989a
test: ignore Ollama metadata calls in decisions decoy check (#50827)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-09 21:52:02 -08:00
tavren27
96b916028a
fix(proxy)!: return managed file ids from rag ingest and use team credentials on vector store file routes (#50459)
* fix(proxy): give rag ingest managed file ids and team credentials on vector store file routes

RAG ingest through a team's OpenAI deployment now registers the uploaded file as a LiteLLM managed file and returns its managed id, so GET /v1/files lists it for the caller. Re-ingesting a managed id attaches the provider file and returns the caller's managed id

Model-less vector store file calls (create, list, retrieve, content, update, delete) from a team key now use the team's OpenAI deployment that ingest uploads through, via the router, and fall back to the direct path when the key may not use one. Explicit api_key, api_base, litellm_credential_name, and forwarded provider keys still win, and blank values count as absent

* fix(proxy): route managed vector store file ids to their team deployment and keep missing-file 404s off the router

Model-less team vector store file calls now go straight to the provider with the team deployment's
credentials, so 404s on missing files no longer put the team deployment in cooldown.

A managed file id on the vector store file routes now uses the deployment named by its
llm_output_file_model_id when that deployment is proxy-wide or belongs to the caller's team, and falls
back to target_model_names otherwise.

* fix(rag): keep managed ids and team checks on every file and search path

Ingest no longer registers a raw file id when file_url is empty, a managed
file id on a proxy without the managed files hook answers 400, and vector
store search and /v1/rag/query hits show the managed id ingest returned.

Model-less vector store file calls use the team deployment only after the
key, team, and member model checks pass, fall through to the router when the
team has no OpenAI deployment, and log the deployment they used.

* fix(rag): keep provider ids on lookup failures and mint managed ids only for keys that can use them

A database error while mapping search hits to managed ids keeps OpenAI's ids instead of failing a finished search. Ingest registers a managed id only when the caller passes the team deployment's model checks, so a model-limited key keeps the raw id it can still use, and the file routes share that check.

* fix(rag): attach the managed file copy that matches the ingest credentials

* fix(vector_store_files): route a managed file id to the caller team's copy

* fix(vector_store_files): keep the upload copy for a store registered with its own credentials

* fix(vector_store_files): use a managed file's team copy only when the store holds it

* fix(vector_store_files): attach a managed file's team copy and keep named credential options out of the store check

* fix(vector_store_files): attach the team copy only to a store that routes to the team deployment

* fix(vector_store_files): keep a managed store's routing and check the upload copy through its own deployment

* fix(vector_store_files): route a managed file to its upload deployment only when the key may call it

A managed file id names its upload deployment, and with require_managed_files off that id is not checked against an ownership record. The routes now use that deployment only when the caller may call its public model, else they keep target_model_names routing as on main.

* fix(vector_store_files): log a pinned managed file call under its deployment's public model name

* fix(vector_store_files): send a pinned managed file call's deployment key only to that deployment's base

* fix(vector_store_files): keep stored credential headers intact and fail the store check on errors other than 404

* fix(vector_store_files): answer a failed store check with the provider status and copy credentials read by name

* fix(vector_store_files): send a call to the upload copy when the store check fails without a 404

A store check error other than a 404 now leaves the call on the upload copy, the way main always routed it, so the real call keeps the router's retries, the provider's status, and its failure log instead of failing on one transient error

* fix(vector_store_files): keep a credential's no-log key out of the store check

A named credential carrying a no-log key made the store check call aretrieve with no-log twice, so every managed id call through that deployment answered 500 where main answered 200

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-11-10 05:44:28 +00:00
devin-ai-integration[bot]
fe6cf07cc8
chore(cost-map): add together_ai/elorian-labs/glimpse-1.0 from the Together models API (#50830)
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-10 04:32:22 +00:00
devin-ai-integration[bot]
9384dff163
chore(cost-map): add the openai gpt-image-2 shutdown date from the deprecations page (#50829)
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-10 04:30:48 +00:00
norkelbyte
68e78bc53a
fix(router): count a Responses WebSocket turn's tokens before the next turn is admitted (#50730)
* fix(router): count a Responses WebSocket turn's tokens before the next turn is admitted

* fix(responses): forward and log a finished websocket turn when its early count fails or is cancelled

A usage payload the parser rejects no longer stops the relay before the terminal event reaches the client, and a client that leaves while the turn is being counted still gets the turn logged. A cancelled early count now clears its counted-tokens marker so the logging callback counts the turn in full.

* fix(responses): finish a turn's early token count when the client leaves

A client that left while a finished turn was being counted cancelled the count halfway. The router's count could be written with no marker, so the logging callback counted the turn a second time. The count now runs to the end before the turn is logged, and the failure path goes back to clearing only on errors

* fix(router): count one router request per Responses WebSocket turn

A socket whose finished turns each counted their own request in router
usage no longer adds one more request when it closes, so N turns count N
requests, as N HTTP calls do. A socket with no counted turn still counts 1

* fix(proxy): keep router usage markers out of pass-through client metadata and ClickHouse rows

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-11-10 04:13:51 +00:00
paveln83
69b0f1deac
feat(proxy): log keys and teams still on the deprecated empty models defaults at boot (#50720)
* feat(proxy): log keys and teams still on the deprecated empty models defaults at boot

* fix(proxy): read the repository tables inside the empty models boot check so a client error never fails startup

* fix(proxy): say which empty-models rows keep access in 2.0.0

Teamless keys listing all-team-models get no models in 2.0.0, so the
boot warning no longer says every counted row keeps its access. It
names each upgrade outcome only when its kind is in use, says the counts
include blocked and expired rows, and words each kind by what the
setting means rather than what it grants

* fix(proxy): drop the unbacked 2.0.0 rewrite promise from the boot warning

The warning no longer says the upgrade keeps access for empty models
lists, since no shipped code or public page backs that yet. It says
2.0.0 removes the defaults and how to keep today's access, and keeps
the advice for teamless all-team-models keys. The integration test
drops an empty-database assertion that ran before the check finished

* fix(proxy): leave rows with access groups out of the empty models boot warning

* fix(proxy): count access group rows, skip management and read-only keys in the empty models warning

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-11-10 04:05:25 +00:00
Krrish Dholakia
9e80943c13
fix(hosted_vllm): omit empty tools list from chat requests (#50812)
* fix: omit empty tools from chat completions

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

* Fix empty tools only for hosted vLLM chat completions

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

---------

Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-09 19:49:13 -08:00
ryan-crabbe-berri
e099925923
fix(proxy): list images in the Codex catalog only for models known to read them (#50738)
* fix(proxy): list images in the Codex catalog only for models known to read them

A translated Codex entry used to offer image input whenever no deployment declared supports_vision,
so Codex let users attach images to models that may not read them. Image input now goes out only
when every deployment behind the name says it reads images.

* test(proxy): type the vision combination test parameters

* refactor(proxy): rename the vision helper and drop the extra vision docstring sentence
2026-11-10 03:46:37 +00:00
kenneth-berri
7ccc0e0fff
feat(usage): split customer spend by the key's current team (#50764)
* feat: add customer spend breakdown and filtering by current team

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

* fix: recover legacy customer team spend and tolerate lookup outages

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

* perf: resolve customer team keys once per daily activity query

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

* fix: paginate customer team activity in SQL

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-11-09 19:35:31 -08:00
ryan-crabbe-berri
16fad64e95
fix(proxy): serve Codex its own Bedrock catalog entries for Bedrock deployments (#50743)
* fix(proxy): serve Codex its own Bedrock catalog entries for Bedrock deployments

Codex builds separate catalogs for Bedrock's runtime and Mantle endpoints that turn off what Bedrock does not support yet. A model whose deployments run on Bedrock now takes its entry from those catalogs, vendored from Codex 0.166.0, instead of from Codex's OpenAI catalog.

* fix(proxy): keep Bedrock catalog entries from Codex releases that cannot decode them

codex debug models leaves out fields such as supports_parallel_tool_calls that Codex releases before 0.148.0 require, so a Bedrock entry now needs at least 0.148.0 as well as its source entry's minimum.
2026-11-09 19:00:22 -08:00
devin-ai-integration[bot]
bdb7e770f2
test(integration): expect the 400 for a content-less leading system on Mantle count_tokens after #50708 (#50820)
Co-authored-by: mateo <277851410+mateo-berri@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-10 02:59:17 +00:00
Mateo Wang
71a9fcb56a
fix(sso): read the SSO client secret before the refresh-token DB check (#50773)
* fix(sso): read the SSO client secret before the refresh-token DB check so a concurrent env sync cannot split the client id from its secret

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

* test(sso): type the secret-race test and read the provider from the IdP boundary mock

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-11-09 18:54:15 -08:00