Commit graph

50888 commits

Author SHA1 Message Date
mateo-berri
8005856411 ci(build_and_test): seed the routing strategy through /config/update 2026-09-18 16:03:27 -07:00
mateo-berri
d057e82e64 test(proxy): assert stored login throttle limits never outrank the config file 2026-09-18 15:13:51 -07:00
mateo-berri
e0a74dabd1 Merge remote-tracking branch 'origin/main' into litellm_config_update_rejects_config_owned_keys 2026-09-18 14:51:11 -07:00
yujonglee
b0b2f13548
Merge pull request #41873 from BerriAI/litellm_rust_exception_type_port
feat(rust): port exception_type to litellm-core-utils
2026-09-18 14:17:17 -07:00
Mateo Wang
59c24abcbe
Merge pull request #33101 from BerriAI/litellm_fix_responses_ws_litellm_params_leak
fix(responses): stop managed Responses WebSocket from leaking litellm_params into provider request body
2026-09-18 14:11:19 -07:00
Yassin Kortam
6759f28e73
Merge pull request #41557 from BerriAI/litellm_azure_speech_passthrough
feat(proxy): add Azure AI Speech pass-through route
2026-09-18 14:05:55 -07:00
ryan-crabbe-berri
006080ea6d
Merge pull request #41349 from BerriAI/litellm_team_member_spend_without_budget
fix(proxy): track team member spend when the member has no budget
2026-09-18 14:04:39 -07:00
Yujong Lee
9d134413c9 test(rust): rename the standalone 429 test to match the rule 2026-09-18 14:01:09 -07:00
mateo-berri
6449632c7b fix(proxy): persist only the router settings keys the request set 2026-09-18 14:01:05 -07:00
Yujong Lee
2806d2546c Merge remote-tracking branch 'origin/main' into litellm_rust_exception_type_port 2026-09-18 21:00:11 +00:00
Yujong Lee
836bf7d897 refactor(rust): make the exception mapper a pure rule table
Rebuild exception_type around text rules per provider family and one shared
status table. The mapper takes the context, an injected redactor and the
original failure, and returns a PublicError with the message, the real
upstream response and the debug text. Every divergence from the Python
mapper and every known gap is listed in the module header

Match Python on a standalone 429 with an unknown status and on Cohere's
rules for failures without a status. Drop python_repr and the unread
public_failures fixtures
2026-09-18 13:59:36 -07:00
Mateo Wang
c553bc92bd
Merge pull request #41875 from BerriAI/litellm_passthrough_stream_timeout
fix(router): honor stream_timeout on the SDK-native passthrough route (/v1/messages, /converse)
2026-09-18 13:59:02 -07:00
Mateo Wang
20e1e6f2a9
Merge pull request #41384 from BerriAI/litellm_fix_azure_vector_store_search_url
fix(azure): keep api-version query after vector store search path
2026-09-18 13:56:05 -07:00
ryan-crabbe-berri
c21e86a443 feat(ui): reset a team member's spend from the Members tab
Every member now carries a membership row, so a member who spent with no budget is over budget the moment a member budget is added later. The only fix was POST /team/{team_id}/member/{user_id}/reset_spend, which had no UI.

The Members tab gets a Reset spend action on rows that have current cycle spend. It confirms in a dialog, posts reset_to 0 through the typed client, and refreshes the team without remounting the page so the tab stays open. A team admin does not see it on their own row because the backend rejects that reset
2026-09-18 13:54:28 -07:00
ryan
61d4c5b9b5 fix(proxy): skip members already on the team before resolving a per-member budget
A mixed /team/member_add list that names an existing member used to run
add_new_member for them, which created or cloned a budget that the empty
upsert update branch never linked to their membership row. Filter the
requested members against the freshly locked roster first so budgets and
membership rows are only written for members who are actually new

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 13:54:17 -07:00
ryan
b6f4ad190e refactor(proxy): freeze the member spend arrays and budget link to stay within the type discipline budget
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 13:54:17 -07:00
ryan
2d61fa66b1 fix(proxy): lock teams in sorted team id order and keep existing member budgets on re-add
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 13:54:17 -07:00
ryan
499c334fce fix(proxy): lock each team in sorted order before the member spend upsert
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 13:54:17 -07:00
ryan
60077e90aa fix(proxy): take the team advisory lock before the member spend flush
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 13:54:17 -07:00
ryan
efef6ab684 fix(proxy): write team member spend as one roster checked upsert statement
Replaces the per team advisory lock and Pydantic roster parse in the spend flush
with a single INSERT ... ON CONFLICT statement that checks the stored roster in
SQL, so malformed roster JSON cannot fail the whole flush and large batches no
longer issue two queries per team inside the fixed transaction deadline

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 13:54:16 -07:00
ryan
7c068a4cf7 fix(repositories): import LiteralString from typing_extensions for python 3.10
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 13:54:16 -07:00
ryan
6dce85c728 fix(proxy): skip recreating membership rows for members removed before a spend flush
Take the team advisory lock in the spend flush transaction and read the roster
through it, so a delayed flush after /team/member_delete cannot recreate the
deleted LiteLLM_TeamMembership row. TEAM_ADVISORY_LOCK_SQL moves to
team_repository so the spend writer can import it without a circular import

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 13:54:16 -07:00
ryan
d9b48ac941 test(proxy): mock team membership upsert in team admin member add test
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 13:54:16 -07:00
ryan
de4b520153 fix(proxy): track team member spend when the member has no budget
add_new_member only wrote a LiteLLM_TeamMembership row when a budget id
resolved, and the spend writer used update_many so a missing row failed
silently. Members without a budget therefore never accrued per-member spend.

The membership row is now always upserted (budget_id NULL when no budget
applies) and the spend write is an upsert so members added before this fix
start accruing on their next request.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 13:54:16 -07:00
kerry-berri
6fa34a299b
Merge pull request #41754 from BerriAI/litellm_qwen3_8_omni_flash
feat(models): add qwen3.8 flash rows, fix Cohere embed v3 context, Bedrock Mantle and OpenRouter pricing
2026-09-18 13:49:35 -07:00
Yassin Kortam
6e750e3d24
Merge pull request #41725 from BerriAI/litellm_mcp_admin_terminate_sessions_revoke_credentials
feat(mcp): let proxy admins force-close live MCP sessions and revoke stored user credentials
2026-09-18 13:44:37 -07:00
Mateo Wang
194e647e64
Merge pull request #41865 from BerriAI/litellm_vscode_extension
feat(vscode): add LiteLLM language model provider extension
2026-09-18 13:40:35 -07:00
mateo-berri
e75ad61fce fix(router): rank litellm_settings.request_timeout on the passthrough route like the completion route 2026-09-18 13:32:43 -07:00
yassin
57fd311f3a Merge remote-tracking branch 'origin/main' into litellm_azure_speech_passthrough 2026-09-18 20:32:32 +00:00
yassin
737c6e1b2d Merge remote-tracking branch 'origin/main' into litellm_mcp_admin_terminate_sessions_revoke_credentials
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

# Conflicts:
#	tests/test_litellm/proxy/config_resolvers/test_settings_store.py
2026-09-18 20:32:31 +00:00
mateo-berri
c984c329fe Merge remote-tracking branch 'origin/main' into litellm_fix_responses_ws_litellm_params_leak 2026-09-18 13:32:23 -07:00
yucheng-berri
176ac4cc2b
Merge pull request #41862 from BerriAI/litellm_settings_store_clear_hang
* fix(proxy): make SettingsStore.clear() terminate when the config file owns a key

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

* test(proxy): compare the cleared settings store against an unmutated refill mapping

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

* fix(proxy): keep the resolved values of config-owned keys across SettingsStore.clear()

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

---------

Co-authored-by: yucheng <yucheng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 13:29:53 -07:00
mateo-berri
eeca8b682b fix(vscode): raise the VS Code minimum to 1.115 for per-model configuration 2026-09-18 13:28:28 -07:00
Yujong Lee
e036e256ef revert: drop redundant anyio lockfile bump
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 20:23:53 +00:00
mateo-berri
acfe9a899a merge main and drop config_data from its two routing group tests 2026-09-18 13:22:54 -07:00
Yujong Lee
2703fab3c8 fix(deps): update anyio for osv scan
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 20:21:21 +00:00
yucheng-berri
88e150bb59
Merge pull request #40982 from BerriAI/litellm_lit5285_login_rate_limit_v2
* feat(proxy): limit repeated failed Admin UI sign-in attempts

The Admin UI sign-in endpoints accept an unbounded number of password attempts.
All three call authenticate_user, and none of them keeps any record of how many
times a given caller has already been refused, so a misbehaving or misconfigured
client can retry indefinitely at full speed.

A LoginThrottle is now a required argument to authenticate_user, so the accounting
lives at the one function all three endpoints share and a fourth endpoint cannot be
added without deciding what to pass. Failures are counted per username and source
address over a fixed window and further attempts are refused with 429 and a
Retry-After header. The check runs before the database lookup and before the
password comparison, so a refused caller does no further work.

Only genuine credential rejections count. Configuration errors do not, a refused
attempt does not extend the window, and a successful sign-in clears the bucket.
The username is case folded because the user lookup is case insensitive, so casing
cannot multiply the allowance. Both credential rejections now return one identical
message. SSO is unaffected; it never calls this function.

max_failed_login_attempts (10) and failed_login_window_seconds (900) are read from
config.yaml, with LITELLM_DISABLE_LOGIN_RATE_LIMIT to turn the accounting off. They
are deliberately not database backed, so editing YAML always wins and an operator
refused by a bad value can recover.

* test(proxy): clear failed-login TTLs when resetting the throttle between tests

* feat(proxy): harden Admin UI login throttling

* docs(proxy): clarify login throttle configuration

* fix(proxy): keep the login throttle inside the type budget and fail safe on secret errors

* fix(proxy): warn about per-worker sign-in counters without a module global

* fix: honor environment login throttle settings

* fix: satisfy login setting type checks

* refactor(proxy): keep authenticate_user within the C901 budget after merge

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

* fix(proxy): write failed-login counters and their expiry in one Redis call

Use RedisCache.async_increment_with_floor (a single Lua INCRBY + EXPIRE) for the
shared login counters instead of the two-step INCRBYFLOAT then EXPIRE, so a
counter can never be committed to Redis without its expiry. The repair in
_remaining_window now only covers expiries stripped out of band (PERSIST, a
restore) and uses the same atomic call

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

* fix(proxy): resolve the login rate limit kill switch once per process

Reading LITELLM_DISABLE_LOGIN_RATE_LIMIT through get_secret_bool on every
unauthenticated sign-in attempt meant a hosted secret manager in read mode
was queried once per password guess, before any counter was checked

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

* fix(proxy): count failed sign-ins in Redis alone while it answers

Every worker spends one shared budget and a successful sign-in clears it for all
of them. This worker's own counter is only consulted while Redis raises, so an
outage degrades to per-worker accounting instead of switching the control off

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

* fix(proxy): keep counting the failed sign-ins Redis missed once it answers again

A guess is recorded in exactly one place, Redis or this worker's own store when Redis
refused it, so the count is the sum of the two. Redis is read through
async_batch_get_counts, which raises on failure, instead of async_get_cache, which
swallows it into None and read as an empty counter

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

* refactor(proxy): read the shared sign-in counter through a tuple of keys

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

* refactor(caching): spell out the key collections batch_get_counts accepts

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

* fix(proxy): warn about per-worker login counters even without general_settings

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

* chore(ui): regenerate schema.d.ts after merging main

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

* refactor(proxy): inject settings and Redis cache into LoginThrottle.from_request

Removes the runtime import of proxy_server from login_throttle so the
throttle module no longer participates in the import cycle CodeQL
flagged (py/cyclic-import). Callers pass general_settings and
redis_usage_cache explicitly; behavior is unchanged.

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

* test(proxy): drop the section banner comment from the login tests

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

* refactor(proxy): assert separate login counter stores in the spray regression test instead of a comment

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

* feat(proxy): throttle failed Admin UI sign-ins per source and source/username

Replace the username-global lockout with counters keyed by source address and by
source/username pair. Each has a fixed counting window (60s) and a separate
block TTL (300s). Blocks are soft: a correct password still signs in, wrong
passwords from a blocked key take one of 5 held slots per worker and are held
30s before a 429. Once a pair is blocked its failures stop counting against the
source. The source scope runs only when trusted_proxy_ranges is set, IPv6 is
grouped by /64, and per-source limits accept IP and CIDR overrides with
longest-prefix matching. Redis is authoritative through one Lua script per
failure, with bounded per-worker fallback when Redis raises.

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

* test(proxy): explain the internal patches in the login throttle tests

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

* fix(proxy): key held sign-in attempts on the source while the source is blocked

An active source block now takes precedence over a pair block, so every blocked username behind one blocked source shares the source's five held slots instead of getting five each

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

* fix(proxy): apply source overrides to IPv4-mapped IPv6 sign-in sources

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

* fix(proxy): catch only Redis failures when falling back to local login counters

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

* fix(proxy): type the login throttle's local store and pass frozen Redis script arguments

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

* fix(proxy): keep the sign-in hold pool from refusing a correct password

The held-attempt cap ran before the password check, so five parked wrong
guesses from a blocked source turned the soft block into a lockout for the
real user. The slot is now taken only after a wrong password, and the
pool-full refusal carries the block's remaining time as Retry-After

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

* test(proxy): stub DATABASE_URL in the hold-pool regression test so it passes off the dev box

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

* chore(proxy): drop a comment that restated the NUM_WORKERS assignment

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

* fix(proxy): match IPv4-mapped IPv6 peers against IPv4 trusted proxy ranges

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

* fix(proxy): keep mapped-notation trusted proxy ranges matching mapped peers

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

* feat(proxy): hard-block throttled Admin UI sign-ins with no credential bypass

A blocked source, or source and username pair, is now refused with 429 before the database lookup and password check, in place of the soft block that held wrong guesses for 30 seconds and let a correct password through. The env admin credentials and the master key typed into the login form are refused like any other credential while blocked; recovery is the master key as an API bearer token, which never goes through the sign-in path

trusted_proxy_ranges: [] now means clients connect directly, so the peer address is the source and the per-source limit stays on. Only an unset or malformed value leaves the topology unknown, warns at startup and turns the per-source limit off

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

* refactor(proxy): move login throttle sentinels into constants

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

* feat(proxy): derive the per-username sign-in allowance from the address limit

The per-address-and-username allowance is now half the effective address allowance, rounded up, instead of a separate max_failed_login_attempts_per_user setting. A per-address override therefore raises or effectively removes both limits for that address, and no second override table is needed

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

* refactor(proxy): raise the sign-in block explicitly and type the empty settings mapping

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

* feat(proxy): round the per-username sign-in allowance down and exempt an address with an override of 0

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

* test(proxy): import LoginThrottle under TYPE_CHECKING for the throttle helper annotation

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

* fix(proxy): break ties between equivalent login limit overrides deterministically

Two spellings of one network share a prefix length, so the exemption wins the tie, then the higher limit, regardless of mapping order

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

* fix(proxy): treat a malformed trusted_proxy_ranges entry as an undeclared topology

A list with an entry that is not an address or CIDR range no longer switches
the per-source Admin UI sign-in limit on against the direct peer address, so a
typo cannot make a shared ingress address the bucket for every user behind it

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

* fix(proxy): reject blank trusted_proxy_ranges entries before they are dropped

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

---------

Co-authored-by: Yucheng Zhu <yucheng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 13:18:50 -07:00
yucheng
08d0819159 fix(proxy): keep the resolved values of config-owned keys across SettingsStore.clear()
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 20:18:48 +00:00
yucheng
d6b6ab31b7 test(proxy): compare the cleared settings store against an unmutated refill mapping
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 20:18:48 +00:00
yucheng
6464c1fd6c fix(proxy): make SettingsStore.clear() terminate when the config file owns a key
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 20:18:48 +00:00
kerry-berri
8a819c611d
Merge pull request #41846 from BerriAI/dependabot/uv/anyio-4.14.2
chore(deps): bump anyio from 4.13.0 to 4.14.2
2026-09-18 13:18:26 -07:00
Yujong Lee
0f59e61f24 fix(rust): align exception mapping tests with Python
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 20:17:11 +00:00
Yassin Kortam
2e46b10320
Merge pull request #41293 from BerriAI/litellm_usage_key_free_aggregate_split
perf(proxy): split aggregated usage query into key-free rollups and bounded top-N keys
2026-09-18 13:16:49 -07:00
yassin
1feffc3635 Merge remote-tracking branch 'origin/main' into litellm_mcp_admin_terminate_sessions_revoke_credentials 2026-09-18 20:16:22 +00:00
mateo-berri
d16d17d2d2 ci(osv-scan): scan the VS Code extension lockfile 2026-09-18 13:15:17 -07:00
mateo-berri
1581c45615 fix(proxy): check and persist only the settings the request sent
POST /config/update compared litellm_settings after lowercasing the
callback list, so a config file spelling a callback in mixed case refused
the same list sent back, and it stored every general_settings model default
next to the keys the request set. Both now use the request as sent; only
the stored callback list is lowercased.

Also drops config_data from the router settings reload callers the previous
commit left behind and teaches the legacy MockProxyConfig the ownership
check.
2026-09-18 13:14:22 -07:00
ryan-crabbe-berri
6e3b6d6d03
Merge pull request #41830 from BerriAI/litellm_scim_multivalued_optional_value
fix(scim): accept entitlements and roles entries without a value on SCIM user PUT
2026-09-18 13:13:51 -07:00
Devin AI
04193745a6 fix(docs-test): restore line-anchored table regex in router settings check
A merge on this branch dropped the ^ anchor and MULTILINE flag from the
doc_key_pattern, so the unanchored match started swallowing key names
into captured fields whenever a row's description cell itself contains
a pipe (Literal unions and similar). The check then reported 27
long-documented keys as undocumented. Restores the exact pattern used
on main, verified against a live litellm-docs checkout: all 58 Router
init params resolve as documented.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 20:10:20 +00:00
mateo-berri
74e9fb2323 fix(passthrough): validate only the winning timeout value in the resolver 2026-09-18 13:07:33 -07:00
yassin
191ca14872 Merge remote-tracking branch 'origin/main' into litellm_usage_key_free_aggregate_split 2026-09-18 20:05:54 +00:00