Commit graph

40964 commits

Author SHA1 Message Date
Tin Chi Lo
6c74e9b988 feat(ui): grey out client-forwarded and OBO MCP servers on the gateway connect flow
On the aggregate gateway connect flow the client holds only an identity-only session
bearer, and upstream credentials are resolved server-side from the per-user vault, which
is only populated by interactive authorization_code (oauth2). The client-forwarded modes
(true_passthrough, oauth_delegate) need the caller to present the upstream Authorization
per call, and oauth2_token_exchange (OBO) needs the caller's own IdP token as the exchange
subject; the session bearer is neither, so a tool call to those servers can never complete
on this connection.

Rather than let them look connectable and then 401, the grid greys those servers and
labels them "Not supported on this connection" when rendered in connect mode. Outside the
connect flow the normal integrations page is unchanged, since the client forwards its own
token there and those modes work. The classification lives in a shared
isUnsupportedOnGatewayConnect helper next to isClientForwardedTokenMode so the UI gate and
the auth-mode taxonomy cannot drift.
2026-07-16 14:51:43 -07:00
Tin Chi Lo
5ab7a855c3 feat(ui): simplify DCR connect grid, auto-finish on tab close, fix connect-status flash
In the gateway DCR connect flow the apps grid now reads as "authorize your servers"
rather than a chat feature; the connectMode prop drops the Beta badge, the "use in
chat" subtitle, and the tool-count chrome

Closing the connect tab now best-effort finishes the flow via navigator.sendBeacon to
/authorize/complete, so the gateway authorization code still reaches the client's
loopback without an explicit click; the explicit "Finish connecting" button stays as
the reliable path. The beacon is skipped while a per-server authorize is navigating
away and after the button was pressed, so it never double-delivers or fires mid-authorize

Authorized servers previously flashed "Connect" for a second before flipping to
"Connected" because the per-user credential checks ran only after the whole tool-count
fetch finished. They now fire in parallel with the tool-count load, and each card shows
a skeleton in the button slot until its status resolves, so the state never flips under
the user
2026-07-16 14:51:43 -07:00
Tin Chi Lo
262e93ec27 feat(ui): connect-flow interlude banner on the MCP apps grid for gateway DCR sign-in 2026-07-16 14:51:43 -07:00
Tin Chi Lo
5df5fa50fa fix(mcp): gate the team-grant union on an admission marker, not api_key absence
A JWT-authenticated caller is also keyless with a user_id and, absent a team claim, no team_id, so gating the multi-team union on api_key-is-None silently broadened JWT MCP access to the union of every team the user belongs to. _reload_admitted_user now stamps MCP_ADMITTED_USER_SUBJECT_METADATA and the union fires only for that positive marker, so the gateway session and bridge user paths union while JWT and other keyless auth keep their prior behavior. Regression-tested.
2026-07-16 14:51:43 -07:00
Tin Chi Lo
b82fb75292 feat(mcp): union team-inherited MCP grants across all of a user's teams for keyless admission 2026-07-16 14:51:43 -07:00
Tin Chi Lo
1b28128b22 refactor(mcp): bind org_id on session admission and clarify unreachable arm
- _reload_admitted_user binds the user's org_id so the org-level MCP ceiling stays in force for a gateway session (a ceiling can only narrow; multi-org users are capped conservatively to their primary org) instead of being silently skipped
- trim the NotSessionBearer arm comment to state it is simply unreachable
2026-07-16 14:51:43 -07:00
Tin Chi Lo
4e9ffb889d feat(mcp): admit gateway DCR session bearers at the aggregate /mcp scope 2026-07-16 14:51:43 -07:00
Tin Chi Lo
05c55d016b refactor(mcp): harden gateway DCR flow per adversarial review
- atomic single-use guard (async_increment_cache) + reload-before-claim so a transient DB blip does not burn a valid code
- PKCE verify over bytes so a non-ASCII code_challenge fails invalid_grant instead of raising a 500; validate code_verifier length (RFC 7636)
- flag-off byte-identical for a server literally named mcp (AS well-known delegates to the named-server document)
- connect flow is single-use (atomic jti claim) so a double-submit cannot mint two codes
- extra=forbid on the sealed models; bound state length; drop unused request param and coarse dict on register
- _reload_failure_response exhaustive match+assert_never; dedupe ReloadUserFailure with _KeyResolutionFailure
- reject control/whitespace chars in the same-origin return_to
2026-07-16 14:51:43 -07:00
Tin Chi Lo
f546cfcb5f feat(mcp): aggregate DCR register, authorize, complete, and token flow for the gateway front door 2026-07-16 14:51:43 -07:00
Tin Chi Lo
22a7174e3c fix(ui): stamp exp on the UI session cookie so bounded-lifetime readers accept it 2026-07-16 14:51:43 -07:00
Tin Chi Lo
6e7238633e feat(mcp): add jti claim for per-mint session token uniqueness 2026-07-16 14:51:43 -07:00
Tin Chi Lo
5b2877f742 feat(mcp): identity-only session tokens for the gateway DCR front door 2026-07-16 14:51:43 -07:00
Tin Chi Lo
90195afa06 test(mcp): isolate MCP discovery tests from a leaked SERVER_ROOT_PATH
tests/test_litellm/proxy/test_custom_proxy.py sets SERVER_ROOT_PATH at import time (its app
mounts under a custom path) and never restores it, so in a shared shard the value leaks into the
process. The discovery routes and the 401 challenges now read SERVER_ROOT_PATH to path-insert it
where they previously ignored it, so a leaked value rewrites every resource_metadata URL and the
exact-URL assertions in the delegate, pass-through, and aggregate challenge tests fail depending
on shard order

An autouse fixture clears SERVER_ROOT_PATH for the MCP discovery tests so they deterministically
exercise the default root-mounted deployment; the tests that assert a sub-path deployment set the
value explicitly within their own body. No assertion changed; the leak was invisible before only
because the code ignored the variable
2026-07-16 14:51:43 -07:00
Tin Chi Lo
e7e5265f65 fix(mcp): reserve mcp for the aggregate AS and root-path the discovery challenges
Two RFC 9728 / 8414 discovery fixes on the aggregate front door, both raised by Bugbot on this PR

The aggregate authorization-server document at /.well-known/oauth-authorization-server/mcp used to
defer to a per-server row literally named "mcp", serving issuer {base} while the aggregate
protected-resource document advertises {base}/mcp as its authorization server. A spec client
following that chain fails the RFC 8414 issuer check and cannot sign in. The single segment /mcp is
now reserved for the aggregate so the issuer stays {base}/mcp and matches the protected-resource
document; a server named "mcp" keeps its standard two-segment discovery at
/.well-known/oauth-authorization-server/mcp/mcp

The 401 challenges built the resource_metadata URL as {base}/.well-known/oauth-protected-resource/mcp
with no SERVER_ROOT_PATH segment, but the routes are registered with the path-inserted root segment,
so a proxy mounted under a sub-path pointed DCR clients at a URL that 404s. Both the aggregate
challenge and the pre-existing per-server pass-through challenge now derive the path from one
well_known_root_suffix helper that the route registrations also use, so the advertised URL cannot
drift from the served route
2026-07-16 14:51:43 -07:00
Tin Chi Lo
943d7aa0a9 refactor(mcp): make the aggregate DCR front door always-on, remove the mcp_gateway_dcr flag
The flag guarded no breaking change: the aggregate discovery lives at new /mcp-suffixed
routes, the challenge only fires at aggregate scope, and the authorize/token/register/admission
arms self-gate on the llm_dcrc_/llm_session_ prefixes. Bare-origin and per-server discovery are
left exactly as they were, and a server literally named mcp keeps its own discovery via
disambiguation, so turning it on for everyone changes nothing about existing flows.
2026-07-16 14:51:42 -07:00
Tin Chi Lo
64868f2fc8 feat(mcp): aggregate gateway DCR discovery front door behind mcp_gateway_dcr 2026-07-16 14:51:42 -07:00
ryan-crabbe-berri
5ab160113f
feat(proxy): add disable_auto_add_proxy_admin_to_teams flag (#33563) 2026-07-16 14:50:40 -07:00
Yassin Kortam
21ba9692c3
fix(router): apply team/key enable_tag_filtering to tag routing (#33436)
Team/key router_settings.enable_tag_filtering was stored and echoed by
/team/info but never applied at request time: the per-request override
whitelist in route_llm_request.py dropped it, tag filtering only read the
router-level flag, and UpdateRouterConfig silently discarded the field on
/key/generate and /config/update. Requests from teams with the toggle on
were load balanced across all deployments instead of tag-matched ones.

- add enable_tag_filtering to the router_settings_override whitelist and
  strip any client-supplied copy from the request body first, so only the
  key/team value reaches the router
- run tag filtering when the request carries enable_tag_filtering=True; a
  request-level False cannot disable a router-level True, so per-request
  settings can only scope down, never escape the global policy
- add the field to UpdateRouterConfig so key and config update paths stop
  dropping it, and to all_litellm_params so it never leaks into provider
  request bodies
- allow it through Router.update_settings/get_settings so the global UI
  toggle persists across DB config reloads

Resolves LIT-4390
2026-07-16 14:41:24 -07:00
Mateo Wang
fb9cb67a5d
Merge pull request #33568 from BerriAI/litellm_ci_stacked_pr_branch_filters
ci: run zizmor and proxy-db unit tests on PRs targeting litellm_ branches
2026-07-16 14:26:46 -07:00
Yassin Kortam
2162da5015
fix(langfuse_otel): build per-request OTLP exporter from key and team dynamic Langfuse credentials (#32437)
* fix(langfuse_otel): build per-request OTLP exporter from key/team dynamic Langfuse credentials

Key-scoped langfuse_otel callbacks only injected Authorization headers into the
init-time exporter, so a proxy without global LANGFUSE_* env vars kept its
fallback exporter and never exported traces to Langfuse. Dynamic params now
build a full per-request OTLP config (endpoint from the key's langfuse_host,
otlp_http, basic auth from the key's credentials).

Resolves LIT-3976

* fix(otel): log dynamic config endpoint in span processor debug output

* fix(otel): redact authorization headers in exporter debug logs
2026-07-16 13:39:10 -07:00
Yassin Kortam
903219a8b1
fix(redis): honor ssl value instead of key presence when building async connection pool (#32590)
* fix(redis): honor ssl value instead of key presence when building async connection pool

* ci: rerun codspeed after cross-runtime-environment flake
2026-07-16 13:38:01 -07:00
Yassin Kortam
c6778b79c3
fix(router): honor per-request routing_strategy from key/team router_settings (#33429)
* fix(router): honor per-request routing_strategy from key/team router_settings

Key and team router_settings.routing_strategy was stored and shown in the
UI but never forwarded to the shared Router, so the global strategy always
won. Forward it through router_settings_override and resolve it in
_get_routing_context: a validated per-request strategy takes precedence
over routing groups and the top-level strategy, with lazily built cached
selectors for strategies that need one. Unknown or unsupported strategy
values are ignored with a warning instead of failing the request, and
routing_strategy is registered in all_litellm_params so it is stripped
before the provider call.

* fix(router): sweep override selectors on strategy re-init and cover coverage-gate helpers

routing_strategy_init now unregisters cached per-request override
selectors so a later update_settings strategy change cannot leave a
zombie selector receiving callback events. Adds direct tests for the
two new helpers so the router code coverage gate passes.

* docs(team): document mcp_rpm_limit in update_team docstring

The documentation CI job walks management_endpoints and requires every
UpdateTeamRequest field to appear in the update_team docstring;
mcp_rpm_limit was added to the model without a docstring line, failing
the job on unrelated PRs depending on walk order. Regenerates
schema.d.ts since the docstring feeds the OpenAPI spec.
2026-07-16 13:36:03 -07:00
Yassin Kortam
51305536bf
fix(proxy): coerce default_internal_user_params.max_budget to float on config load (#32434)
* fix(proxy): coerce default_internal_user_params.max_budget to float on config load

* fix(proxy): log coerced default_internal_user_params and cover absent max_budget in tests
2026-07-16 13:34:43 -07:00
Yassin Kortam
03e7dc4ac5
build(deps): bump uvicorn lock to 0.51.0 so worker health-check and jitter flags take effect (#33574) 2026-07-16 13:21:29 -07:00
Mateo Wang
5c58aa2070
Merge pull request #33485 from BerriAI/litellm_unxfail_azure_fable5_opus48
test(reasoning_effort_grid): enable azure fable-5 and opus-4-8 grid cells
2026-07-16 13:19:19 -07:00
Yassin Kortam
a8ae515bee
fix(proxy_cli): reap orphaned prisma query-engine processes when a worker dies (#33424)
* fix(proxy_cli): reap orphaned prisma query-engine processes when a worker dies

When the proxy runs multi-worker (uvicorn multiprocess supervisor or the
gunicorn arbiter), a worker that crashes or is force-killed never runs its
in-process atexit cleanup, so its prisma query-engine subprocess reparents
to PID 1 and keeps its database connection pool established forever while
the replacement worker opens a fresh pool. Active DB connections then grow
past database_connection_pool_limit with every worker death.

Run a reaper thread in the supervisor process that marks itself a child
subreaper on Linux, scans for adopted query-engine children whose worker
is gone, and terminates them with SIGTERM escalating to SIGKILL after a
bounded grace period. Engines owned by live workers are children of those
workers, never of the supervisor, so they are structurally out of reach.

Resolves LIT-4449
Fixes https://github.com/BerriAI/litellm/issues/33023

* fix(proxy_cli): address review findings on the query-engine reaper

Make start_query_engine_reaper idempotent, reap simultaneous orphans
under one shared grace period instead of serially, and log when a PID
survives SIGKILL. Also regenerate schema.d.ts for the update_team
docstring line that documents the existing mcp_rpm_limit param (fixes
the walk-order-dependent documentation CI failure) and avoid a cast in
the prctl wrapper

* test(proxy): fix reaper idempotency-test isolation and widen coverage

The daemon-thread startup test now stubs threading.enumerate so a
reaper thread left running by an earlier test in the same xdist worker
cannot satisfy the idempotency guard and skip the code under test. Add
coverage for stat-file truncation, non-numeric ppid, non-child reap,
signal-to-dead-pid, subreaper capability, and reaper-loop resilience
2026-07-16 13:16:41 -07:00
Mateo Wang
fba7ac4428
Merge pull request #33222 from BerriAI/litellm_fix_stream_reset_empty_200
fix(streaming): surface upstream connection resets instead of empty 200 streams
2026-07-16 12:43:07 -07:00
yucheng-berri
7fe3dd86a4
feat(logging): add structured budget fields to budget rejection failure logs (#33460) 2026-07-16 12:39:04 -07:00
ryan-crabbe-berri
74ff8d0ff9
fix(ui): navigate to /ui/login/ with trailing slash via hard navigation (#33561)
* fix(ui): navigate to /ui/login/ with trailing slash via hard navigation

Logged-out redirects targeted /ui/login without the trailing slash, so
Starlette's StaticFiles(html=True) mount answered with a 307 whose
absolute Location is built from the scheme the container sees. Behind a
TLS-terminating reverse proxy uvicorn does not trust X-Forwarded-Proto
by default, so the redirect downgraded https to http and stranded users
on an unreachable URL (#33454). The auth guard also used the Next client
router for this navigation, which first requests an RSC payload that the
static export cannot serve, producing 404s before falling back to a full
page load.

Centralize the login URL in getLoginUrl(), which always emits the
trailing slash so no server redirect fires, and use
window.location.replace for the login redirects so no RSC fetch is
attempted.

* test(ui): expect trailing slash in expired-token login redirect
2026-07-16 12:18:55 -07:00
mateo-berri
40aeb33d61 Merge branch 'litellm_internal_staging' into litellm_fix_stream_reset_empty_200 2026-07-16 12:09:35 -07:00
mubashir1osmani
ebdf0bbfd7
chore(e2e): establish litellm_e2e_staging integration line (#33502)
* chore(e2e): establish litellm_e2e_staging integration line

Long-lived berri branch for e2e suite recovery work (LIT-4479 through LIT-4486) before merge to litellm_internal_staging

* test(e2e): remove langfuse_otel logging e2e suite (#33558)

* test(e2e): remove langfuse_otel logging e2e suite

Removes the LIT-4483 dynamic per-team/key/org langfuse_otel logging e2e tests (tests/e2e/logging/test_langfuse_e2e.py, added in #32857). The shared logging_client harness and the langfuse coverage-registry cells are left in place; only the test module is removed. The otel and prometheus logging e2e suites are unaffected.

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

* test(e2e): drop orphaned langfuse coverage-registry cells

The three logging.langfuse.*.logs_spend P0 cells were only exercised by the deleted langfuse_otel e2e suite. Remove them so the coverage registry has no orphaned rows.

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>

* fix(e2e): log into the react admin ui in the management browser fixture (#33562)

The management ui_page fixture drove the old server-rendered login form: it clicked input[type="submit"] and treated wait_for_url("**/ui/**") as the done signal. /ui/ now serves the react (antd) dashboard whose submit is a <button type="submit">, so the click waited out the full 30s timeout and errored every browser test in the suite. wait_for_url also matched instantly because the login page already lives at /ui/, so on the fast path the fixture navigated before the auth cookie landed and got bounced back to login.

Click the antd submit button and wait for the token cookie loginCall sets on document.cookie, the real post-login signal.

Co-authored-by: Mubashir Osmani <mubashir@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(e2e): make ui login readiness robust to httpOnly token cookies (#33564)

The login readiness check waited only on document.cookie including token=, which is empty when the token cookie is httpOnly. If the server ever sets it via a Set-Cookie header, the wait would spin to the 30s timeout and silently reproduce the original hang. Also accept the login form detaching (#username gone after the post-login redirect) so readiness holds regardless of how the cookie is delivered.

Co-authored-by: Mubashir Osmani <mubashir@berri.ai>
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: yucheng <yucheng@berri.ai>
Co-authored-by: Mubashir Osmani <mubashir@berri.ai>
2026-07-16 12:09:24 -07:00
Yassin Kortam
c012373e1c
fix(router): cast model_info cost values to float in _set_model_group_info (#33556)
Cost values read from deployment model_info can be strings when the
config YAML contains scientific notation with an integer mantissa
(e.g. 1e-05), which YAML 1.2 parsers such as PyYAML 6.x treat as a
string. Comparing that string against the running float aggregate in
_set_model_group_info raised TypeError and broke /model_group/info,
the prometheus remaining-usage callback, and the
x-litellm-response-cost header. Coerce input/output cost values to
float before comparing and storing them.
2026-07-16 12:05:36 -07:00
yucheng-berri
899ddef219
feat(logging): add user and team level spend and budget to StandardLoggingPayload metadata (#33459)
* feat(logging): add user and team level spend and budget to StandardLoggingPayload metadata

* fix(logging): include user and team budget fields in dummy standard logging payload
2026-07-16 12:01:11 -07:00
tin-berri
db800152c0
Merge pull request #33450 from BerriAI/litellm_mcp_issuer_anchored_discovery
feat(mcp): issuer-anchored OAuth discovery (RFC 8414 §3.3) to close the authorization-server mix-up
2026-07-16 11:55:35 -07:00
mateo-berri
9d88f9a894 ci: run zizmor and proxy-db unit tests on PRs targeting litellm_ branches 2026-07-16 11:44:35 -07:00
Mateo Wang
582907d1ab
Merge pull request #32255 from BerriAI/litellm_fix_openrouter_streaming_usage_cost
fix(streaming): use provider-reported usage cost for OpenRouter streams
2026-07-16 11:18:01 -07:00
tin-berri
748ccde5fd
Merge pull request #33318 from BerriAI/litellm_semantic_filter_lazy_sync
fix(mcp): index authed request-time tools missing from the semantic filter startup index
2026-07-16 11:08:32 -07:00
mubashir1osmani
3f5ed5a9c8
fix(e2e/claude_code): unblock stage collection, align proxy env names, register compat models (#33433)
* refactor(e2e/claude_code): align proxy env names with the rest of tests/e2e

Every claude_code compat cell used to read its own `LITELLM_PROXY_BASE_URL` and `LITELLM_PROXY_API_KEY` and duplicate the same 12-line "missing env, hard fail" block. The rest of `tests/e2e/` reads `LITELLM_PROXY_URL` and `LITELLM_MASTER_KEY` from `e2e_config.py`, so anyone standing up a live proxy for one suite had to export a second spelling for claude_code, and every cell repeated the same boilerplate.

Centralize the resolution in `claude_code/_env.py`. `resolve_proxy()` prefers the suite-wide `LITELLM_PROXY_URL` / `LITELLM_MASTER_KEY` names and falls back to the legacy pair so existing CI wiring on stage keeps working during the roll-out. `require_proxy(compat_result)` is the one-liner cells call to bind `(base_url, api_key)` or hard-fail with a message that names both spellings.

55 cell files, `_basic_messaging.py`, and the driver's own unit-test fixture now go through the helper. `run_compat.sh` accepts either spelling and normalizes to the primary names before invoking pytest. `cron_vm/run_daily.sh` exports the primary names when launching pytest.

`_pr_gate_unit_tests/test_env_resolution.py` pins the resolution rules so a future edit cannot silently reintroduce the drift: primary names win on tie, legacy names still resolve when primary is unset, mixed URL-primary key-legacy still resolves, empty-string exports are treated as unset, `require_proxy` names both spellings in its error message.

Net diff: 71 files, +370/-1240.

* fix(e2e): anchor claude_code Bash pin at parents[1] so container run collects

`test_bash_tool_restrictions.py` derived `REPO_ROOT = Path(__file__).resolve().parents[4]` and then joined `tests/e2e/claude_code/<feature>`. That works locally, but the stage container mounts tests/e2e/ at /app/e2e/, so parents[4] resolves to filesystem root and the `_bash_cells()` assertion looks for `/tests/e2e/claude_code/tool_use` — a path that doesn't exist. Collection interrupts before any test runs, so the entire e2e suite appears broken.

Fix: `CLAUDE_CODE_DIR = Path(__file__).resolve().parents[1]` resolves to the sibling `claude_code/` dir in either layout, and the `relative_to(REPO_ROOT)` calls become `relative_to(CLAUDE_CODE_DIR)` so test IDs and error messages read the same.

Adds `test_claude_code_dir_anchor_is_layout_independent` as a regression pin: it checks the anchor lands on a directory named `claude_code` that contains this test file, which would fail under the old parents[4] anchor when run from /app/e2e/.

* feat(e2e/claude_code): register compat deployments via /model/new from a session fixture

Every compat cell hardcodes a virtual model name like `claude-sonnet-4-6` or `claude-sonnet-4-6-bedrock-invoke` and hits the proxy expecting it to be routable. On stage those live in the deployed model_list; locally the `docker-config.yaml` under tests/e2e/ only declares one of them, so anything past haiku 400s with `Invalid model name`.

`claude_code/test_config.yaml` is the ground-truth compat matrix config the deployment already uses. `_compat_models.py` loads it, normalizes the yaml keys pydantic would silently drop (vertex_ai_* → vertex_*), and selects the subset whose provider credentials are present in the environment. An autouse session fixture in `conftest.py` POSTs each selected deployment to `/model/new`, blocks until it is servable on the data plane, and tears them all down on session exit. Skips silently when the proxy env is unset so pure-unit runs stay hermetic.

`test_compat_models.py` pins the invariants that keep this safe. Every cell-referenced name must have a yaml entry (drift check catches a cell probing a name the fixture never registered); the yaml has no unused declarations; the fixture registers exactly 15 deployments (3 tiers × 5 provider surfaces); vertex_ai_* yaml keys populate the pydantic body's vertex_* fields (they got silently dropped historically); Azure needs both AZURE_FOUNDRY_* env vars; Bedrock lifts creds from the ambient AWS chain; Vertex needs both the yaml refs AND ambient GCP credentials.

* refactor(e2e/claude_code): inject env + runner instead of monkeypatching

`require_proxy` and `_basic_messaging.run_basic_messaging_cell` now take the env mapping (and the CLI runner) as constructor-style arguments with `os.environ` and `run_claude_models_parallel` as defaults. Tests exercise the branching by passing dicts and callables directly, so `monkeypatch.setenv` and `monkeypatch.setattr(_basic_messaging, "run_claude_models_parallel", ...)` are gone from every unit test in this refactor's blast radius.

`test_env_resolution.py` drops the `monkeypatch.setenv`/`delenv` fixtures and passes `env={...}` dicts to `require_proxy`. Added a new pinned check that a successful resolution leaves `compat_result` untouched, and split the "unset env" test into three explicit shapes (empty, primary-only, legacy-only) so a regression that swaps the precedence rule can no longer hide behind a single monkeypatched fixture.

`test_basic_messaging.py` (driver) replaces the `_install_fake_runner(monkeypatch, ...)` helper with `_make_fake_runner(...)` that returns a `(callable, captured_dict)` pair the test passes in via the helper's new `runner=` kwarg. Also drops the autouse `_proxy_env` fixture in favor of a module-level `_PROXY_ENV` dict each test wires through the helper's new `env=` kwarg. Added a regression pin that a missing-env call hard-fails without ever invoking the runner (so the guard order stays correct).

`test_run_daily_pytest_scrubs_env.py` updates its pin to assert the new suite-wide env spellings (`LITELLM_PROXY_URL` / `LITELLM_MASTER_KEY`) instead of the legacy `LITELLM_PROXY_BASE_URL` / `LITELLM_PROXY_API_KEY` that `run_daily.sh` used to export.

* handwrote rules
2026-07-16 11:05:31 -07:00
Yassin Kortam
45fb9a70b9
feat(helm): add per-component PodDisruptionBudget and topologySpreadConstraints to componentized chart (#33430)
* feat(helm): add per-component PodDisruptionBudget and topologySpreadConstraints to componentized chart

The componentized chart (helm/litellm) had no PodDisruptionBudget template
for the gateway, backend, or ui, so voluntary disruptions (node drains,
Karpenter consolidation) could evict every replica of a component at once.
The legacy chart shipped one out of the box. Deployments also had no way to
configure topologySpreadConstraints, blocking HA spread across AZs.

Adds a shared litellm.pdb helper rendered per component, gated on
<component>.pdb.enabled with minAvailable/maxUnavailable (minAvailable wins,
fallback maxUnavailable: 1), selectors matching each component's
selectorLabels. Adds <component>.topologySpreadConstraints rendered into
each Deployment pod spec. PDBs default to disabled since the default
hpa.minReplicas of 1 with minAvailable: 1 would block drains entirely.

Resolves LIT-4452

* fix(helm): honor explicit 0 in pdb minAvailable/maxUnavailable

A Go-template truthy check treated an explicit 0 (forbid all voluntary
disruptions via maxUnavailable: 0) as unset and silently replaced it with
the fallback maxUnavailable: 1, weakening the configured protection. Treat
a value as set when it is non-nil and non-empty-string instead.
2026-07-16 10:41:59 -07:00
Yassin Kortam
df51cebcd3
fix: remove dead user-cache lookup with None key in spend-update path (#33555)
With litellm_settings.enable_redis_auth_cache enabled, user_api_key_cache
is Redis-backed. _update_user_db performed a cache lookup with
key=user_id where user_id can be None; the in-memory cache tolerates a
None key but Redis raises redis.exceptions.DataError (Invalid input of
type: NoneType) on every spend update for requests without a user_id.

The looked-up value was never used by any subsequent code, so the lookup
is removed along with the user_api_key_cache parameter it existed for.
Spend updates for users, end users, and the global proxy budget are
unchanged
2026-07-16 10:39:53 -07:00
devin-ai-integration[bot]
260d1eae8e
fix(cli): make CLI output ASCII-only so it doesn't crash legacy Windows consoles (#33465)
* fix(cli): force UTF-8 output so emoji don't crash the CLI on Windows

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

* test(cli): drop dead flush calls flagged by review

* fix(cli): replace non-ASCII CLI output with ASCII so legacy Windows consoles don't crash

---------

Co-authored-by: ryan <ryan@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-16 10:35:06 -07:00
yucheng-berri
edc30ea515
test(e2e): datadog log delivery for successful chat, messages, and responses (LIT-4447) (#33415)
* test(e2e): datadog log delivery for successful chat, messages, and responses

Covers logging.datadog.success.exports_metric on all three routes: one
successful non-streaming call must reach the DataDog logs intake as exactly
one log event whose StandardLoggingPayload message carries the model group,
real token counts, and a response cost equal to the x-litellm-response-cost
header of the same response. Delivery is judged at the intake: the compose
stack gains a dd-sink service recording every batch the datadog callback
ships via the DD_BASE_URL testing override, and a typed reader replays it.

Writing these caught a live product bug: /v1/messages double-logs every
success (two byte-identical events per call), filed as LIT-4447; the messages
test tolerates byte-identical duplicates of the one event until it lands,
while a second differing event still fails

* test(e2e): address review findings on the datadog delivery suite

Consolidates the fresh-key first_ok helper into logging_client now that the
otel PR it mirrored has merged (both test files use the shared copy), moves
intake batch parsing into a helper so no path can leave the batch unbound,
and gives the sink's /health endpoint a truthful text/plain content type

* test(e2e): tolerate same-logical-event duplicates by call id, not byte identity

A clean LIT-4447 repro showed the duplicated payload is built twice and can
mint a fresh synthetic completion id per emission, arriving as two separate
intake POSTs with the same litellm_call_id and identical substantive fields.
Byte-identity was therefore a flaky criterion; duplicates now qualify only
when they share the call id, call type, model group, tokens, and cost, and a
second differing event still fails

* test(e2e): assert the scenario strictly; the messages test is the LIT-4447 regression pin

Per review direction the tests now assert exactly what the scenario promises:
exactly one DataDog log event per successful call, on every route. The
/v1/messages test therefore fails on current code against the known
double-log (LIT-4447) and is its regression pin; it goes green when the fix
lands. The duplicate-tolerance machinery is removed

* Simplify docstrings for DataDog log tests

Removed redundant phrasing about cost cross-checking in docstrings.

* Update test_datadog_log_e2e.py
2026-07-16 09:54:07 -07:00
yuneng-jiang
69a491e168
Merge pull request #33343 from BerriAI/litellm_/migrate-simple-tables-ac3786
refactor(ui): migrate vector stores, prompts, and skills tables onto shared DataTable
2026-07-16 07:29:59 -07:00
Yuneng Jiang
ce8ecd4fcf
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/migrate-simple-tables-ac3786
# Conflicts:
#	ui/litellm-dashboard/eslint-suppressions.json
2026-07-16 07:14:30 -07:00
yuneng-jiang
dce1beadca
Merge pull request #33357 from BerriAI/litellm_/gallant-carson-880e37
refactor(ui): migrate policies, deleted keys, deleted teams, budgets, and search tools tables onto shared DataTable
2026-07-16 07:11:15 -07:00
yuneng-jiang
ff06119aa9
Merge pull request #32853 from BerriAI/litellm_/guardrail-monitor-details-fix-8845d6
fix(guardrails): show YAML-defined guardrails in the Guardrail Monitor
2026-07-16 07:10:10 -07:00
Mateo Wang
2f03789927
Merge pull request #33489 from BerriAI/litellm_ocr_model_2512
test(ocr): use mistral-document-ai-2512 in azure_ai OCR tests
2026-07-16 00:52:51 -07:00
devin-ai-integration[bot]
ebc6fdb4c2
fix(cli/anthropic): unblock lite autoroute proxy deps, adaptive thinking, and thinking+signature streaming (#33507) 2026-07-16 00:44:00 -07:00
devin-ai-integration[bot]
5a0e1dd1dd
feat(autoroute): prompt for semantic keywords per tier in configure wizard (#33508) 2026-07-16 07:43:41 +00:00
devin-ai-integration[bot]
bbd52984b1
fix(anthropic): stop 500 on combined thinking+signature streaming chunk (#33505) 2026-07-16 00:24:02 -07:00