Commit graph

56381 commits

Author SHA1 Message Date
Tin
d1c2674e6c test: tighten shared OAuth and retired Lens startup coverage
Some checks are pending
Cost map pins recheck / rerun-cost-map-guard (push) Waiting to run
LiteLLM Rust DB / db-queries (push) Waiting to run
LiteLLM Rust / rust-lint (push) Waiting to run
LiteLLM Rust / rust-test (push) Waiting to run
LiteLLM Rust / rust-wheel (push) Waiting to run
Terraform Modules / fmt, validate, test (aws) (push) Waiting to run
Terraform Modules / fmt, validate, test (gcp) (push) Waiting to run
Terraform Provider / gofmt, vet, build, test (push) Waiting to run
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Waiting to run
2026-11-12 16:39:12 -05:00
Tin
111668467b chore: preserve latest main features in Lens removal 2026-11-12 12:54:09 -05:00
devin-ai-integration[bot]
382588a365
feat(proxy): publish opted-in pass-through endpoints on the Model Hub (#43905)
* feat(proxy): publish opted-in pass-through endpoints on the Model Hub

Pass-through endpoints gain display_name and show_in_model_hub. Endpoints that opt in appear on
/public/v1/model_hub and /public/model_hub as rows in mode passthrough, named by their display name
or their path, with the route in pass_through_path. The admin UI exposes both fields on the create
and edit forms and the public hub renders passthrough rows with a route-specific curl example

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

* fix(proxy): carry pass-through methods onto hub rows and key dashboard rows by route

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

* fix(proxy): keep health, cost and shell quoting correct for pass-through hub rows

Pass-through rows no longer inherit a router model's health check when they share its name, the public table shows n/a instead of Free for their token costs, and the generated curl example escapes shell characters in the route

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

* fix(ui): quote the method in the pass-through hub curl example

A configured method string is placed after curl -X inside escaped double quotes so it cannot alter the copied command

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

* test(ui): build pass-through hub fixtures through a helper

Keeps the dashboard lint budget for inline object arguments under its limit

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

* test(integration): audit the pass-through Model Hub surface end to end

Cover forwarding, pagination, validation, auth, updates, duplicate names,
config-file precedence, health-name isolation, repeated reads and two chaos
cells (create/delete churn, a killed proxy worker). Build the stored
pass_through_endpoints list in one shot instead of appending to the list
the config reader handed back

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

* fix(proxy): keep concurrent pass-through creates from overwriting each other

Creating an endpoint appends to the general_settings list the proxy already holds instead of persisting a private copy, so two admins creating at the same time both land, as on main before this branch

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

* fix(proxy): serialize pass-through endpoint creates behind a process lock

Each create copies the stored list under an asyncio lock before persisting, so simultaneous creates in one worker no longer overwrite each other and a rejected write (config-owned path) never lands in the in-memory settings

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

* test(integration): collect paginated pass-through rows without mutating a list

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

* fix(ui): let a pass-through with no stored timeout save from the edit form

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

* test(ui): type the pass-through edit fixture so a null timeout compiles

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

* perf(proxy): read published pass-throughs from the in-memory settings store

The public Model Hub routes read the pass-through list this worker already
serves from proxy_config.settings instead of querying the config table on
every anonymous hub read. Also drops a mutable-ok suppression that no longer
guards a violation

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

* test(integration): list a config file pass-through beside database rows on the hub

Main merges database pass-throughs with config file ones on other paths since
#43962, so a database write beside a config entry is accepted rather than
rejected as config-owned

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

* test(proxy): type the monkeypatch fixture in the pass-through hub tests

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

* refactor(proxy): validate pass-through config entries instead of splatting unknown dicts

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

* refactor(proxy): list model hub rows as a tagged union of model groups and pass-throughs

Health enrichment and the no-router check now match on ModelGroupRow or
PassThroughRow instead of testing pass_through_path in each route. Both hub
routes share published_hub_rows, the v1 row helper is sync again, and the
config list route goes back to main's inline merge since the hub no longer
needs configured_pass_through_endpoints

* test(ui): find the pass-through hub row by role to stay under the no-node-access budget

* fix(proxy): keep database pass-through endpoints in the settings store across config reloads

A config reload cleared the stored database row before rebuilding the
pass-through list, so without store_model_in_db the list fell back to the
config file entries alone. The Admin UI triggers that reload on page load
through /get/config/callbacks, which dropped UI-created endpoints from the
Model Hub until the next write

* refactor(proxy): make the hub row matches exhaustive with assert_never

* refactor(proxy): return the exhaustive match fallthrough and build hub test inputs in one shot

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

* refactor(proxy): drop the in-process create lock from pass-through endpoints

The lock only covered one worker, and the read-modify-write on the general_settings blob is a separate storage problem tracked as tech debt. Create goes back to main's behavior.

* fix: align pass-through model hub with current lint rules

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

* test: share worker state for pass-through hub restart coverage

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

* fix: show pass-through curl examples in model hub inspector

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

* fix: skip invalid pass-through hub entries and await peer refresh

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

---------

Co-authored-by: ryan <ryan@berri.ai>
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>
2026-11-12 17:46:38 +00:00
ishaan-j-berri
f10b42b1ce
feat(ui): add daily model usage to customer pages (#51419)
* feat(ui): show daily model usage on customer pages

* test(ui): assert the customer budget hint is hidden
2026-11-12 12:45:40 -05:00
Tin
7263e10b9c chore: resolve Lens removal conflicts with main 2026-11-12 12:43:43 -05:00
kenneth-berri
be623b5bde
fix(token-counter): count Anthropic server tool history (#51334)
* fix(token-counter): count Anthropic server tool history

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

* fix(token-counter): bound nested results and update proxy coverage

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

* fix(token-counter): include readable web search snippets

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-12 09:35:36 -08:00
Nate Armstrong
2845677822
fix(messages): name the deployment on a rust stream's transport error (#51358)
When the upstream connection drops or stalls mid-stream on the Rust Messages
path, the caller gets the raw httpx error without litellm_debug_info, so the
llm_exceptions alert names no API base, model group or deployment. Python's
stream iterator attaches that info to the same error, and the Rust host now does
too.
2026-11-12 17:29:37 +00:00
Tin
b67aceeb60 test(auth): preserve OAuth introspection coverage after Lens removal 2026-11-12 11:58:27 -05:00
devin-ai-integration[bot]
b60df93c46
feat(fal_ai): add Nano Banana 2.1, bill it from fal billable units, fix google/ routing (#51410)
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-12 08:55:08 -08:00
Mateo Wang
00bd56760d
fix(auth): enforce JWT end-user budget rate limits (#51405)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-12 08:51:27 -08:00
shrey
40981a5d89
feat(mcp): cache upstream tool lists with an admin-set lifetime (#51160) 2026-11-12 08:48:04 -08:00
Kerry Lu
e3b32534dc
feat(replicate): add video generation, image generation and text to speech through the predictions API (#51360)
* feat(replicate): add video generation, image generation and text to speech through the predictions API

* fix(replicate): surface poll HTTP errors, keep deployment ssl_verify and native speech input, flatten outputs without recursion

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

* fix(replicate): keep model family mapping for deployments and versions, preserve video id on status, poll until terminal status

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

* fix(replicate): validate output downloads and poll origin, bind retrieval to the requested model, flatten image extra_body, keep speech input authoritative, use the async image path

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

* fix(replicate): keep the audited prompt authoritative over image extra_body

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

* test(replicate): cover extra_body.prompt not replacing the audited prompt

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

* fix(replicate): honor the request ssl_verify when polling and downloading speech output

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

* refactor(replicate): build the video usage into VideoObject instead of assigning it afterwards

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

* fix(replicate): keep the inspected video prompt authoritative over native extra_body prompt

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

* feat(replicate): bill native image dimensions and token-metered speech output

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

* fix(replicate): bound output downloads, accept both token metric spellings and build speech usage at construction

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

* fix(http_handler): walk bounded-download redirects iteratively

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

* fix(replicate): reject dot and empty model path segments and keep the bounded redirect walk annotated

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

* fix(http_handler): drop the unused rebind suppression

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

* fix(http_handler): put the rebind suppression on the redirect hop binding

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

* fix(replicate): run pinned versions through the model's own versions endpoint

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>
2026-11-12 08:44:50 -08:00
devin-ai-integration[bot]
482c8eb1e5
chore(cost-map): add 7 fal_ai rows for seedream 5.5, flux virtual try-on, patina material and vidu templates (fifth fal-ai sync) (#51407)
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-12 16:42:04 +00:00
tin-berri
d076cee903
feat(skills): let authors edit and withdraw pending submissions (#51312) 2026-11-12 08:30:10 -08:00
devin-ai-integration[bot]
5a511b6fcc
chore(cost-map): rolling registry audit 2026-11-12 (#51395)
Absorb #51347, #51222, #51357 and #51275 with the GMI large-context tier.

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-12 08:03:26 -08:00
Kerry Lu
6d17f8bff5
feat(model-catalog): read output_cost_per_billable_unit in the rust registry check (#51365)
* feat(model-catalog): read output_cost_per_billable_unit in the rust registry check

* fix(model-catalog): add billable_unit to the flat key schema pattern

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-12 07:59:03 -08:00
Kerry Lu
2c7a36abe9
fix(rate-limit): hold router fallbacks, nested calls, WebSocket turns and oversized prompts to a borrowing team's model share (#51367) 2026-11-12 07:52:01 -08:00
Kerry Lu
c0114e55aa
feat(dynamic_rate_limiter_v3): priority_class_share_splits_equally_among_active_teams splits a priority class share among the teams active on a model (#51366)
* feat(dynamic_rate_limiter_v3): split a priority class share equally among the teams active on a model

Adds priority_reservation_settings.priority_class_share_splits_equally_among_active_teams (default false).
When on, the class weights are renormalized over the classes with a team active on the model in the
current window, each class share is divided equally among its active teams, and once the model saturates
each team is held to its own share with a 429 whose rate_limited_by header reads
priority_class_share_saturated. Active teams are one Redis sorted set per model with the window as TTL.

* fix(proxy): keep basedpyright counts flat for the priority class share setting

PriorityReservationSettings is built with model_validate instead of **value, so a new
field no longer adds an unknown-argument diagnostic, and the limiter unit test helpers
type the handler by its implementation class.

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

* fix(dynamic_rate_limiter_v3): keep team shares usable when Redis, mixed-priority keys, WebSocket turns or tiny shares get in the way

A registry failure falls back to the class pool instead of skipping the model limits, a team's
counter is keyed by model and team so keys of different priorities spend one share, Responses
WebSocket turns are held to the class share like the team model limit, a share is floored at one
request and one token, and the registry declares the rate limits key family.

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

* fix(dynamic_rate_limiter_v3): catch only Redis and validation errors when the active team registry fails

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

* fix(dynamic_rate_limiter_v3): keys without a team keep their class present, team ids may contain the registry separator, breaker-open counts as a registry failure

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

* fix(dynamic_rate_limiter_v3): per-class pool markers that take no team slice, collision-free model and team counter keys, local registry pruned by last-seen time

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

* fix(dynamic_rate_limiter_v3): settle tokens only on the stash the call owns, keep a configured class named default apart from teams with no priority

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

* fix(dynamic_rate_limiter_v3): tag team registry members and count every unenforced request against the team share

A team id can no longer spell a pool marker, since real teams are stored under a team prefix. Below saturation the team share counter is bumped through the atomic increment at the model-wide limit, so the local over-limit short-circuit never skips a request and the counter is complete when the share grows.

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

* fix(dynamic_rate_limiter_v3): list the caller under its own class and resync the socket stash share on every frame

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

* fix(dynamic_rate_limiter_v3): keep the connection share across a registry outage and settle fallback tokens on the served model's team counter

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-12 06:38:07 -08:00
Kerry Lu
52d1c4cd80
feat(dynamic_rate_limiter_v3): steer flexible teams to a permitted alternate when their preferred model is saturated (#51350)
* feat(router): steer flexible teams to a permitted alternate model when their preferred model is saturated

Teams opt in with metadata.model_preferences: [{"model": "luna", "alternates": ["opus"]}].
The dynamic rate limiter v3 pre-call hook reads the saturation cache for the preferred model
and, once it is at or above saturation_threshold, rewrites the request to the first alternate
the team may call that is below the threshold. The response carries
x-litellm-model-steered-from with the preferred model. Rate limits and spend are charged to
the model that served. Teams without model_preferences are untouched.

* fix(dynamic_rate_limiter_v3): type the hook request dicts so the steering rewrite passes the lint gates

* fix(dynamic_rate_limiter_v3): authorize alternates against the key and its model budget, send the steering header on streams

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

* fix(dynamic_rate_limiter_v3): enforce member and project allowlists on alternates, skip the steering header for non Latin-1 names

* fix(dynamic_rate_limiter_v3): check every budget layer for an alternate, fall back to the preferred model when the alternate refuses, merge the served model's guardrails

* fix(dynamic_rate_limiter_v3): never steer agent keys, type the steering unit tests

* fix(dynamic_rate_limiter_v3): hold alternates to customer grants, access group budgets, guardrails and the zero-cost waiver

An alternate now passes every check a budget fallback passes. Customer model grants are enforced, the access groups that grant the alternate are budget checked and stamped on the token and request metadata so spend charges them, a zero-cost preferred model that waived the aggregate budget checks is never steered to a priced alternate, and an alternate whose deployments add model-level guardrails the request did not run is skipped.

* fix(dynamic_rate_limiter_v3): stamp the alternate's groups on every bucket and skip alternates the request cannot safely take

The served model's access groups now land on every metadata bucket and the auth copy inside it even when the preferred model had none, so group spend is charged. An alternate is skipped when a budget reservation already holds a different group, when it does not serve the request's endpoint, when unpriced models are blocked and it has no price, or when its guardrails differ from the request's by name or settings. A failing alternate check or alternate admission leaves the request on the preferred model instead of failing it.

* fix(dynamic_rate_limiter_v3): keep a reserved budget off pricier alternates

While the request holds a budget reservation sized for the preferred model, an alternate that costs more per token is skipped, so fail-closed budget enforcement never admits a call the reservation does not cover.

* fix(dynamic_rate_limiter_v3): an unpriced alternate never takes a reserved budget

While a budget reservation is held, an alternate with no input or output price is skipped rather than treated as free.

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-12 09:10:23 +00:00
yuneng-jiang
1677c2250e
ci: stop PR runs from thrashing the Actions cache and stop smoke compiling from source (#51343)
Some checks failed
unit / guardrails-hooks (push) Blocked by required conditions
unit / jwt-and-keys (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 / proxy-utils (push) Blocked by required conditions
unit / rust-bridge (push) Waiting to run
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.0.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
Issue label sync / sync-issue-labels-tests (push) Has been cancelled
Issue label sync / sync-issue-labels (push) Has been cancelled
Terraform Modules / fmt, validate, test (aws) (push) Has been cancelled
Terraform Modules / fmt, validate, test (gcp) (push) Has been cancelled
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled
VS Code Extension / vscode-extension (push) Has been cancelled
* ci: stop PR runs from writing Actions caches and remove the sccache GitHub cache backend

PR runs were saving their own cache copies and sccache was making one cache
request per compiled file, about 700 to 2000 per Rust build. Together they
pushed the repository past GitHub's cache storage and request limits, so
restores failed with 429 or found nothing and jobs rebuilt from scratch. The
rust-bridge job that gates every unit shard went from 0% cache misses on
Oct 21 to about 65% on Nov 11.

Caches are now saved only from main and restored everywhere else. The Rust DB
cache gets the same save-if guard the other Rust jobs already have. The lint
python and ui-api-types jobs restore the uv cache that the unit workflow saves
on main, because lint only runs on pull requests and could never save a cache
that another pull request can read. The Terraform provider jobs turn off
setup-go's built-in cache, which saves from every ref, and use a new
cache-go-build action that follows the existing restore-everywhere and
save-on-main pattern.

sccache is removed from the unit, Rust and Rust DB workflows. The Cargo target
directory caches they already restore stay in place.

The smoke diagnostics upload no longer fails a job whose checks passed.

* ci(smoke): install the prebuilt Rust bridge and cache wheels built from source

Every smoke job compiled the Rust bridge inside uv sync, about 4.5 minutes each
in 8 jobs per run, and the Python 3.14 and 3.15 jobs also compiled grpcio-tools,
numpy and jiter from source on every run because no wheels exist for those
versions yet. Smoke only ran on pull requests, so nothing it built could be
saved for the next run.

The Linux smoke jobs now restore the bridge that the unit workflow already
builds once and saves on main. The bridge is abi3, so the same build serves
every Python version. When the restore misses, which happens when a pull
request changes a Rust build input, the job restores the Cargo build cache and
compiles as before. The cache key and restore step move into a
restore-rust-bridge action that the unit workflow's producer job also uses, so
the key is defined once.

Smoke now also runs on pushes to main and prunes the uv cache down to wheels
built from source before setup-uv saves it. Pull requests restore that cache,
so 3.14 and 3.15 stop recompiling the same packages.

The windows-sdk job drops its Cargo cache step. Nothing ever saved a Windows
copy, and saving one from main would add about 2 GB to a cache that is already
near its limit.
2026-11-11 23:52:32 -08:00
devin-ai-integration[bot]
db8ad3b13e
fix(gemini): add batch and priority image output token prices to gemini/gemini-2.5-flash-image (#51368)
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-12 06:59:37 +00:00
Kerry Lu
ece1484bc7
feat(rate-limit): team_model_limit_max_borrow_multiple caps how far a team borrows idle model capacity (#51359)
* feat(rate-limit): team_model_limit_max_borrow_multiple caps how far a team borrows idle model capacity

With team_model_limit_borrows_idle_capacity_from_other_teams on, a team could
use everything up to the model's own tpm and rpm while the model was idle.
Setting team_model_limit_max_borrow_multiple in the team's metadata caps the
team at its model_tpm_limit and model_rpm_limit times that multiple, with a 429
whose rate_limited_by header reads model_per_team_max_borrow_multiple. Unset
keeps the previous behavior. Values below 1 or non-numeric are logged and
ignored.

* fix(rate-limit): reject infinite or oversized team_model_limit_max_borrow_multiple values instead of failing the rate check

* fix(team): make team_model_limit_max_borrow_multiple proxy-controlled team metadata so team admins cannot lift their own cap

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-12 06:55:59 +00:00
devin-ai-integration[bot]
3d389262a4
test(providers): expect Rust routing for idle Messages streams (#51364)
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-12 06:24:23 +00:00
kenneth-berri
4cd8f9174d
fix(anthropic): close strict JSON schemas on the Messages to Responses bridge (#51337)
* fix(anthropic): close strict JSON schemas on the Messages to Responses bridge

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

* perf(anthropic): copy each strict schema subtree once

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

* fix(anthropic): close nullable objects in strict JSON schemas

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-12 06:15:14 +00:00
Kerry Lu
7c7e02b06f
fix(fal_ai): give MiniMax H3 Max video endpoints their own resolution profile (#51348)
* fix(fal_ai): give MiniMax H3 Max video endpoints their own resolution profile

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

* test(fal_ai): keep the H3 Max test signature on one line

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

* test(fal_ai): type the H3 Max test locals and signatures

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-12 05:05:41 +00:00
Kerry Lu
19ffe3a5f2
feat(rate-limit): team_model_limit_borrows_idle_capacity_from_other_teams lets a team's model TPM and RPM limits borrow idle capacity in the dynamic rate limiter (#51328)
* feat(rate-limit): opt-in team_model_limit_is_floor turns a team's model_tpm_limit into a guaranteed share that borrows idle capacity

With priority_reservation_settings.team_model_limit_is_floor: true, a team's inherited model_tpm_limit on a model the dynamic rate limiter covers stops being a hard cap in the regular limiter. The dynamic limiter tracks it as a team_model_floor descriptor, lets the team past it while the model is below saturation_threshold, and holds the team to it once the model saturates, with rate_limited_by: team_model_floor on the 429. Default false keeps today's behavior. Key-owned model_tpm_limit, model_rpm_limit, the model-wide tpm and customer limits stay hard.

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

* feat(rate-limit): name the setting team_model_limit_mode with hard and soft values

soft replaces the team_model_limit_is_floor flag. The 429 header reads rate_limited_by: team_model_soft_limit and the message says the team soft limit was reached.

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

* feat(rate-limit): team_model_limit_borrows_idle_capacity_from_other_teams covers team model TPM and RPM

Replaces team_model_limit_mode. A team's inherited model_tpm_limit and model_rpm_limit on a model the dynamic
rate limiter covers are enforced by that limiter once the model saturates, so the team borrows idle capacity below
the threshold. The 429 names the limit and reads rate_limited_by: model_per_team_saturated.

Folds in the bot findings: key ownership is checked per requested model, team limits resolve through the model
alias like the regular limiter, the team counter is charged only for requests the pre-call hook tracked, every
response type that carries usage is settled, and tracking below saturation no longer adds a token per request.

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

* fix(rate-limit): keep the type gate flat in the dynamic limiter's team limit paths

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

* fix(rate-limit): settle responses usage and null auth metadata, resolve aliases for the borrowing team limit

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

* fix(rate-limit): key the borrowing team counter by resolved model group and read usage through get_usage_object

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

* fix(rate-limit): hold Responses WebSocket turns to the borrowing team limit once the model saturates

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-12 04:58:34 +00:00
moe-berri
6a98d09261
fix(alerts): stop detecting traffic drops (#51338) 2026-11-11 20:22:24 -08:00
Yassin Kortam
61944def17
ci: fail when the repo root grows past 15 entries (#51007)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-12 04:19:58 +00:00
Yassin Kortam
adc4c01aa9
chore: move scripts and runtime sources under litellm (#51006)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-12 04:19:57 +00:00
Yassin Kortam
db6dfb6bb6
chore: move workspace packages under litellm (#51000)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-12 04:19:57 +00:00
Yassin Kortam
f6bee81c73
chore: move ui and vscode-extension under litellm and delete docs (#50992)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-12 04:19:57 +00:00
Yassin Kortam
6efbaf539c
chore: move deployment sources under litellm/deploy (#50981)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-12 04:19:56 +00:00
Mateo Wang
a3c52e69d3
ci(integration): install the Rust gateway and fail forwarded_proxy when Rust is not in front (#51307)
* ci(integration): install the Rust gateway and fail forwarded_proxy when Rust is not in front

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

* ci(integration): build the Rust gateway only for the management suites that run forwarded_proxy

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-12 04:07:07 +00:00
devin-ai-integration[bot]
454424c36c
test(observability): include CollieAi in guardrail scope settings (#51345)
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-12 04:01:47 +00:00
devin-ai-integration[bot]
66656609fa
test(providers): expect Rust routing for quiet Messages streams (#51346)
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-12 04:01:28 +00:00
yuneng-jiang
2d8fd9b384
fix(cost-map): drop the duplicate gemini/gemini-decision-1 entry (#51341)
Two PRs each added gemini/gemini-decision-1 to the price map, so main defines the key twice and test_price_map_has_no_duplicate_keys fails in the root unit job for every PR that merges main. The two entries hold the same fields and values, so this removes the earlier one and leaves the one JSON parsers already keep. The parsed map is unchanged.
2026-11-11 19:42:29 -08:00
mrinal-berri
c96ca27afc
fix(proxy): reject unknown guardrail names on key, user, team and project writes (#51080)
* fix(proxy): reject unknown guardrail names on key and user writes

Key generate, update, regenerate, team bulk update and /user/new now return 400 for a guardrail name that no loaded guardrail answers to and that has no active DB row, instead of storing it so requests run unguarded. Names the key already holds are not rechecked, and the key endpoints keep the enterprise license 403 ahead of the name check.

Resolves LIT-10613

* fix(proxy): read guardrail names from the writer when validating key writes

A guardrail created on another pod may not have reached a lagging read replica yet, so the fallback lookup now reads the writer and a just-created guardrail is not rejected as unknown. Other guardrail table reads keep using the reader.

* feat(proxy): validate guardrail names on team and project writes, reject malformed values loudly

Team and project create and update now return 400 for guardrail names that no loaded guardrail answers to and that have no active DB row, the same check key and user writes use. A metadata.guardrails value that is not a list of names is rejected even when it equals the stored value, with a short message naming the value. The lookup and the license-first wrapper move into common_utils so every entity shares them.

* refactor(proxy): one validate_guardrail_names entry point for guardrail name checks

The name check, its DB lookup and the license-first option now live in litellm/proxy/guardrails/guardrail_name_validation.py and every key, user, team and project write calls validate_guardrail_names directly. The lookup factory, both wrappers and the per-endpoint helpers are gone, and common_utils and guardrail_registry are back to their main versions. Behavior is unchanged.

* fix(proxy): chunk the guardrail lookup and leave legacy dict guardrail settings alone

The guardrail name lookup goes through find_many_in so its IN list has a written bound, and the project test keeps pytest.raises to one statement. A dict-shaped metadata.guardrails, a team's documented {"modify_guardrails": false} or the v1 {name: bool} toggle map, is read at request time, so validation leaves it alone instead of rejecting it.

* fix(proxy): only let the team modify_guardrails permission through as a dict

Stored key, team and project guardrails only run when metadata.guardrails is a list, so a dict such as {"config-guard": true} saved fine and attached nothing. The one stored dict with an effect is a team's documented {"modify_guardrails": bool}, which auth checks read, so that exact shape passes and every other dict gets the Invalid guardrail 400.

* refactor(proxy): move guardrail name validation into guardrail_helpers

validate_guardrail_names and its helpers now live in litellm/proxy/guardrails/guardrail_helpers.py next to the other guardrail helpers, and guardrail_name_validation.py is removed. guardrail_helpers imports LiteLLM_TeamTable and UserAPIKeyAuth from litellm.proxy._types instead of litellm.proxy.proxy_server, so the endpoints can import it at module level without pulling in the proxy server, and its dead sys.path line is dropped. Behavior is unchanged.
2026-11-11 19:28:45 -08:00
Mateo Wang
7461a996cc
feat(gemini): support gemini-decision-1 on /v1/decisions and /v1/systemone (#51220)
* feat(gemini): add Decisions API support

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

* fix(gemini): add Decisions quotas and test package marker

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

* fix(gemini): bound decisions image downloads

* fix(gemini): preserve image order and bounded cache reuse

* test(gemini): cover cached image budgets and text messages

* refactor(gemini): iterate bounded image downloads

* fix(gemini): require explicit keys for decisions base overrides

* refactor(gemini): infer downloaded image types

* test(proxy): accept either metrics startup failure on occupied ports

* fix(llms-types): remove duplicate compact beta match arm

* fix(gemini): validate caller credentials before merging deployment keys

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-11 18:33:22 -08:00
Yassin Kortam
b0e389503f
chore: move config and docs off the repo root (#50966)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-11 18:29:35 -08:00
tin-berri
45a31852a3
feat(skills): add admin-only version restore (#51309)
* feat(skills): add admin-only version restore

* fix(skills): retain verified scan evidence when restoring
2026-11-11 18:25:07 -08:00
moe-berri
baf5718b47
feat(alerts): enable monitoring by default with a five-alert free allowance (#51123)
* feat(alerts): add default-on monitoring and a five-alert free allowance

* fix(alerts): handle monitoring races and retry-safe migration

* fix(ui): match monitoring upgrade link to status
2026-11-11 18:23:03 -08:00
moe-berri
16a36c66b4
feat(ui): show performance alerts in the Alerts page (#51104)
* feat(ui): restore performance alerts on updated detector

* fix(ui): keep team key detail tabs independent

* fix(ui): preserve team tabs while permissions load

* feat(ui): show concise alerts in the notification bell

* refactor(ui): remove replaced deprecation bell item

* fix(ui): pin notification resource links

* fix(ui): show deprecation failures in notifications

* fix(ui): reset activity state when changing resources

* fix(ui): reset key detail state from every entry point
2026-11-11 18:23:03 -08:00
moe-berri
701639ea22
feat(alerts): detect daily changes in recorded usage (#50666)
* feat(alerts): detect daily changes in usage for gateway, models, teams, users and keys

* fix(alerts): drop unused suppressions

* fix(alerts): satisfy strict lint for model usage timing

* fix(alerts): skip only resources that time out or hold invalid data

* fix(alerts): log an unfinished daily sweep at info level

* fix(alerts): keep the deployment-wide run every five minutes

* fix(alerts): record alert and sweep timestamps in UTC

* fix(alerts): repair detector tests and tracing

* refactor(alerts): build the key index in a migration instead of after startup

* refactor(alerts): build the key index after startup again

A concurrent index build inside the boot migration can exceed the 600 second migrate deploy deadline on a large LiteLLM_DailyUserSpend and stop the proxy from starting. This reverts 393db6e4e0.

* fix(alerts): keep keys pending, bound the accounting wait, resume unfinished scopes

Keys waiting for their index no longer mark the day complete. Only deliveries created during the evaluated day hold back its sweep. A day that did not finish restarts the next day from where it stopped instead of a hash boundary that never rotated non-hex IDs.

* refactor(alerts): drop the pending delivery wait, alerts are best effort on flushed usage

* test(alerts): cover detector persistence and recovery

* fix(alerts): recognize Prisma statement timeouts
2026-11-11 18:23:02 -08:00
moe-berri
26971a8f1f
feat(alerts): add persisted alerts and status APIs (#50658)
* feat(alerts): add persisted alerts and status APIs

* fix(alerts): preserve string IDs and complete backend wiring

* feat(alerts): support whole-gateway alert scope
2026-11-11 18:23:02 -08:00
moe-berri
b5a70cbc18
feat(ui): add Activity tabs to resource details (#50630)
* feat(ui): add Activity tabs to resource details

* fix(ui): preserve Activity dates and model breakdowns

* fix(ui): clarify team activity visibility

* fix(ui): skip unused entity breakdown in model activity
2026-11-11 18:23:01 -08:00
jesus-berri
d40dedf23b
feat(proxy): run spend log cleanup from a DB-backed scheduled job table (#50131)
* feat(proxy): run spend log cleanup from a DB-backed scheduled job table

Spend log retention ran from an in-process APScheduler timer that reset on
every restart and needed Redis for leader election, so pods that restart
more often than the interval never cleaned up at all.

Add LiteLLM_ScheduledJobRun, a generic table with one row per job run.
Every pod polls it, plans the next run idempotently and claims a due run
under a transaction-scoped advisory lock, so exactly one pod runs each
occurrence. Runs carry a lease, a status and a log. Intervals count from
the last finished run and survive restarts. Schedule edits cancel and
replace the pending run. The retention and size-check jobs share a lock
group so they never overlap, and the Redis pod lock is gone.

* fix(proxy): plan an earlier cron edit from now, and cover the job runner

Moving the cleanup cron earlier queued tomorrow's run because the next
occurrence was planned after the pending run being cancelled. Plan from
finished runs only.

Register LiteLLM_ScheduledJobRun as a span table, keep the advisory lock
query inside the tick's span, move the non-string cron startup test to
the schedule parser, and add unit tests for the runner and job wiring.

* test(proxy): annotate new spend log cleanup test locals as Final

* test(proxy): type the parameters of the new spend log cleanup tests

* test(proxy): leave an hour of margin when moving a cron earlier so an hour boundary cannot flake it

* fix(proxy): reject a cron that never fires instead of failing every tick

A crontab like 0 0 31 2 * parses but has no next fire time, so next_run_at raised on every tick and the job was never scheduled again. cron_schedule now returns InvalidSchedule for it, which logs and keeps the pending run like any other bad config

* refactor(proxy): drop the informational log column from LiteLLM_ScheduledJobRun

Nothing read the column back. Run outcomes stay in status, and the reasons
already go to the proxy logs, so JobResult becomes a SUCCEEDED or FAILED
literal and the cleanup returns its RunOutcome instead of a summary report
2026-11-11 18:22:02 -08:00
devin-ai-integration[bot]
e7fd0d6cba
feat(gemini): add gemini-decision-1, the Decisions API model priced at $0.10 per 1M input tokens (#51215)
* feat(gemini): add gemini-decision-1, the Decisions API model priced at $0.10 per 1M input tokens

* fix(cost-map): give gemini-decision-1 its Tier 1 rpm and tpm so test_get_model_info_gemini passes

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-12 02:12:28 +00:00
Mateo Wang
6b98361765
test(integration): remove the openapi docs gate detached worktree test that cannot run offline (#51300)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-12 11:04:20 +09:00
Kerry Lu
23354ad08f
ci(unit): deselect test_unit_test_proxy_hooks from the q-z shard it actually matches, keep the dead proxy-infra-root codecov flag pinned (#51324)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-12 01:59:21 +00:00
Kerry Lu
801e139c35
fix(gemini): attribute /gemini batchGenerateContent batches to the calling key (#51279)
* fix(gemini): attribute /gemini batchGenerateContent batches to the calling key

A 200 from models/{model}:batchGenerateContent through the Gemini pass-through now registers a managed
batch row for the calling key, team and tags, so CheckBatchCost prices the output file on completion
instead of the job landing as a $0 pass_through_endpoint row with model unknown.

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

* test(gemini): unit regression for pass-through batch registration

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

* test(integration): file batch created through /gemini is registered for the calling key

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

* fix(gemini): bill pass-through batches through the configured gemini deployment

The managed row now points at the gemini/<model> deployment CheckBatchCost fetches the output with, not
the first router id for the model name, and a model with no gemini deployment logs a warning instead of
registering a row that can never be billed. A key without a user keeps user_id unset rather than
default-user. Typed the registration helper and handler so the lint gates stay flat.

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-11 17:39:27 -08:00