Skip to content

chore(deps): bump pillow to 12.3.0 to resolve osv-scan CVEs - #33093

Merged
yucheng-berri merged 2 commits into
litellm_internal_stagingfrom
litellm_bump_pillow_12_3_0
Jul 13, 2026
Merged

chore(deps): bump pillow to 12.3.0 to resolve osv-scan CVEs#33093
yucheng-berri merged 2 commits into
litellm_internal_stagingfrom
litellm_bump_pillow_12_3_0

Conversation

@yucheng-berri

@yucheng-berri yucheng-berri commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Fixes #33091

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Before (any PR against litellm_internal_staging, e.g. #31099): the osv-scan check fails with

Total 1 package affected by 5 known vulnerabilities (0 Critical, 4 High, 1 Medium, 0 Low, 0 Unknown)

| https://osv.dev/PYSEC-2026-2253 | 7.5 | pillow | 12.2.0 | 12.3.0 |
| https://osv.dev/PYSEC-2026-2254 | 7.5 | pillow | 12.2.0 | 12.3.0 |
| https://osv.dev/PYSEC-2026-2255 | 7.5 | pillow | 12.2.0 | 12.3.0 |
| https://osv.dev/PYSEC-2026-2256 | 7.5 | pillow | 12.2.0 | 12.3.0 |
| https://osv.dev/PYSEC-2026-2257 | 4.5 | pillow | 12.2.0 | 12.3.0 |

After: the osv-scan check on this PR runs against pillow 12.3.0 and reports no known vulnerabilities (proof is the green osv-scan check on this PR's HEAD, commit 84dd160; will update here once CI completes)

No runtime behavior changes; pillow is only in the ci dependency group, so there is no live-proxy surface to exercise

Type

🐛 Bug Fix
🚄 Infrastructure

Changes

Bumps Pillow from 12.2.0 to 12.3.0 in the ci dependency group of pyproject.toml and regenerates uv.lock via uv lock --upgrade-package pillow. The lock was regenerated with uv 0.10.9 (the version family that wrote the current staging lock) so the diff touches only pillow's entry (version, sdist, per-platform wheel hashes) plus the exclude-newer timestamp that uv refreshes on every relock; regenerating with uv 0.11.x adds a few hundred lines of unrelated marker and option normalization churn. Verified with uv lock --check under both uv 0.10.9 and 0.11.26 against current litellm_internal_staging

Pillow 12.2.0 has 5 known CVEs (PYSEC-2026-2253 through PYSEC-2026-2257, 4 High and 1 Medium) that are fixed in 12.3.0, which made the osv-scan check fail on every PR

Credit

Adopted from #33089 by @yamaceay. Mirrored onto a litellm_ branch because fork PRs must not modify uv.lock, so lockfile changes have to come from a branch in the canonical repository

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

Note

Low Risk
Dependency-only bump in the CI group with no application code changes; typical patch-level security upgrade risk.

Overview
Bumps Pillow from 12.2.0 to 12.3.0 in the ci dependency group in pyproject.toml and refreshes uv.lock (pillow pins, wheel hashes, and uv’s exclude-newer timestamp).

This addresses five known vulnerabilities on 12.2.0 (PYSEC-2026-2253–2257) so osv-scan can pass. Pillow stays CI-only, not in core or proxy installs, so production dependency surfaces are unchanged.

Reviewed by Cursor Bugbot for commit 84dd160. Bugbot is set up for automated code reviews on this repo. Configure here.

pillow 12.2.0 has 5 known vulnerabilities (PYSEC-2026-2253 through
PYSEC-2026-2257), all fixed in 12.3.0.
@yucheng-berri
yucheng-berri requested a review from a team July 13, 2026 16:42
@yucheng-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR bumps Pillow from 12.2.0 to 12.3.0 in the ci dependency group of pyproject.toml and regenerates uv.lock accordingly. Pillow is used only in CI (not in production/proxy code), so there is no runtime surface affected.

  • pyproject.toml: Single-line version change; correctly scoped to the ci dependency group.
  • uv.lock: Pillow entry updated to 12.3.0 with new sdist/wheel hashes; the exclude-newer timestamp advances to the relock time (2026-07-10T16:47:58.286372Z), and some dependency marker conditions for unrelated packages were normalized as a uv relocking side-effect.

Confidence Score: 5/5

Safe to merge — Pillow is a CI-only dependency with no production surface, and the version bump correctly targets the patched release.

The pyproject.toml change is a single targeted line touching only the ci dependency group. The uv.lock update carries the expected wheel/sdist hash replacements for Pillow plus minor marker normalization from relocking, which does not affect resolved package versions for any production or SDK dependency.

No files require special attention.

Important Files Changed

Filename Overview
pyproject.toml Single-line version bump of Pillow from 12.2.0 to 12.3.0 in the ci dependency group — clean and targeted
uv.lock Lockfile regenerated: Pillow entry updated to 12.3.0 (new sdist/wheel hashes), exclude-newer timestamp updated to 2026-07-10; broader marker/dependency normalization changes are also present as uv relocking artifacts

Reviews (3): Last reviewed commit: "chore(ci): regenerate uv.lock with uv 0...." | Re-trigger Greptile

Comment thread uv.lock Outdated
Comment on lines 12 to 13
exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values.
exclude-newer-span = "P3D"

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 84dd160; the lock was regenerated with uv 0.10.9 (matching the version family that wrote the staging lock), which removes the marker normalization churn and the exclude-newer sentinel rewrite. The remaining diff is the pillow entry plus the exclude-newer timestamp refresh that any relock performs, and the PR description was updated to say exactly that

@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@yucheng-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@BerriAI BerriAI deleted a comment from greptile-apps Bot Jul 13, 2026
@yucheng-berri
yucheng-berri enabled auto-merge (squash) July 13, 2026 17:08
@codspeed-hq

codspeed-hq Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_bump_pillow_12_3_0 (84dd160) with litellm_internal_staging (e6d916b)

Open in CodSpeed

@yucheng-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 84dd160. Configure here.

@yucheng-berri
yucheng-berri merged commit 20e646c into litellm_internal_staging Jul 13, 2026
132 checks passed
@yucheng-berri
yucheng-berri deleted the litellm_bump_pillow_12_3_0 branch July 13, 2026 17:20
@yucheng-berri yucheng-berri changed the title fix(ci): bump pillow to 12.3.0 to resolve osv-scan CVEs chore(deps): bump pillow to 12.3.0 to resolve osv-scan CVEs Jul 15, 2026
yuneng-berri added a commit that referenced this pull request Jul 18, 2026
…1.93.0 stable cut (#33847)

* fix(ci): bump pillow to 12.3.0 to resolve osv-scan CVEs (#33093)

(cherry picked from commit 20e646c)

* chore(deps): pin httplib2 and setuptools transitive floors (#33233)

Raise the constraint floors for two transitive dependencies so resolution moves them to their latest maintenance releases: httplib2 0.31.2 -> 0.32.0 and setuptools 82.0.1 -> 83.0.0. Both are pulled in only by optional integrations (Google API client, grpc tooling, lunary observability, the nvidia-riva extra), all lower-bound only, so the floors stay inside every requirer's allowed range and a default install is unaffected

(cherry picked from commit 8b32320)

* fix(anthropic/passthrough): drop incompatible temperature when downgrading adaptive thinking for pre-4.6 models (#33244)

* fix(anthropic/passthrough): drop temperature and cap thinking budget when downgrading adaptive thinking for pre-4.6 models

* test(anthropic/passthrough): use sufficient max_tokens for reasoning_effort thinking mapping

* fix(anthropic/passthrough): drop incompatible temperature when downgrading adaptive thinking for pre-4.6 models

Narrow the fix to the temperature reconciliation; the reasoning_effort
budget cap is reverted because the live translation grid relies on
budget_tokens >= max_tokens to reject unsupported effort tiers
(xhigh/max) on budget-mode models, so capping turned those 400s into
200s.

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
(cherry picked from commit 71dffc1)

* build: raise requires-python cap to <3.15 so Python 3.14 installs current releases (#33438)

* build: drop requires-python upper cap so Python 3.14 resolves to current releases

The <3.14 cap made pip on Python 3.14 fall back to litellm 1.83.7, a
pre-April release whose old auth flow fails with 400s. The cap was added
in d9a4602 because deps lacked 3.14 wheels and uv could not resolve
the 3.14 split; both are fixed now via the existing python_version
markers plus a ddtrace version split (2.x has no cp314 wheels, 3.16+
does). Verified on 3.14.5: uv sync --all-extras installs, litellm and
proxy_server import (rust bridge falls back to pure python), real
provider calls succeed sync/async/streaming, and the core-utils test
suite passes.

* build: cap requires-python at <3.15 and keep ddtrace on one major per python band

Reviewer preference to bound the supported window at the newest tested
minor rather than leaving it open-ended, and Greptile flagged the
ddtrace 3.14+ range spanning two majors; every ddtrace 4.x ships cp314
wheels so the band is now >=4.0,<5.0, matching the single-major
convention of the 2.x band.

(cherry picked from commit c6d49a8)

* build(deps): update ddtrace to the 4.x line

A single ddtrace constraint now covers every supported Python version, so this collapses the version split introduced in #33438. Also aligns the build_from_pip image pin and updates the type-only Tracer import to its current module path

(cherry picked from commit edc38ea)

* fix(docker): restore litellm-proxy-extras source dir in runtime images (#33592)

* fix(docker): restore litellm-proxy-extras source dir in runtime images

#30243 narrowed the runtime stage to an allowlist COPY, which dropped
/app/litellm-proxy-extras from the published images. Downstream
migration jobs point prisma migrate deploy at that path; with the
schema gone (or a schema with no adjacent migrations dir, where prisma
exits 0 without applying anything) those jobs went green while never
migrating the database. Restore the folder in all three runtime stages
and assert in image-scan that the schema and a non-empty migrations dir
ship at the source path

* chore(ci): drop image-scan migration-assets assertion

(cherry picked from commit 111d447)

* fix(model_armor): restore reference attachments via skip_unscannable_attachments and remove the attachment count cap (#33554)

* fix(model_armor): add skip_unscannable_attachments to allow reference-only attachments through

* fix(model_armor): wire skip_unscannable_attachments through guardrail config

* fix(model_armor): make max_file_attachments configurable and scan overflow instead of dropping

* fix(model_armor): remove the per-request attachment count cap and scan all attachments

---------

Co-authored-by: yucheng <yucheng@berri.ai>
(cherry picked from commit 0d7b0f7)

* build(rust): raise pyo3 to 0.29 so the native bridge compiles on Python 3.14 (#33798)

pyo3 0.23.5 hard-caps the interpreter at Python 3.13, so building the
native bridge against a 3.14 interpreter aborts inside pyo3-ffi's build
script before anything links. This raises pyo3 and pyo3-async-runtimes
to 0.29 (currently the newest line, and the range starting at 0.26 that
supports 3.14) and migrates the three call sites whose APIs were renamed
across that range: Python::with_gil is now Python::attach and
Python::allow_threads is now Python::detach. On a GIL-enabled interpreter
those are pure renames with identical semantics, so behavior on 3.10
through 3.13 is unchanged

Verified by compiling the native module for cp313 and cp314 and driving
it directly on both interpreters: gil_stats reports exactly one GIL
release per sync OCR call and the async path completes, matching the
0.23.5 baseline. cargo fmt, clippy, and the workspace tests pass on both
3.13 and 3.14 with the lockfile locked, and the lock churn is confined to
the pyo3 crates

Part of #26343; addresses the pyo3 build failure reported in #33116

(cherry picked from commit f3d2015)

* build(deps): allow redisvl, pypdf, and openapi-core on Python 3.14 (#33801)

Remove the python_version < '3.14' environment markers from redisvl,
pypdf, and openapi-core now that all three install and import cleanly
on 3.14. The relock is marker-only: no package version changed for any
Python branch, and the locked versions (redisvl 0.4.1, pypdf 6.13.3,
openapi-core 0.22.0) now serve 3.14 as well. semantic-router and
aurelio-sdk stay gated because every published release caps
python_requires below 3.14

(cherry picked from commit 967d934)

* build(deps): bump mcp lock to 1.28.1 to clear image-scan findings (#33803)

* build(deps): bump mcp lock to 1.28.1 to clear image-scan findings

* build(deps): require mcp>=1.28.1

(cherry picked from commit 40e914c)

* fix(proxy): source /v1/models token limits from the cost map instead of Router.get_model_group_info (#33721)

* fix(proxy): source /v1/models token limits from cost map instead of Router.get_model_group_info

Resolves the per-model get_model_group_info fan-out on GET /v1/models
(and /models) that pegged the event loop on wildcard listings (#33636).
create_model_info_response now reads max_input_tokens/max_output_tokens
from litellm.get_model_info (the static cost map) rather than the router,
which aggregated and deepcopied every deployment in a group per listed
model.

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

* test(proxy): inject model-info lookup into create_model_info_response for deterministic coverage

Inject the cost-map lookup (defaulting to litellm.get_model_info) so the
except and max_output_tokens branches are exercised deterministically and
the token-limit tests no longer hardcode mutable cost-map values.

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

* feat(proxy): surface custom deployment token limits on /v1/models via cheap index lookup

Add Router.get_configured_token_limits, an O(1) model-name index lookup that
reads a concrete deployment's configured max_input_tokens/max_output_tokens
without triggering pattern matching or deep copies. create_model_info_response
layers this over the cost map so custom deployments absent from the cost map
still surface their limits, and admin-configured limits override cost-map
defaults, while wildcard-expanded names stay on the fast path.

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>
(cherry picked from commit 8536e3b)

---------

Co-authored-by: yucheng-berri <yucheng@berri.ai>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: ryan-crabbe-berri <ryan@berri.ai>
cloudiaspecula added a commit to cloudiaspecula/litellm that referenced this pull request Jul 25, 2026
* fix(mcp): surface rejected delegate-auth upstream tokens as connect-time 401

For MCP servers with auth_type=oauth2 + delegate_auth_to_upstream=true, a
client-supplied upstream token that the upstream rejects was masked: the
upstream 401 raised during tools/list is absorbed by the list handler, so on a
single-server route a rejected token became HTTP 200 with an empty tool list.
Clients showed "0 tools" instead of re-authenticating, and monitoring never saw
an unauthorized signal.

Extend the connect-time preflight _check_passthrough_upstream_auth to probe
delegate-auth servers with the caller's bare Authorization bearer, reusing the
existing _probe_upstream_auth and the RFC 6750 challenge builder, so a rejected
token fails the connect with 401 + WWW-Authenticate error="invalid_token" and a
compliant client re-runs the upstream OAuth flow.

The bare Authorization header is a valid upstream token only when admission took
the delegate bypass, so the delegate target is resolved through
get_mcp_server_by_name (the same resolver admission uses) rather than the wider
allowed-server prefix/access-group matching. A name that reaches a delegate
server only via server_id or an access group is admitted as a real LiteLLM key,
so probing it would leak that key upstream; requiring the admission-resolver
match closes that gap. The probe is gated to single-server routes (matching the
OBO preflight), keyed to the caller's authorized set by server_id, and the
challenge echoes the requested name so aliased routes get the same
resource_metadata URL as the tokenless preemptive challenge. Tokenless requests
keep flowing to the preemptive discovery challenge unchanged.

Resolves LIT-4194

* fix(model_cost): add supports_reasoning: false to Gemini image generation models

vertex_ai/gemini-2.5-flash-image, vertex_ai/gemini-3-pro-image-preview,
vertex_ai/gemini-3.1-flash-image-preview, gemini/gemini-3-pro-image-preview,
and gemini/gemini-3.1-flash-image-preview were missing supports_reasoning
entries; _supports_factory then fell through to the vertex_ai provider-level
config which returns true, causing requests with reasoning_effort to be sent
to an API that rejects them.

* fix(model_cost): apply supports_reasoning: false to root pricing JSON

The backup file is used by tests; the root model_prices_and_context_window.json
is what gets published to the pricing URL and loaded by the proxy at runtime.
Without this, the proxy would continue resolving supports_reasoning via the
provider-level fallback and returning true for Gemini image generation models.

Also covers vertex_ai/gemini-3-pro-image and vertex_ai/gemini-3.1-flash-image
(non-preview variants) and gemini/gemini-3.1-flash-image which exist only in
the root JSON.

* fix(model_cost): add supports_reasoning: false to gemini/gemini-3-pro-image

* fix(model_cost): align backup gemini/gemini-3-pro-image entry with root pricing JSON

* fix(model_cost): add missing backup entries for gemini image models

gemini/gemini-3.1-flash-image, vertex_ai/gemini-3-pro-image, and
vertex_ai/gemini-3.1-flash-image existed in the root pricing JSON but not in
litellm/model_prices_and_context_window_backup.json, leaving deployments with
LITELLM_LOCAL_MODEL_COST_MAP=True unprotected. Copies the root entries into
the backup verbatim and extends the regression test to cover all ten gemini
image models, asserting each exists in the local cost map so a missing backup
entry fails the test instead of passing vacuously

* fix(anthropic): translate raw adaptive thinking for chat completions on pre-4.6 models

Clients that pass thinking={"type": "adaptive"} directly (not via the
reasoning_effort alias) on the /chat/completions interface had it forwarded
unmodified to pre-4.6 Anthropic models, which reject the shape. Mirrors the
translation already applied on the native /v1/messages passthrough (#32867):
translate to legacy thinking={type: enabled, budget_tokens}, capped below
max_tokens, dropping thinking when max_tokens can't fit even the minimum
budget. Hoists the shared budget-capping helper onto AnthropicConfig so both
paths use one implementation.

* fix(bedrock-converse): translate adaptive thinking for pre-4.6 models

Follow-up to #32867 (native /v1/messages) and the /chat/completions
commit earlier on this branch, extending the same adaptive-thinking
translation to the Bedrock Converse path.

Clients like Claude Code send thinking={type: "adaptive"} on every
request. When routed via Bedrock Converse to pre-4.6 models
(claude-haiku-4-5, claude-sonnet-4-5), this was forwarded as-is and
rejected by the model. Mirrors the translation already applied on the
/chat/completions and /v1/messages paths: map to legacy
thinking={type: enabled, budget_tokens}, capped below max_tokens.

Also fixes the missing custom_llm_provider arg in the chat completions
path's call to AnthropicConfig._map_reasoning_effort.

* fix(anthropic): pass resolved provider to adaptive-thinking check

The rebase onto staging changed _is_adaptive_thinking_model to require
custom_llm_provider (no default), so the one-arg call in the raw adaptive
thinking branch raised TypeError at runtime for any /chat/completions
caller sending thinking={type: adaptive}. Use self._resolved_provider,
matching the reasoning_effort branch just below. Caught by Greptile.

* test(bedrock-converse): cover adaptive-thinking drop when max_tokens too small

Adds the regression test for the warning-drop branch in the Converse
adaptive-thinking translation, mirroring the chat completions path's
test_raw_adaptive_thinking_dropped_when_max_tokens_too_small.

* feat(ui): working Test Connection for the complexity auto router

The consolidated auto-router tab dropped the Test Connection button because
the shared prepareModelAddRequest helper returns an empty array for an auto
router (it has no model_mappings), so the caller crashed destructuring
result[0].litellmParamsObj. That is the crash in #31590 and the open PR
#31794. #31794 only silenced the crash by pointing the test at
auto_router/complexity_router, which is not a provider model, so the
/health/test_connection health check (a real litellm.ahealth_check
completion) would still error.

Bring the button back and make it meaningful: an auto router dispatches to
saved model groups, so Test Connection now probes those directly. It builds
a deduped target list from the configured tiers (tiers sharing a model group
collapse to one probe) plus the embedding model when semantic keyword
matching is on, then runs a live /health/test_connection against each and
shows per-target pass/fail. This never touches prepareModelAddRequest, so the
original destructure crash cannot recur.

Scope is the recommended complexity router only; the to-be-deprecated
semantic router is untouched. No backend changes.

Supersedes #31794. Resolves #31590.

* fix(ui): probe auto-router tiers via real proxy routing, not /health/test_connection

Live testing showed the first cut was broken: /health/test_connection merges
{...configParams, ...requestParams}, so passing the public model_group name as
the request model overrode the resolved provider model and every tier failed
with "LLM Provider NOT provided". The frontend only has the public group name,
not the underlying litellm_params, so it cannot build the request that endpoint
needs.

Switch to testing each model group the way production actually routes it: send a
minimal request to /v1/chat/completions (or /v1/embeddings for the embedding
model) by public group name through the shared apiClient. The router resolves
the group, credentials, and provider itself, so a green row means the tier is
genuinely reachable. Verified live: voyage embedding returns 200, a tier with a
bad key returns the real provider auth error.

Also address Greptile feedback: rows now update progressively as each probe
settles instead of all at once, and TIER_ORDER is derived through a
`satisfies Record<keyof ComplexityTiers, null>` guard so adding a tier without
listing it is a compile error.

* fix(completion): forward aws credential kwargs into litellm_params so the responses bridge keeps WIF auth

Chat-completions requests to responses-only Bedrock Mantle models are
bridged to the Responses API, but completion() forwarded only
aws_bedrock_project_id into get_litellm_params, so aws_role_name,
aws_web_identity_token, aws_session_name and the other SigV4 credential
kwargs never reached sign_request and botocore fell back to the default
credential chain ("Bedrock Mantle auth failed: no Bearer token and no
usable AWS credentials"). Forward the whole AWS credential kwarg family,
extracted from the OPTIONAL_KWARGS_KEYS set get_litellm_params already
supports.

* fix(bedrock): allow bedrock-mantle:CreateInference in the web identity session policy

* fix(ui): drop max_tokens from the auto-router connection probe

max_tokens=1 makes reasoning models (o1/o3/...) return a 400 "max_tokens
reached" because reasoning tokens count against the cap, so a reachable
reasoning tier showed a false failure in Test Connection. Live-verified: o3
400s with the cap and succeeds without it.

Extract the request shape into a pure buildModelGroupTestRequest and cover it
with a test asserting the chat body carries no max_tokens (or
max_completion_tokens), so this regression is caught in unit tests instead of
only against a live reasoning model.

* test(main): assert the responses bridge forwards static aws keys as well as web identity params

* docs(github): add QA runbook section to the PR template

* docs(github): scope the QA runbook to tests/e2e edits and add example checklists

* docs(github): shape QA runbook examples as node id plus behavior bullets

* fix(xecguard): use StandardLoggingGuardrailInformation in logging hook (#32911)

XecGuard's async_logging_hook wrote a bare dict to
standard_logging_object["guardrail_information"] while the typed
contract is Optional[List[StandardLoggingGuardrailInformation]].
Readers that iterated the field walked dict keys, raised on
info.get, or silently dropped the entry from guardrail usage
tracking and spend-log writes

Construct the typed entry and append it to the existing list or
create a new one, matching the shared helper pattern. Record the
configured guardrail name instead of a hardcoded "xecguard" and
pass the GuardrailEventHooks enum for guardrail_mode

* feat(ui): adopt openapi-react-query ($api) and convert useCustomers (#32949)

* feat(ui): adopt openapi-react-query and convert useCustomers to $api

Add openapi-react-query and expose $api = createQueryClient(fetchClient)
alongside fetchClient. Rewrite useCustomers as
$api.useQuery("get", "/customer/list", {}, { enabled, select }), which
derives the query key from method + path (dropping the hand-written
createQueryKeys entry and the manual key) and forwards the request signal
for cancellation. The response type still flows from schema.d.ts as
CustomerResponse[]. Tests assert the path, the admin/token enabled gate,
and the empty-body select fallback.

* test(ui): read the last render's options in useCustomers helper

The lastCallOptions helper was named for the last call but read
mock.calls[0]. Harmless while each test renders once, but it would
silently assert against first-render options if a test ever re-renders.
Read the final call instead.

* refactor(ui): colocate the mcp-servers view, keeping the shared mcp_tools surface (#32968)

* refactor(ui): colocate the usage view, keeping the shared usage components

Split for the usage (UsagePage) segment. Most of the folder is the usage page's
own view, but four pieces are reused elsewhere and stay in @/components/UsagePage:
TopKeyView (old-usage), KeyModelUsageView and value_formatters (activity_metrics),
and the shared types (activity_metrics, chartUtils). The other 21 files move into
usage/_components, preserving the folder structure.

The external consumers import only the retained files, so they are untouched. The
moved files' imports of the retained files become @/components/UsagePage paths,
other escaping relative imports are absolutized, and lint suppressions are re-keyed
for moved files only. No behavior change.

* refactor(ui): colocate the mcp-servers view, keeping the shared mcp_tools surface

* docs(github): add Final Attestation and per-test sanity-check step to QA runbook

* refactor(ui): convert endpoint usage charts to shadcn/recharts (#32723)

* refactor(ui): convert endpoint usage charts to shadcn/recharts

Adds a LineChart wrapper to the shared charts kit, mirroring the
BarChart/AreaChart composition with connectNulls and curveType props,
and converts EndpointUsageBarChart and EndpointUsageLineChart from
tremor to the shared wrappers. Both endpoint chart tests now assert on
real recharts SVG output instead of tremor mocks.

* refactor(ui): drop unused endpointData prop from EndpointUsageLineChart

* fix(ui): point endpoint chart test type imports at the UsagePage types alias after colocation move

* fix(auto_router): filter embedding models out of tier selects, require all tiers, add inline validation

The Add Auto Router complexity tab let chat models fill the embedding-model
slot (and vice versa) since neither dropdown filtered on ModelGroup.mode, and
submit only required at least one of the four tiers instead of all four. Adds
getMissingTiersError alongside the existing getSemanticConfigError, and
highlights unfilled tier/embedding selects inline once a submit attempt fails.

* fix(model-cost-map): anchor the bedrock-claude-ids routing rule to the start of the id

* fix(proxy-auth): deny provider-wildcard access inferred through an unrecognized model namespace

* fix(auto_router): reset inline validation errors when switching router type

* fix(auto_router): flag name field and tier fields together on empty submit

Clicking Add Auto Router with the name empty returned early with only a
toast, so blank tier selects never got their inline error state. The
empty-name branch now sets showValidationErrors and triggers antd
validation on the name field, so every unfilled mandatory field is
flagged at once. Adds a regression test for the tab component.

* feat(mcp): mint gateway-bound envelope at the token endpoint for dcr_bridge oauth_delegate

* feat(mcp): seal the authorizing key hash in the dcr_bridge envelope

The mint bound only user_id/server_id into the envelope, which gave admission
no way to reload the caller's key and enforce its current restrictions. Seal the
hashed authorizing key instead (a one-way digest, not a usable credential), so
admission reloads the live UserAPIKeyAuth by it and the key's team/org/tool
permissions and revocation apply per request.

Extract the token endpoint's key resolution into a shared _resolve_active_litellm_key
so the per-user token store (user_id) and the bridge mint (key hash) derive from one
active-key-gated path, and fail the mint closed with invalid_request when no active
key accompanies the request.

* fix(mcp): return 502 not KeyError when a bridge upstream response lacks access_token

The eager access_token = token_response["access_token"] extraction ran before
the dcr_bridge branch, so a missing upstream access_token raised an unhandled
KeyError and _bridge_grant_from_token_response's nil guard (which maps to a clean
502) was dead code. Move the extraction onto the non-bridge result path so the
bridge branch reaches its 502 guard.

* fix(mcp): let a keyless-user active key mint a bridge envelope

_resolve_active_litellm_key gated on _active_key_user_id, which returns None both
for blocked/expired keys AND for valid keys with no user_id, so a team-scoped or
service-account key was wrongly rejected with invalid_request at bridge token
exchange. Split the active-state gate (_key_is_active: blocked/expiry only) from
the user_id extraction; the mint seals the key hash, not the user, and admission
already handles a keyless-user key. The per-user token store still gets no user
for such a key, as there is none to key a stored credential by.

* style(mcp): use X | None annotations on the touched key-resolution helpers

The keyless-user fix moved these signatures, so their pre-existing Optional[...]
annotations counted against the diff and tripped the UP045 strict-budget gate.
Modernize the four touched return annotations to the X | None form the gate
wants; runtime behavior is unchanged.

* fix(mcp): coerce numeric expires_in and make the active-key check total

Two correctness gaps in the bridge mint. _bridge_grant_from_token_response only
accepted an int expires_in, dropping a float (3600.0) or numeric-string ('3600')
lifetime to None so the envelope fell back to its 1h cap and could outlive a
shorter-lived upstream token; coerce it to a positive int (bool excluded). And
_key_is_active called datetime.fromisoformat on the str|datetime expires outside
the resolver's try, so a malformed stored expiry raised an unhandled 500 instead
of the fail-closed invalid_request; it now fails closed (inactive) on an
unparseable expiry. Regression tests cover int/float/string/bool coercion, the
short-float TTL, and the malformed-expiry fail-closed path.

* fix(mcp): harden the bridge token mint (multi-lens review pass)

Findings from a full adversarial review of the mint path across security,
correctness, error-handling, concurrency, and OAuth-protocol dimensions.

- expires_in coercion is now total: int(float(...)) can raise OverflowError on
  Infinity / a giant numeric string, which escaped the ValueError/TypeError catch
  and 500'd the token endpoint. Unified to catch OverflowError too.
- Resolve the litellm identity BEFORE exchanging the single-use upstream code, so
  a missing or transiently-unresolvable identity fails closed with invalid_request
  without burning the code (the mint re-resolves via a cache hit).
- The no-identity failure is now an RFC 6749 5.2-shaped invalid_request
  (JSONResponse, top-level error, no-store) instead of a detail-wrapped
  HTTPException, matching the BYOK OAuth endpoint.
- EnvelopeTooLarge (upstream token too big to seal) surfaces a 502, not a 500.
- The upstream refresh_token is no longer sealed into the envelope: the edge
  never consumes it, so it was dead weight embedding a long-lived upstream
  credential in the client bearer and enlarging the envelope; refresh is a
  follow-up (a dedicated refresh-envelope).

Security review found no exploitable defect (forgery, cross-server/user replay,
leakage, confused-deputy all closed). Regression tests cover the OverflowError,
the code-not-burned path, the RFC-shaped error, the 502, and the dropped refresh.

* fix(mcp): close the burn-before-check gate for both grants and validate master_key first

Follow-up to the pre-exchange identity gate, which I had only added to the
authorization_code branch and which left the master_key check inside the mint
(after the upstream exchange) - so the very burn-then-fail pattern it was meant to
prevent still applied to refresh_token grants and to a misconfigured gateway.

- Hoist a single pre-exchange gate above the upstream call that covers BOTH grant
  types: it fails closed (invalid_request) on an unresolvable litellm identity and
  500s on an unset master_key BEFORE the single-use code or refresh token is
  exchanged/rotated, so a bad key or a misconfigured gateway never burns the
  upstream credential.
- Report expires_in from the envelope JWT's own second-truncated exp (rounding the
  elapsed portion up) instead of the raw expires_at - now delta, so the client is
  never told the bearer is valid past the ~1s point admission already expires it.

Regression tests assert the upstream exchange is never called on the no-identity
refresh grant and the master_key-unset path, and that the reported expires_in does
not overstate the JWT exp.

* refactor(mcp): make the bridge delegate mint a phased failures-as-values pipeline

The dcr_bridge oauth_delegate token mint validated its preconditions in two
places: a pre-exchange guard inside exchange_token_with_server (master_key set,
resolvable litellm identity) and an authoritative re-check inside the post-exchange
_mint_bridge_delegate_token_response. Keeping the two in step by hand is what kept
producing the same class of finding: a precondition guarded on one grant branch but
not the other, master_key checked after the exchange on one path, identity resolved
twice, and each failure raising an ad-hoc HTTPException with its own status and body
shape.

Model the mint as three phases whose failures are values. _prepare_bridge_mint runs
before the exchange, checks every precondition once (master_key, then identity), and
returns either a frozen _BridgeMintReady carrying the resolved key hash and the
master-key-derived envelope keys, or a _BridgeMintError literal. Because every
precondition lives in prepare, and prepare runs before the upstream POST, no failure
can burn the single-use code or rotate a refresh token, for either grant type, by
construction rather than by a guard we have to remember to keep in sync.
_finish_bridge_mint runs after the exchange and has no preconditions left that can
fail; its only failure values are properties of the upstream response itself (no
usable access_token, or a token too large to seal). One mapper,
_bridge_mint_error_response, turns each _BridgeMintError into an RFC 6749 section
5.2-shaped body with a status truthful about where the failure is (400 for the
caller, 500 for gateway config, 502 for the upstream), with an exhaustive match plus
assert_never so a new failure mode cannot be added without a matching status.

Behavior is unchanged for the client. Every failure that previously raised now
returns the same status as an OAuth error body, which is the correct token-endpoint
contract; the three tests that asserted a raised HTTPException now assert the
returned response. _exchange_for_bridge_server additionally asserts the identity
resolver is awaited exactly once for a bridge server and never for a non-bridge one.

* fix(mcp): let the bridge envelope report expires_in 0 at the jwt exp boundary

_finish_bridge_mint floored the reported expires_in at 1. Admission expires the
envelope against the JWT's second-truncated exp, so when the mint lands in the same
second that exp falls on (a sub-second upstream lifetime, for instance), the true
remaining life is 0 and reporting 1 tells the client the bearer lives one second past
the point admission already rejects it. Floor at 0 instead so the reported lifetime
never overstates the exp; the value still cannot go negative.

The regression pins the boundary directly: minting at now=100.25 with a 1s upstream
token seals exp=101, and the reported expires_in is max(0, 101 - ceil(100.25)) = 0.
Under the old floor of 1 it reads 1, so the test fails on that mutation.

Also drops the unused mcp_server parameter from _prepare_bridge_mint; identity and
key derivation there never referenced the server.

* refactor(mcp): make bridge-mint resolvers return tagged unions so status is truthful by construction

Three findings landed together, all one defect: a resolution step crushed several distinct outcomes
into a single None or a silent default, so the mint's error mapper could not tell them apart and
assigned the wrong status. Identity resolution mapped a database outage to the same None as a missing
credential, which the mint reported as 400 invalid_request, blaming the caller for a gateway outage
while admission statuses the same outage 503/500. Lifetime coercion mapped an explicit non-positive
expires_in to the same None as an absent one, so an upstream token the IdP reports as already dead was
sealed into an hour-long envelope. And the refresh_token grant was run through the upstream exchange
(which can rotate the client's upstream refresh credential) and its result then discarded, even though
a bridge server seals no refresh_token and the client never holds one to present.

Rather than add a mapping branch per finding, the fix changes the return types so a wrong status is not
representable. Each resolution step now returns a precise tagged value instead of None: identity
resolution returns a _ResolvedKey or one of no_active_key / unavailable / unresolvable, classified the
same way admission's _reload_admitted_key classifies the same conditions; upstream-lifetime
classification returns a positive number of seconds, "unspecified" (absent or unparseable, which the
envelope caps), or "expired" (a parseable non-positive value, an already-dead token); and upstream-grant
validation returns a typed grant or one of no_access_token / expired_lifetime. Thin exhaustive mappers
(match plus assert_never) lift each vocabulary into one bridge-mint taxonomy of eight named failures,
and a single _bridge_mint_error_response gives each its truthful RFC 6749 §5.2 status: 400 for the
caller's missing credential or an unsupported grant, 503 for a transient auth-DB outage, 500 for a
gateway that cannot resolve identity or is not configured, and 502 for an upstream response with no
usable token, an already-expired lifetime, or a token too large to seal. Adding a failure mode now
requires a new literal and a match arm the type checker forces, so the class of wrong-status bug cannot
recur silently.

The refresh_token grant is rejected in _prepare_bridge_mint before the exchange with
unsupported_grant_type, so it can never rotate or consume the client's upstream refresh credential;
renewal is re-running authorization_code, as the sealed refresh_token=None already intends. An absent or
unparseable expires_in still mints a capped envelope (the by-design behaviour for an upstream that omits
the field); only an explicitly-dead lifetime is rejected.

Tests cover the resolver's three failure classes (including a real connection-error outage and a missing
prisma_client), the mint statuses for each (503 before the upstream exchange, 500, 502 on an expired
upstream lifetime, and a capped mint on an unknown one), and the refresh-grant rejection before any
exchange. The three findings are mutation-checked: reverting each fix turns its regression test red.

* fix(mcp): treat a positive sub-second upstream lifetime as alive, not expired

_classify_upstream_lifetime decided "expired" from int(float(expires_in)), which truncates toward
zero, so a positive fractional lifetime in (0, 1) became 0 and was misread as already elapsed. That
rejected the mint with 502 in _finish_bridge_mint after the single-use upstream code had already been
consumed, even though the upstream reported a positive remaining lifetime.

Decide expired on the parsed numeric value rather than its truncated int, so only a genuinely
non-positive value is expired. The envelope works in whole seconds and cannot represent a sub-second
lifetime, so a positive value that truncates to 0 clamps up to the 1s floor instead of being rejected.
Values >= 1 still truncate toward zero so the envelope never claims more life than the upstream stated,
and NaN / Infinity / oversized input still read as unparseable ("unspecified").

Regression covers the classifier (0.5 and 0.001 clamp to 1, 1.9 truncates to 1, -0.5 stays expired) and
the mint (a 0.5s upstream lifetime mints a 200 envelope rather than a 502); reverting to the
truncate-then-check reddens both.

* fix(auto_router): inline error for missing LLM classifier model

Selecting the LLM classifier without picking a model only surfaced a
toast on submit; the classifier model select now gets the same red
outline and helper text as the tier and embedding selects once a submit
attempt has failed.

* build(dev-env): add make bootstrap and unprovisioned-checkout preflight to pre-commit

* feat(router): random-pick multi-model complexity tiers (#32967)

* feat(router): random-pick multi-model complexity tiers

Tier pools already make sense without adaptive; stop pinning lists to
index 0 and shuffle within the classified tier instead.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ci): format complexity router config

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ci): use PEP 585 types for tier pools

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(xecguard): sanitize scan result before recording it for logging (#32935)

* chore: keep it brief

* chore: keep it brief

* docs(readme): point developer-mode setup at make bootstrap

* chore: keep it concise

* feat(router): add Router(plugins=[...]) routing-plugin pipeline (#32972)

* feat(router): add Router(plugins=[...]) routing-plugin pipeline

Runs a sequence of user-supplied plugins before the routing decision is
made. Each plugin reads/mutates a RoutingContext (messages, candidate
models, metadata, signals); the narrowed candidate list is enforced when
picking a deployment, raising rather than silently falling back if a
plugin narrows to zero candidates.

Prototype for the routing-plugin pipeline discussed in #32168.

* fix(router): use ruff-modern typing, add raw/structured messages to RoutingContext

- Use dict/list/X|None instead of Dict/List/Optional in new code, staying
  within the ruff strict-rule budget ratchet
- Extract the guardrail-translation message normalization ComplexityRouter
  already had into a shared resolve_structured_messages() helper
  (litellm_core_utils/prompt_templates/factory.py), reused by
  ComplexityRouter and the new routing-plugin pipeline instead of
  duplicating it
- RoutingContext now exposes both raw_messages (as received) and
  structured_messages (normalized across chat completions / Anthropic
  messages / Responses API), mirroring CustomGuardrail.apply_guardrail's
  pattern, per review feedback on #32972
- Add direct unit tests for _run_routing_plugins and
  _filter_by_routing_plugin_candidates (router_code_coverage gate requires
  every router.py function be called by name somewhere in tests/)

* fix(test): rename to test_router_routing_plugins.py

router_code_coverage.py's AST scanner only inspects test files whose
filename contains the substring "router" -- test_routing_plugins.py
doesn't match (routing != router), so it silently skipped this file
and flagged _run_routing_plugins/_filter_by_routing_plugin_candidates
as untested despite the direct unit tests added for them.

* fix(router): fail closed when plugins are configured but the resolved
routing path can't run them

Router.completion() (and other sync entry points) resolves deployments
via the synchronous get_available_deployment(), which never runs
async_pre_routing_hook and therefore never runs the routing-plugin
pipeline. async_get_available_deployment() itself falls back to that
same synchronous method for routing strategies without an async-native
selector (e.g. legacy "usage-based-routing" v1). Both paths would let a
policy plugin (e.g. a deny-all rule) be silently bypassed.

Raise instead of silently proceeding when self.routing_plugins is
configured and the sync path is reached, since applying the pipeline to
every selector path is a larger change out of scope for this PR.

Per review: https://github.com/BerriAI/litellm/pull/32972/changes/BASE..bdfb583c2c6f8df10004fb249e11629d41ce71fa#r3565373303

* feat(router): soft-floor adaptive mode for complexity router (#32947)

* feat(router): soft-floor adaptive mode for complexity router

Let complexity_router_config.adaptive=true Thompson-sample across the
union of tier pools with a tier-distance penalty, and wire the existing
adaptive post-call bandit so mis-tiered requests can still recover.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(router): reattach adaptive hooks for hybrid complexity

Finalize was wiping every AdaptiveRouterPostCallHook and only
re-registering standalone auto_router/adaptive_router deployments,
so complexity adaptive=true never received bandit updates.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(router): drop unnecessary hybrid docstrings

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(router): attribute adaptive feedback

Credit user reactions to the model that produced the previous response while keeping current-response signals on the serving model

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(router): tune hybrid cold defaults

Use the cost-weighted policy that beat equal-pool complexity in the full bakeoff, and make the committed harness compare identical tier pools

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(router): preserve hybrid cold quality floor

Sample only unobserved models in the classified tier until feedback exists, then apply adaptive scoring without mis-penalizing models shared across tiers

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(router): bound feedback context cache

Cap retained session feedback so unique session IDs cannot exhaust router memory

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(router): preserve exhaustion signals

Include tool-result exhaustion in adaptive feedback and clear strict lint regressions blocking CI

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(router): remove stale owner cache

Remove obsolete attribution state, tighten the embedded router type, and keep the test diff focused on adaptive behavior

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(router): centralize hook cleanup

Use the callback manager to discover and remove adaptive hooks across every registered callback list

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(responses): continue MCP gateway tool turns from the final response and surface failures

When a /responses request uses a hosted MCP tool (server_url: litellm_proxy/<label>)
with store=true and the model calls a tool, the gateway auto-executes the tool and
streams one logical response stitched from several upstream responses: an interim
response whose only output is the function_call, then the post-tool answer

B1 (correctness): every streamed event was pinned to the first round's response id,
i.e. the interim response that carries the function_call but no tool output. The
client then continued the next turn from that dangling response and the provider
rejected it with "No tool output found for function call <id>", which on the
streaming path surfaced as a silent empty completion. The fix adopts each
auto-execute round's own response id (the cached id is reset when a follow-up round
starts) so the client continues from the final round, whose stored input chain
includes the function_call_output

B2 (robustness): initial and follow-up call failures were swallowed; the stream
emitted the mcp_list_tools discovery events and then closed with HTTP 200 and no
output and no error. The fix stashes the failure, makes the initial call eagerly in
aresponses_api_with_mcp so a pre-stream failure re-raises as a real 4xx before any
SSE bytes are written, and emits a terminal error event when a follow-up call fails
mid-stream

Adds regression tests covering continuation exposing the final round's response id
rather than the interim tool-call id, a follow-up failure emitting a terminal error
event, and an initial-call failure being stashed for eager re-raise

* ci(ui): report only error-level knip findings in CI (#32971)

* feat(batches): track cost for unmanaged Bedrock batches, generalize the flag (#32315)

* feat(batches): track cost for unmanaged Bedrock batches, generalize the flag

CheckBatchCost skipped Bedrock batches whose unified_object_id is a raw
model-invocation-job ARN, the same root cause previously fixed for
unmanaged Vertex batches. Bedrock batches embed the model name in their
s3:// input file name instead (litellm-bedrock-files-{model}-{uuid}.jsonl),
so the same routing mechanism now derives the model from that layout and
matches it to a configured bedrock deployment.

track_unmanaged_vertex_batch_cost is renamed to track_unmanaged_batch_cost
since two providers now share this mechanism.

* fix(batches): parse Bedrock batch output and price with deployment model name

Bedrock model-invocation-job results use modelOutput/error rows and short
internal model ids that are not in the cost map, so unmanaged batch cost
tracking logged tokens but $0 spend. Use deployment model name for pricing
and add regression tests.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(guardrails): walk custom_tool_call_output items in _content_utils (#32969)

* fix(guardrails): walk custom_tool_call_output items in _content_utils

* Change _OUTPUT_ITEM_TYPES to Frozenset type

* fix(guardrails): use builtin frozenset generic for _OUTPUT_ITEM_TYPES annotation

Frozenset is not a defined name (typing exports FrozenSet, the builtin is
frozenset), so module import raised NameError and broke every proxy test
suite. The builtin generic is valid on the supported python floor (3.10)
and keeps the UP006 ruff-strict budget at its ceiling, which the typing
alias would exceed

* fix: show and allow editing team model aliases after team creation (#33047)

* refactor(ui): rename OldTeams component file to Teams

* fix: show and allow editing team model aliases after team creation

* fix(ui): mark team model_aliases as nullable to match the prisma schema

* fix(ci): bump pillow to 12.3.0 to resolve osv-scan CVEs (#33093)

* fix(proxy): track unauthenticated pass-through requests in spend logs (#32410)

Pass-through endpoints configured with auth=false reach the cost-tracking callback with no key/user/team/end-user, so _should_track_cost_callback returned False and the spend-log write was skipped, leaving the request out of request/usage logs. Track pass-through call types even when unauthenticated so the SpendLog row is still written.

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

* feat(lasso): send source.type=litellm for Used By attribution (#33090)

Co-authored-by: Or Gershoni <org@lasso.security>

* feat(mcp): generalize the bridge envelope identity to a key_hash or user_id subject

The scripted two-header client mints under a virtual key it presents at the token
endpoint (key_hash), but the interactive DCR client authenticates via SSO at the
bridged authorize, which yields a user, not a key. Make EnvelopeIdentity a
discriminated subject (subject_type key_hash | user_id) with key_hash_identity /
user_identity constructors, and dispatch admission on it: a key_hash reloads the
key, a user_id reloads the user and admits them as themselves (user-level budget
and SCIM enforced via the same centralized gate; no team bound, since a user
belongs to many teams or none). The interactive producer that mints a user_id
envelope lands in the follow-up commit.

* feat(mcp): interactive SSO sign-in for dcr_bridge oauth_delegate DCR clients

Completes the oauth_delegate bridge for real DCR clients (Claude Code, Claude
Desktop), which send no litellm key and cannot use the scripted two-header path.
On the short-circuit bridge arm the gateway now captures the SSO-authenticated
litellm user from the browser session at /authorize and seals it into the OAuth
state; at /callback it seals that user plus the upstream code into a gateway
authorization code the client echoes back; at /token it recovers the user,
exchanges the real upstream code, and mints a user-subject envelope. The user
identity captured in the browser thus rides to the back-channel token call with
nothing stored server-side, and admission opens the envelope under that user. The
scripted key_hash path is unchanged (raw upstream code, key from the request);
without a session the browser is sent through login first.

* fix(mcp): classify the user-subject reload's errors like the key path (503 outage, 401 missing)

_reload_admitted_user mirrored only part of _reload_admitted_key's error contract: it caught
ProxyException and HTTPException but had no arm for anything else, so a transient DB outage surfaced as
an opaque 500 instead of the retryable 503 the key path guarantees, and a missing user surfaced as a 500
too. The missing-user case is the subtle one: get_user_object raises a bare Exception for a deleted user
(not a ProxyException like get_key_object does for a missing key), so the ProxyException/HTTPException
clause never caught it and the user_object-is-None branch it was supposed to hit is unreachable on the
production path.

Add the same except-Exception arm the key path uses, with the one deliberate difference the differing
get_user_object contract requires: a database-service-unavailable error still raises the retryable 503,
while a missing user or any other non-outage resolution failure fails closed as a 401 rather than
propagating as a 500. The regression tests now drive the real behavior (get_user_object raising) rather
than a None return that never happens in production, and cover both the 503 outage and the 401
missing-user paths.

* fix(mcp): admit a user-subject envelope with the user's own MCP object permission

_reload_admitted_user returned a bare UserAPIKeyAuth(user_id=...), so the shared
get_allowed_mcp_servers found no key/team/object-permission grants and an interactive SSO client could
admit successfully yet see zero tools on a normal (allow_all_keys=False) server. The key path returns
the full key record whose object permission drives that computation; the user path dropped it.

Resolve the user's own MCP object permission and put it on the returned auth, so the same
get_allowed_mcp_servers the key path uses grants the user their litellm-granted servers and access
groups. This reuses get_object_permission (the id-to-grants resolver keys and teams already use) and
does not duplicate any permission logic; get_user_object does not load object_permission, so it is
resolved from the user's object_permission_id the same way the key and team paths do.

Only the user's own object permission is bound. A UserAPIKeyAuth carries a single team_id while a user
may belong to many teams, so team-inherited MCP grants for a user are a follow-up: they need a
many-teams union get_allowed_mcp_servers does not do off one auth object, and faking one here would be
the kind of half-measure that spawns more bugs. Tests cover the user's object permission riding onto the
admitted auth, and the existing admit/SCIM/missing-user/503 cases still hold.

* fix(ui): respect litellm_key_header_name in BYOK credential save and workflow runs fetches (#33103)

* refactor(ui): standardize debounce waits behind shared DEBOUNCE_WAIT_MS constant (#33040)

* feat(ui): rebuild the Virtual Keys table on the shared DataTable (#32991)

* feat(ui): rebuild the Virtual Keys table on the shared DataTable

Replaces the hand-rolled Tremor table and bespoke toolbar/pagination on the admin
Virtual Keys page with the shared DataTable: server-side sort, paginate, and
filter, a sticky scrolling body, a search plus column-visibility plus filters
toolbar, a right-side filter drawer, and a rows-per-page footer. A page header
with the existing key icon carries the Create New Key action.

Adds reusable, shadcn-default building blocks for the tables migrating onto the
DataTable next: shared IdentityCell, ModelsCell, and SpendBudgetCell in
shared/table_cells, plus a shared PageHeader. The models cell reveals overflow in
a hover tooltip and the spend/budget cell uses the Meter primitive.

All data and domain logic is preserved, including the useKeys query, team and org
alias resolution, the user popover, and the KeyInfoView detail swap. The rich
async Team/Org/Alias filters move into the drawer, and the toolbar search maps to
the key-alias substring search. Status now also reflects key expiry alongside
blocked and SCIM-blocked.

The VirtualKeysTable tests are updated to the new markup and extended with focused
coverage for each new shared cell

* fix(ui): address Virtual Keys redesign review feedback

Fold the status badge into the clickable Key cell and drop the separate Status
column so a key's alias, secret, and status read as one unit. The Key cell is
now the single click target that opens the key detail; the whole-row click is
removed

Migrate the filter drawer off AntD to shadcn. A new Combobox composed from
Popover and Input backs the Team, Organization, and Key Alias filters, keeping
search and the alias infinite-scroll

Show $0.00 for zero spend instead of a hyphen, and extend the shared DataTable
with badge, chips, and meter skeleton shapes so the loading state matches the
loaded cells (status pill, model chips, spend meter) rather than uniform bars

Fix key sorting: the Key column sent its column id "key" as sort_by, which
/key/list rejects with 400. It now sorts by the backend field key_alias

* fix(ui): use the shadcn base combobox and refine the keys filters and skeletons

Replace the hand-rolled filter combobox with the supported shadcn Base UI combobox
(ui/combobox, added via the CLI and reused through a small SearchSelect wrapper).
Its vended input-group and textarea deps are written for React 19 (plain functions
with ref-as-prop); this app is on React 18, where those subcomponents drop the refs
Base UI passes for focus and anchoring, so InputGroupInput, InputGroupButton, and
ComboboxTrigger are adapted to forwardRef. Those ui/ files now diverge from the
registry, and a future shadcn add would overwrite the adaptation until the app moves
to React 19. Adds class-variance-authority, which input-group needs

Give loading skeletons a per-column renderSkeleton escape hatch on the shared
DataTable and mirror the Key cell exactly (alias line, secret, status pill), so
skeleton rows match the real rows instead of being shorter and simpler

Resolve the automated review: the toolbar search and the drawer Key Alias filter
both mapped to the key-alias query, so the search silently overrode the drawer value
while its chip stayed visible. Consolidate to a single alias search in the toolbar
(placeholder now "Search by key alias…") and drop the redundant drawer field. Re-add
coverage for the Created By column's alias-over-email display

Refine the Team and Organization filters: they match on name and id, so the labels
read "Team" and "Organization" rather than "... ID", each option shows the name with
the id on a muted second line instead of "name (id)", and the active-filter chip
shows the friendly name

* chore(ui): drop duplicate class-variance-authority, use the repo cva package in input-group

* fix(mcp): relay upstream OAuth token and DCR rejections instead of a generic 500

An upstream token endpoint rejection (e.g. Google requiring client_secret even for PKCE web clients) escaped exchange_token_with_server as a raw httpx.HTTPStatusError, which the global exception handler turned into an opaque 500 Internal server error in the create-flow UI. The RFC 6749 section 5.2 error body the IdP sent (error, error_description, error_uri) is now relayed with the upstream's own 400/401 status; rejections outside the section 5.2 contract map to 502 so a broken upstream is not misattributed to the caller. The same relay covers the non-bridge DCR registration arm, and a 200 token response without a usable access_token now answers 502 instead of a KeyError 500. The catch wraps the post call itself because litellm's AsyncHTTPHandler raises MaskedHTTPStatusError at call time, which also made the pre-existing bridge-relay status check unreachable in production. The dashboard's token exchange error message now composes error and error_description so the form shows the IdP's reason

* refactor(mcp): drop bridge relay status check made unreachable by the unified relay

The try/except around the registration post now relays every upstream 4xx/5xx for both arms, so the bridge_relay status_code check could never fire; removing it addresses the Greptile P2 dead-code finding

* fix(mcp): classify get_user_object's wrapped DB outage across the exception chain

get_user_object catches every DB failure in a broad except and re-raises a bare ValueError (litellm/proxy/auth/auth_checks.py), so a real outage and a missing user look identical and the original error survives only as __context__. The dcr_bridge admission path keyed its 503-vs-401 decision on the exception type, so a transient outage during a user-subject reload surfaced as a 401 rather than a retryable 503, and the regression test injected a raw ConnectionError, a shape get_user_object never produces, so it passed on a fiction

Add PrismaDBExceptionHandler.is_database_service_unavailable_error_in_chain, which walks __cause__/__context__ (bounded and cycle-safe) the PEP 3134 way, and route _raise_503_if_db_unavailable through it. Move the user's object_permission resolution inside the single classified try so an outage there is a 503 too, never an opaque 500. Pin get_user_object's wrapping with a contract test that drives the real function, and drive the reload tests with that same faithful shape so a chain-blind regression fails them

* fix: redact async complete streaming response for custom callbacks (#33106)

* fix response not being redacted for custom callbacks with streaming enabled

* reduce code duplication

* add unit test

* fix: resolve lint violations in adopted redaction fix

* fix: scope streaming response redaction to the opted-out custom logger

---------

Co-authored-by: Moritz Müller <moritz.mueller2@tu-dresden.de>

* build(ui): bump @tanstack/react-pacer from 0.2.0 to 0.22.1 (#33041)

* refactor(ui): standardize debounce waits behind shared DEBOUNCE_WAIT_MS constant

* build(ui): bump @tanstack/react-pacer from 0.2.0 to 0.22.1

* fix(ui): address Virtual Keys redesign review nits (#33112)

* fix(ui): address Virtual Keys redesign review nits

Restore sorting by budget on the merged Spend / Budget column. The column now
uses a new DataTableMultiSortHeader whose chevron opens a menu offering Spend
and Budget in both directions plus Reset, so the progress-bar cell stays merged
while the sort field becomes an explicit choice. Sorting is server-side, so the
chosen field id (spend or max_budget, both accepted by /key/list) flows straight
through as sort_by

Fill the DataTable to its container width when column resizing is on. The table
width was pinned to the sum of column widths, so hiding columns left an empty
gutter on the right. It now keeps that width as a minimum and stretches to 100%
on underflow while still scrolling on overflow, which also covers the same gap
in TeamVirtualKeysTable since both share the component

Drop the dark background box behind the page-header icon so the Virtual Keys
header reads like the Teams header, and pull the 4-line inline filter lambda in
SearchSelect out into a named matchesQuery helper

Extends the DataTable and VirtualKeysTable tests to cover the new multi-field
sort menu (field id maps to sort_by, active indicator, reset) and the
fill-to-container width

* fix(ui): emphasize the active field in the Spend / Budget sort header

The merged Spend / Budget header always read "Spend / Budget" regardless of
which field drove the sort, so after picking Budget descending there was no way
to tell what was sorted without reopening the menu. The header now builds its
label from the sort fields and emphasizes whichever one is active (bold,
full-strength text) while muting the other, so the sorted column reads at a
glance alongside the direction chevron. Drops the now-redundant title prop since
the label is derived from the fields

* fix(ui): remove w-full so the keys page content stops overflowing by 32px

The virtual keys content wrapper used "w-full mx-4", which sets the width to
100% of the parent and then adds 16px of horizontal margin on each side, so its
margin-box came to 100% + 32px and overflowed the scrollable main region by
exactly 32px. That surfaced as a horizontal scrollbar along the bottom of the
whole content area, under the pagination. A block div is already full-width, so
dropping w-full lets mx-4 inset it correctly with no overflow

* fix(ui): darken the clickable Key cell on hover so it reads as clickable

The Key cell was the click target that opens the key detail, but hovering only
faded the chevron in with no change to the cell itself, so there was no cue that
the area was clickable. Give the cell a subtle muted background and a pointer
cursor on hover. The button spans the full cell (a negative inline margin plus a
matching width offset so the hover fill reaches both cell edges while the title
stays aligned with the other columns)

* fix(openai/responses): clamp max_output_tokens below API minimum (#33098)

* fix(openai/responses): clamp max_output_tokens below API minimum

Claude Code sends a max_tokens=1 warmup probe when running /model, which
the Anthropic Messages -> Responses adapter forwards as max_output_tokens=1.
OpenAI's Responses API rejects values below 16, so the probe failed with a
400. Clamp anything below the minimum up to 16 in map_openai_params so all
Responses API entrypoints (direct, chat->responses, anthropic->responses)
are covered.

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>

* refactor(openai/responses): extract _enforce_min_max_output_tokens helper

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>

* fix(prometheus): read v3 rate limiter remaining values for per-key model gauges (#33119)

* fix(ui): drop w-full from page-content wrappers to remove 32px horizontal overflow (#33118)

Several dashboard pages wrap their content in a div styled w-full mx-4, so the
element's width is 100% of the scrollable main while mx-4 adds 16px of margin on
each side. That makes the margin-box 100% + 32px wide, which overflows main by
exactly 32px. Because main uses overflow-y-auto its overflow-x computes to auto,
so the overflow surfaces as a horizontal scrollbar along the bottom of the whole
content area under the pagination

The wrapped block is already full width without w-full, so removing that one
token keeps the layout and drops the overflow to 0. This is the same fix already
applied to the Virtual Keys page in #33112, extended to the remaining pages that
share the wrapper: Models + Endpoints, Tag Management, Organizations, Vector
Stores, AI Hub, and Logging & Alerts

* refactor(ui): migrate straightforward value debounces to react-pacer (#33042)

* refactor(ui): standardize debounce waits behind shared DEBOUNCE_WAIT_MS constant

* build(ui): bump @tanstack/react-pacer from 0.2.0 to 0.22.1

* refactor(ui): migrate straightforward value debounces to react-pacer

* feat(mcp): client-held refresh envelope for the dcr_bridge oauth_delegate flow

A dcr_bridge oauth_delegate access envelope is capped at one hour, and until now the mode had no refresh
at all: when the envelope expired the client had to re-run the interactive authorization_code flow. This
adds a second client-held credential, the refresh envelope, so the client renews on a back channel and
only re-authenticates when the refresh envelope expires or the upstream refresh token dies.

The refresh envelope is a distinct llm_refresh_ credential that seals only the upstream refresh token
(never the access token) bound to the same litellm identity and MCP server as the access envelope, under
the same master-key-derived keys, with nothing stored server-side. Both envelopes now carry a signed
kind claim ("access" or "refresh") that open() requires to match, so a refresh envelope can never open as
an access credential even if its wire prefix is swapped (the prefix is not signed; the claim is). A
refresh envelope presented at the MCP tool-call edge is not an access envelope, so admission fails it
closed the same way it already fails any non-access bearer.

At the token endpoint the authorization_code mint now returns a refresh envelope alongside the access
envelope whenever the upstream returned a refresh token, and the refresh_token grant is supported for
bridge servers: the client presents its refresh envelope, the endpoint opens it, re-validates the sealed
litellm key so a revoked key cannot keep refreshing, unwraps the real upstream refresh token, exchanges
it with the upstream IdP, and returns a fresh access envelope. Because the endpoint re-seals a refresh
envelope only when the upstream returns a new refresh token, the design mirrors the upstream's own
rotation policy rather than reinventing it: with a rotating upstream the client rotates and reuse is
detected upstream; with a non-rotating upstream the original refresh envelope stands until its bounded
14-day TTL. Both preconditions and the unwrap run before the exchange, so a rejected refresh never
consumes or rotates an upstream token.

The pure envelope and credential layers stay side-effect free: mint/open share one signing, size, and
kind gate across both envelope kinds, and every failure is a value. Tests cover the refresh round-trip,
the kind-claim and server-id bindings, the revoked-key gate, upstream rotation carried through, the
unwrap sending the real upstream token upstream, and edge rejection of a refresh envelope; the three
security bindings are mutation-checked. Limitation documented in the PR: gateway-enforced refresh
rotation with reuse detection would require server-side state, which this zero-custody mode omits by
design, so the refresh envelope inherits the upstream's rotation posture plus gateway identity binding
and a bounded TTL.

* fix(mcp): reject a refresh envelope explicitly at the tool-call edge

The live proof showed a refresh envelope presented at the MCP tool-call edge was rejected, but through
the generic oauth2 arm ("expected a virtual key starting with sk-") rather than the bridge arm, because
the admission routing gate is_bridge_envelope_shaped matched only the access prefix. The rejection was
already fail-closed and never forwarded anything upstream, but the path was imprecise and the unit test
modelled a route the real router did not take.

Match either envelope kind in is_bridge_envelope_shaped so the bridge arm engages for a refresh envelope
too, and have resolve_bridge_envelope return BridgeEnvelopeInvalid for it: a refresh envelope is a valid
gateway credential but only ever presented back to the token endpoint, never usable to authenticate a
tool call. Admission now fails it closed with the bridge arm's own 401 ("Invalid or expired
credential"), live-verified, with the upstream never touched. is_bridge_envelope_shaped has a single
caller (the admission routing gate), so the change is contained.

* fix(mcp): SecretStr the unwrapped refresh token, drop the dead request arg, fail closed on a missing user

Three review findings on the refresh path, addressed at the root:

_BridgeRefreshReady.upstream_refresh_token was a plain str, the one credential in the envelope/bridge
layer that escaped the SecretStr discipline every other one follows (RefreshCredential.refresh_token,
UpstreamTokenGrant.access_token, EnvelopeKeys.signing_key). A repr or a traceback capturing a local
_BridgeRefreshReady would have logged the raw upstream refresh token. It is now a SecretStr, carried as
the SecretStr open_bridge_refresh_envelope already returns and unwrapped only at the point the exchange
builds the upstream request body.

_prepare_bridge_refresh took a request it never read; on the refresh path identity comes entirely from
the sealed envelope, not the HTTP request, so the parameter was dead and misleadingly implied it read
from the request the way the authorization_code prepare does. Removed, and the caller updated.

_reload_active_user_by_id misclassified a missing user as unresolvable (500). This is the same root
cause as the admission user-reload fix: get_user_object raises a bare Exception for a deleted user
rather than a ProxyException, so its except-Exception arm must fail closed to no_active_key (which the
refresh path maps to invalid_grant) for anything that is not a database-service-unavailable outage,
rather than treating a missing user as an opaque gateway fault. Regression tests cover the missing-user
and DB-outage classifications directly.

* fix(mcp): make the dcr_bridge refresh path fail correctly on outages, dead tokens, and revoked owners

Four fixes to the refresh_token grant for dcr_bridge oauth_delegate, surfaced by an adversarial pass over the exchange path

Route the user-subject re-validation's outage check through the chain-aware classifier, so a transient DB outage (which get_user_object wraps in a bare ValueError) reports as unavailable (a retryable 503) rather than collapsing to no_active_key and an invalid_grant, matching how admission now handles the same wrapper

When the upstream reports its own refresh token as already elapsed (refresh_expires_in non-positive), do not seal it into a full-TTL refresh envelope; return no refresh so the exchange degrades to an access-only response, mirroring how the access grant refuses an already-elapsed access token instead of capping it

When the upstream rejects the sealed refresh token with 400 invalid_grant (revoked or expired at the IdP), return an RFC 6749 invalid_grant response so the OAuth client re-runs authorization_code, rather than surfacing the opaque upstream error it cannot act on

Gate key-subject renewal on the owner's SCIM state, mirroring admission's _reject_if_admitted_owner_scim_deactivated, so an offboarded user cannot keep refreshing a still-active key; the check fails open on a missing owner or a DB blip so a key that outlives its owner record does not get wrongly revoked

Each fix has a mutation-checked regression test

* test(proxy): add regression tests for management_endpoints edge cases (#32976)

Mutation testing surfaced branches in cost_tracking_settings and common_utils that the suite executed but never asserted on. Pin those behaviors with targeted tests: the returned (model, provider) from _resolve_model_for_cost_lookup for deployments carrying a custom_llm_provider and for deployments missing the litellm_params / model_info keys, plus the exact error-response bodies, the caller-identity lookup arguments, and the member and guard branches in common_utils.

* fix(auto-router): correct Responses API tool_choice shape and propagate alias litellm_params (#32974)

* fix(anthropic-messages): send bare-string tool_choice to Responses API, propagate router-alias litellm_params

The Anthropic /v1/messages -> Responses API adapter always wrapped
tool_choice in an object ({"type": "auto"}, {"type": "required"}), but
the Responses API's tool_choice schema for these cases is a bare
string ("auto"/"required"/"none"). Sending the object shape to an
OpenAI-compatible backend (e.g. vLLM) fails Pydantic validation with a
400. The "none" case also fell through to "auto" instead of mapping to
"none".

Separately, litellm_params configured directly on a router-alias
deployment (auto_router/complexity_router, adaptive_router,
quality_router, or semantic auto_router) - e.g.
cache_control_injection_points, drop_params - were silently dropped
for every request through that alias. async_pre_routing_hook swaps
`model` from the alias name to the selected tier/route's model before
the deployment lookup runs, so the outbound call only ever merged in
the tier deployment's own litellm_params, never the alias's. Register
non-routing-config litellm_params from the alias deployment and apply
them to the request whenever a pre-routing hook substitutes the model.

* fix: satisfy ruff-strict-budget UP006 and router coverage checker

Use builtin dict[...] generics instead of typing.Dict for the two new
annotations introduced in the previous commit, since they pushed
UP006 over the codebase ceiling in ruff-strict-budget.json. Add a
direct unit test for _register_pre_routing_alias_overrides so the
text-based router_code_coverage.py checker sees it exercised by name.

* fix(router): replace alias-param denylist with a tight allowlist

_PRE_ROUTING_ALIAS_RESERVED_PARAMS excluded router-init-only keys from
the alias's litellm_params before forwarding the rest as request
kwargs, but GenericLiteLLMParams also holds deployment-management
fields (tpm, rpm, weight, tags, max_budget, budget_duration,
use_in_pass_through, litellm_credential_name, ...) on the same object.
Any of those left off the denylist would get silently forwarded as if
they were request kwargs.

Replace the denylist with a tight allowlist of exactly the two
request-shaping params this feature exists for - drop_params and
cache_control_injection_points - so unrelated management fields never
reach the outbound call regardless of what else GenericLiteLLMParams
grows to hold.

* fix(router): re-register adaptive-alias overrides on set_model_list reload

set_model_list() unconditionally clears pre_routing_alias_overrides on
every call (e.g. /config/reload), but _finalize_adaptive_router_if_configured()
skips rebuilding an AdaptiveRouter whose model_name already exists in
self.adaptive_routers - so _register_pre_routing_alias_overrides() never
ran again for an auto_router/adaptive_router alias after a reload,
silently dropping its drop_params/cache_control_injection_points.

Build the Deployment unconditionally and re-register its overrides even
on the skip-existing-router path; only the …
doonga pushed a commit to greyrock-labs/home-ops that referenced this pull request Jul 29, 2026
…4.0) (#201)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/berriai/litellm](https://images.chainguard.dev/directory/image/wolfi-base/overview) ([source](https://github.com/BerriAI/litellm)) | minor | `v1.93.0` → `v1.94.0` |

---

### Release Notes

<details>
<summary>BerriAI/litellm (ghcr.io/berriai/litellm)</summary>

### [`v1.94.0`](https://github.com/BerriAI/litellm/releases/tag/v1.94.0)

[Compare Source](https://github.com/BerriAI/litellm/compare/v1.94.0...v1.94.0)

##### Verify Docker Image Signature

All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](https://github.com/BerriAI/litellm/commit/0112e53046018d726492c814b3644b7d376029d0).

**Verify using the pinned commit hash (recommended):**

A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \
  ghcr.io/berriai/litellm:v1.94.0
```

**Verify using the release tag (convenience):**

Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/v1.94.0/cosign.pub \
  ghcr.io/berriai/litellm:v1.94.0
```

Expected output:

```
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
```

***

##### What's Changed

- feat(ui): working Test Connection for the complexity auto router by [@&#8203;akapur99](https://github.com/akapur99) in [#&#8203;32950](https://github.com/BerriAI/litellm/pull/32950)
- fix(xecguard): use StandardLoggingGuardrailInformation in logging hook by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;32911](https://github.com/BerriAI/litellm/pull/32911)
- feat(ui): adopt openapi-react-query ($api) and convert useCustomers by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;32949](https://github.com/BerriAI/litellm/pull/32949)
- refactor(ui): colocate the mcp-servers view, keeping the shared mcp\_tools surface by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;32968](https://github.com/BerriAI/litellm/pull/32968)
- refactor(ui): convert endpoint usage charts to shadcn/recharts by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;32723](https://github.com/BerriAI/litellm/pull/32723)
- fix(proxy-auth): stop unrecognized model namespaces slipping through provider wildcard keys by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;32979](https://github.com/BerriAI/litellm/pull/32979)
- feat(router): random-pick multi-model complexity tiers by [@&#8203;krrish-berri-2](https://github.com/krrish-berri-2) in [#&#8203;32967](https://github.com/BerriAI/litellm/pull/32967)
- fix(xecguard): sanitize scan result before recording it for logging by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;32935](https://github.com/BerriAI/litellm/pull/32935)
- fix(auto\_router): filter embedding models in complexity tab dropdowns, require all tiers, inline validation by [@&#8203;akapur99](https://github.com/akapur99) in [#&#8203;32978](https://github.com/BerriAI/litellm/pull/32978)
- fix(anthropic): translate raw adaptive thinking for pre-4.6 models on chat completions and Bedrock Converse by [@&#8203;akapur99](https://github.com/akapur99) in [#&#8203;32944](https://github.com/BerriAI/litellm/pull/32944)
- feat(router): add Router(plugins=\[...]) routing-plugin pipeline by [@&#8203;krrish-berri-2](https://github.com/krrish-berri-2) in [#&#8203;32972](https://github.com/BerriAI/litellm/pull/32972)
- feat(router): soft-floor adaptive mode for complexity router by [@&#8203;krrish-berri-2](https://github.com/krrish-berri-2) in [#&#8203;32947](https://github.com/BerriAI/litellm/pull/32947)
- docs(github): add QA runbook section to the PR template by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;32965](https://github.com/BerriAI/litellm/pull/32965)
- fix(model\_cost): add supports\_reasoning: false to Gemini image generation models by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;32836](https://github.com/BerriAI/litellm/pull/32836)
- build(dev-env): add make bootstrap and unprovisioned-checkout preflight to pre-commit by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;32981](https://github.com/BerriAI/litellm/pull/32981)
- ci(ui): report only error-level knip findings in CI by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;32971](https://github.com/BerriAI/litellm/pull/32971)
- feat(batches): track cost for unmanaged Bedrock batches, generalize the flag by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;32315](https://github.com/BerriAI/litellm/pull/32315)
- fix(guardrails): walk custom\_tool\_call\_output items in \_content\_utils by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;32969](https://github.com/BerriAI/litellm/pull/32969)
- fix: show and allow editing team model aliases after team creation by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33047](https://github.com/BerriAI/litellm/pull/33047)
- chore(deps): bump pillow to 12.3.0 to resolve osv-scan CVEs by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33093](https://github.com/BerriAI/litellm/pull/33093)
- feat(mcp): mint gateway-bound envelope at the token endpoint for dcr\_bridge oauth\_delegate by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;32828](https://github.com/BerriAI/litellm/pull/32828)
- fix(mcp): surface rejected delegate-auth upstream tokens as connect-time 401 by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;32741](https://github.com/BerriAI/litellm/pull/32741)
- fix(proxy): track unauthenticated pass-through requests in spend logs by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;32410](https://github.com/BerriAI/litellm/pull/32410)
- feat(lasso): send source.type for Used By attribution by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33090](https://github.com/BerriAI/litellm/pull/33090)
- fix(responses): continue MCP gateway tool turns from the final response and surface failures by [@&#8203;thibault-linktree](https://github.com/thibault-linktree) in [#&#8203;33025](https://github.com/BerriAI/litellm/pull/33025)
- fix(responses): continue MCP gateway tool turns from the final response and surface failures by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;33099](https://github.com/BerriAI/litellm/pull/33099)
- fix(completion): forward aws credential kwargs into litellm\_params so the responses bridge keeps WIF auth by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;32956](https://github.com/BerriAI/litellm/pull/32956)
- fix(ui): respect litellm\_key\_header\_name in BYOK credential save and workflow runs fetches by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33103](https://github.com/BerriAI/litellm/pull/33103)
- refactor(ui): standardize debounce waits behind shared DEBOUNCE\_WAIT\_MS constant by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33040](https://github.com/BerriAI/litellm/pull/33040)
- feat(ui): rebuild the Virtual Keys table on the shared DataTable by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;32991](https://github.com/BerriAI/litellm/pull/32991)
- fix: redact async complete streaming response for custom callbacks by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33106](https://github.com/BerriAI/litellm/pull/33106)
- build(ui): bump [@&#8203;tanstack/react-pacer](https://github.com/tanstack/react-pacer) from 0.2.0 to 0.22.1 by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33041](https://github.com/BerriAI/litellm/pull/33041)
- fix(ui): address Virtual Keys redesign review nits by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33112](https://github.com/BerriAI/litellm/pull/33112)
- fix(openai/responses): clamp max\_output\_tokens below API minimum by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33098](https://github.com/BerriAI/litellm/pull/33098)
- fix(prometheus): read v3 rate limiter remaining values for per-key model gauges by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33119](https://github.com/BerriAI/litellm/pull/33119)
- fix(ui): drop w-full from page-content wrappers to remove 32px horizontal overflow by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33118](https://github.com/BerriAI/litellm/pull/33118)
- refactor(ui): migrate straightforward value debounces to react-pacer by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33042](https://github.com/BerriAI/litellm/pull/33042)
- feat(mcp): interactive SSO sign-in for dcr\_bridge oauth\_delegate DCR clients by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;32946](https://github.com/BerriAI/litellm/pull/32946)
- test(proxy): add regression tests for management\_endpoints edge cases by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;32976](https://github.com/BerriAI/litellm/pull/32976)
- fix(auto-router): correct Responses API tool\_choice shape and propagate alias litellm\_params by [@&#8203;krrish-berri-2](https://github.com/krrish-berri-2) in [#&#8203;32974](https://github.com/BerriAI/litellm/pull/32974)
- fix(ui): render the sidebar scrollbar with shadcn ScrollArea by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33124](https://github.com/BerriAI/litellm/pull/33124)
- refactor(ui): migrate callback debounce sites to react-pacer with regression tests by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33043](https://github.com/BerriAI/litellm/pull/33043)
- chore: add CODEOWNERS for ui and proxy UI build artifacts by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33131](https://github.com/BerriAI/litellm/pull/33131)
- feat(mcp): client-held refresh envelope for the dcr\_bridge oauth\_delegate flow by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;32980](https://github.com/BerriAI/litellm/pull/32980)
- feat(ui): rebuild the Teams table on the shared DataTable by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33128](https://github.com/BerriAI/litellm/pull/33128)
- fix(mcp): relay upstream OAuth token and DCR rejections instead of a generic 500 by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;33113](https://github.com/BerriAI/litellm/pull/33113)
- fix(keys): persist key\_type so the UI shows correct key scope instead of "All Proxy Models" by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33115](https://github.com/BerriAI/litellm/pull/33115)
- feat(router): opt-in session affinity for complexity router by [@&#8203;krrish-berri-2](https://github.com/krrish-berri-2) in [#&#8203;33126](https://github.com/BerriAI/litellm/pull/33126)
- feat(prometheus): expose video duration and image count consumption metrics by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33138](https://github.com/BerriAI/litellm/pull/33138)
- test(e2e): otel trace completeness on /chat/completions by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33132](https://github.com/BerriAI/litellm/pull/33132)
- fix(sso): paginate through all pages when fetching service principal group assignments by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33149](https://github.com/BerriAI/litellm/pull/33149)
- test(e2e): otel trace completeness on /v1/messages by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33133](https://github.com/BerriAI/litellm/pull/33133)
- feat(ui): add adaptive routing settings to Auto-Router v2 by [@&#8203;krrish-berri-2](https://github.com/krrish-berri-2) in [#&#8203;33146](https://github.com/BerriAI/litellm/pull/33146)
- refactor(mcp): extract the dcr\_bridge token flow into bridge\_token\_flow\.py by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;33141](https://github.com/BerriAI/litellm/pull/33141)
- chore: bump litellm 1.93.0 -> 1.94.0, litellm-enterprise 0.1.49 -> 0.1.50, litellm-proxy-extras 0.4.76 -> 0.4.77 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33229](https://github.com/BerriAI/litellm/pull/33229)
- fix(proxy): route master key to team-scoped models by [@&#8203;kunal2002](https://github.com/kunal2002) in [#&#8203;32926](https://github.com/BerriAI/litellm/pull/32926)
- chore(deps): pin httplib2 and setuptools transitive floors by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33233](https://github.com/BerriAI/litellm/pull/33233)
- feat(ui): left-anchor the Create Key and Create Team CTAs by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33248](https://github.com/BerriAI/litellm/pull/33248)
- fix(anthropic/passthrough): drop incompatible temperature when downgrading adaptive thinking for pre-4.6 models by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33244](https://github.com/BerriAI/litellm/pull/33244)
- fix(guardrails): run apply\_guardrail-style model-level pre\_call guardrails at deployment hook by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33136](https://github.com/BerriAI/litellm/pull/33136)
- fix(proxy)!: enforce user budget on team keys (read-time + reservation) with UI opt-out by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;32005](https://github.com/BerriAI/litellm/pull/32005)
- fix(e2e): bound spend-log snapshots to a /spend/logs/v2 window by [@&#8203;mubashir1osmani](https://github.com/mubashir1osmani) in [#&#8203;33265](https://github.com/BerriAI/litellm/pull/33265)
- test(e2e): cover key rpm/tpm rate limiting, window reset, and pacing headers by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;32914](https://github.com/BerriAI/litellm/pull/32914)
- fix(anthropic): use native output capability by [@&#8203;krrish-berri-2](https://github.com/krrish-berri-2) in [#&#8203;33235](https://github.com/BerriAI/litellm/pull/33235)
- fix(ci): retry setup-uv installs to survive transient manifest fetch failures by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;33279](https://github.com/BerriAI/litellm/pull/33279)
- fix(proxy): never log raw virtual keys in key insertion debug output by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33268](https://github.com/BerriAI/litellm/pull/33268)
- fix(bedrock\_mantle): route xai.grok-4.3 via /openai/v1 frontier path by [@&#8203;marty-sullivan](https://github.com/marty-sullivan) in [#&#8203;33027](https://github.com/BerriAI/litellm/pull/33027)
- feat(pricing): add gemini-omni-flash-preview with video output token pricing by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;33274](https://github.com/BerriAI/litellm/pull/33274)
- fix(auth): scope the JWT enterprise gate to actual JWTs by [@&#8203;mubashir1osmani](https://github.com/mubashir1osmani) in [#&#8203;33296](https://github.com/BerriAI/litellm/pull/33296)
- fix(s3): sanitize slashes in response-id-derived object key file name by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33271](https://github.com/BerriAI/litellm/pull/33271)
- refactor(ui): migrate guardrails table onto shared DataTable by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33303](https://github.com/BerriAI/litellm/pull/33303)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33308](https://github.com/BerriAI/litellm/pull/33308)
- feat(guardrails): streaming text transformation in generic\_guardrail\_api by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33110](https://github.com/BerriAI/litellm/pull/33110)
- test(e2e): cover model-aware mid-conversation system handling on Bedrock Invoke /v1/messages by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;32963](https://github.com/BerriAI/litellm/pull/32963)
- test(claude\_code): move the Claude Code compatibility matrix under tests/e2e by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;32548](https://github.com/BerriAI/litellm/pull/32548)
- chore(ci): sync litellm\_internal\_staging into daily OSS branch by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33337](https://github.com/BerriAI/litellm/pull/33337)
- feat(bedrock guardrails): add resource-less InvokeGuardrailChecks (detect-only) mode by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33299](https://github.com/BerriAI/litellm/pull/33299)
- Revert "chore(ci): sync litellm\_internal\_staging into daily OSS branch" by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33339](https://github.com/BerriAI/litellm/pull/33339)
- fix(websearch): intercept web search on the Responses API by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33129](https://github.com/BerriAI/litellm/pull/33129)
- fix(anthropic-adapter): drop empty content\_block\_delta events by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;33315](https://github.com/BerriAI/litellm/pull/33315)
- fix(mcp): persist discovered OAuth endpoints and keep last known good on failed re-discovery by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;33286](https://github.com/BerriAI/litellm/pull/33286)
- test(e2e): otel trace completeness on streaming chat, messages, and responses (LIT-3787) by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33234](https://github.com/BerriAI/litellm/pull/33234)
- feat(router): resolve auto-router routing plugins from proxy YAML config by [@&#8203;krrish-berri-2](https://github.com/krrish-berri-2) in [#&#8203;33251](https://github.com/BerriAI/litellm/pull/33251)
- test(e2e): failed request error span carries the full untruncated message and status (LIT-4179) by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33304](https://github.com/BerriAI/litellm/pull/33304)
- refactor(ui): migrate tags table onto shared DataTable by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33314](https://github.com/BerriAI/litellm/pull/33314)
- fix(cli): surface actionable CLI SSO errors when CLI and proxy versions skew by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33309](https://github.com/BerriAI/litellm/pull/33309)
- feat(bedrock\_mantle): add GPT-5.6 sol/terra/luna to model cost map by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;33412](https://github.com/BerriAI/litellm/pull/33412)
- chore(codeowners): exempt generated schema.d.ts from UI ownership by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33411](https://github.com/BerriAI/litellm/pull/33411)
- feat(proxy): push-based OTLP billable-request metering for enterprise deployments by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;31592](https://github.com/BerriAI/litellm/pull/31592)
- fix(mcp): cap per-user OAuth token cache TTL at the token's own lifetime by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;33346](https://github.com/BerriAI/litellm/pull/33346)
- feat(ui): move Caching out of Experimental into Developer Tools by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33432](https://github.com/BerriAI/litellm/pull/33432)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33425](https://github.com/BerriAI/litellm/pull/33425)
- chore(ci): merge daily internal staging branch by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33335](https://github.com/BerriAI/litellm/pull/33335)
- feat(guardrails): add Compresr guardrail for query-aware context compression by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33295](https://github.com/BerriAI/litellm/pull/33295)
- fix(logging): preserve callback order in get\_combined\_callback\_list by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33005](https://github.com/BerriAI/litellm/pull/33005)
- fix(logging): redact assistant tool call arguments in spend logs by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33111](https://github.com/BerriAI/litellm/pull/33111)
- fix(anthropic): honor messages request timeout by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33418](https://github.com/BerriAI/litellm/pull/33418)
- fix(llm\_guard): apply sanitized prompt returned by moderation API to request by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33331](https://github.com/BerriAI/litellm/pull/33331)
- fix(logging): stop pinning large request payloads past request end by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;33455](https://github.com/BerriAI/litellm/pull/33455)
- feat(guardrails): forward optional metadata on POST /guardrails/apply\_guardrail by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33067](https://github.com/BerriAI/litellm/pull/33067)
- build: raise requires-python cap to <3.15 so Python 3.14 installs current releases by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33438](https://github.com/BerriAI/litellm/pull/33438)
- feat(ui): add reusable BetaBadge and use it for Projects sidebar item by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33449](https://github.com/BerriAI/litellm/pull/33449)
- fix(mcp): discover missing OAuth scopes and token\_url when authorization\_url is set manually by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;33317](https://github.com/BerriAI/litellm/pull/33317)
- feat(ui): show exact license expiration date in usage cards by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33478](https://github.com/BerriAI/litellm/pull/33478)
- test(claude\_code): rename misleading REPO\_ROOT to SUITE\_ROOT in test\_v0\_layout by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;33472](https://github.com/BerriAI/litellm/pull/33472)
- build(deps): update ddtrace to the 4.x line by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33484](https://github.com/BerriAI/litellm/pull/33484)
- fix(complexity\_router): return empty dict from \_classifier\_call\_metadata when metadata is absent by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33452](https://github.com/BerriAI/litellm/pull/33452)
- fix(ui/chat): resolve chat routes at render time so navigation works under server\_root\_path by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33446](https://github.com/BerriAI/litellm/pull/33446)
- fix(key management): enforce minimum custom key length and mask short keys in key\_name by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33462](https://github.com/BerriAI/litellm/pull/33462)
- chore(ui): remove unmounted UsageIndicator and the Hide Usage Indicator flag by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33482](https://github.com/BerriAI/litellm/pull/33482)
- test(e2e/claude\_code): add passthrough matrix row for the big-3 clouds and Anthropic API by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;33473](https://github.com/BerriAI/litellm/pull/33473)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33491](https://github.com/BerriAI/litellm/pull/33491)
- fix(ui): stop sending the complexity-router pseudo-model to /health/test\_connection by [@&#8203;krrish-berri-2](https://github.com/krrish-berri-2) in [#&#8203;33498](https://github.com/BerriAI/litellm/pull/33498)
- feat(cli): add lite up/down to ambiently route Claude Code through the proxy by [@&#8203;krrish-berri-2](https://github.com/krrish-berri-2) in [#&#8203;33231](https://github.com/BerriAI/litellm/pull/33231)
- feat(complexity\_router): enable session\_affinity by default by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33500](https://github.com/BerriAI/litellm/pull/33500)
- fix(anthropic): stop 500 on combined thinking+signature streaming chunk by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33505](https://github.com/BerriAI/litellm/pull/33505)
- feat(autoroute): prompt for semantic keywords per tier in configure wizard by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33508](https://github.com/BerriAI/litellm/pull/33508)
- fix(cli/anthropic): unblock lite autoroute proxy deps, adaptive thinking, and thinking+signature streaming by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33507](https://github.com/BerriAI/litellm/pull/33507)
- test(ocr): use mistral-document-ai-2512 in azure\_ai OCR tests by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;33489](https://github.com/BerriAI/litellm/pull/33489)
- fix(guardrails): show YAML-defined guardrails in the Guardrail Monitor by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;32853](https://github.com/BerriAI/litellm/pull/32853)
- refactor(ui): migrate policies, deleted keys, deleted teams, budgets, and search tools tables onto shared DataTable by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33357](https://github.com/BerriAI/litellm/pull/33357)
- refactor(ui): migrate vector stores, prompts, and skills tables onto shared DataTable by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33343](https://github.com/BerriAI/litellm/pull/33343)
- test(e2e): datadog log delivery for successful chat, messages, and responses (LIT-4447) by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33415](https://github.com/BerriAI/litellm/pull/33415)
- fix(cli): make CLI output ASCII-only so it doesn't crash legacy Windows consoles by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33465](https://github.com/BerriAI/litellm/pull/33465)
- fix: remove dead user-cache lookup with None key in spend-update path by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;33555](https://github.com/BerriAI/litellm/pull/33555)
- feat(helm): add per-component PodDisruptionBudget and topologySpreadConstraints to componentized chart by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;33430](https://github.com/BerriAI/litellm/pull/33430)
- fix(e2e/claude\_code): unblock stage collection, align proxy env names, register compat models by [@&#8203;mubashir1osmani](https://github.com/mubashir1osmani) in [#&#8203;33433](https://github.com/BerriAI/litellm/pull/33433)
- fix(mcp): index authed request-time tools missing from the semantic filter startup index by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;33318](https://github.com/BerriAI/litellm/pull/33318)
- fix(streaming): use provider-reported usage cost for OpenRouter streams by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;32255](https://github.com/BerriAI/litellm/pull/32255)
- feat(mcp): issuer-anchored OAuth discovery (RFC 8414 §3.3) to close the authorization-server mix-up by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;33450](https://github.com/BerriAI/litellm/pull/33450)
- feat(logging): add user and team level spend and budget to StandardLoggingPayload metadata by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33459](https://github.com/BerriAI/litellm/pull/33459)
- fix(router): cast model\_info cost values to float in \_set\_model\_group\_info by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;33556](https://github.com/BerriAI/litellm/pull/33556)
- chore(e2e): establish litellm\_e2e\_staging integration line by [@&#8203;mubashir1osmani](https://github.com/mubashir1osmani) in [#&#8203;33502](https://github.com/BerriAI/litellm/pull/33502)
- fix(ui): navigate to /ui/login/ with trailing slash via hard navigation by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33561](https://github.com/BerriAI/litellm/pull/33561)
- feat(logging): add structured budget fields to budget rejection failure logs by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33460](https://github.com/BerriAI/litellm/pull/33460)
- fix(streaming): surface upstream connection resets instead of empty 200 streams by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;33222](https://github.com/BerriAI/litellm/pull/33222)
- fix(proxy\_cli): reap orphaned prisma query-engine processes when a worker dies by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;33424](https://github.com/BerriAI/litellm/pull/33424)
- test(reasoning\_effort\_grid): enable azure fable-5 and opus-4-8 grid cells by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;33485](https://github.com/BerriAI/litellm/pull/33485)
- build(deps): bump uvicorn lock to 0.51.0 so worker health-check and jitter flags take effect by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;33574](https://github.com/BerriAI/litellm/pull/33574)
- fix(proxy): coerce default\_internal\_user\_params.max\_budget to float on config load by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;32434](https://github.com/BerriAI/litellm/pull/32434)
- fix(router): honor per-request routing\_strategy from key/team router\_settings by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;33429](https://github.com/BerriAI/litellm/pull/33429)
- fix(redis): honor ssl value instead of key presence when building async connection pool by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;32590](https://github.com/BerriAI/litellm/pull/32590)
- fix(langfuse\_otel): build per-request OTLP exporter from key and team dynamic Langfuse credentials by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;32437](https://github.com/BerriAI/litellm/pull/32437)
- ci: run zizmor and proxy-db unit tests on PRs targeting litellm\_ branches by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;33568](https://github.com/BerriAI/litellm/pull/33568)
- fix(router): apply team/key enable\_tag\_filtering to tag routing by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;33436](https://github.com/BerriAI/litellm/pull/33436)
- feat(proxy): add disable\_auto\_add\_proxy\_admin\_to\_teams flag by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33563](https://github.com/BerriAI/litellm/pull/33563)
- fix(proxy): stop stale auth cache re-publish to Redis so key updates propagate across replicas by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;33565](https://github.com/BerriAI/litellm/pull/33565)
- feat(e2e): emit structured E2E\_RESULT lines for package status history by [@&#8203;mubashir1osmani](https://github.com/mubashir1osmani) in [#&#8203;33578](https://github.com/BerriAI/litellm/pull/33578)
- chore: bump litellm-enterprise 0.1.50 -> 0.1.51, litellm-proxy-extras 0.4.77 -> 0.4.78 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33571](https://github.com/BerriAI/litellm/pull/33571)
- fix(docker): restore litellm-proxy-extras source dir in runtime images by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33592](https://github.com/BerriAI/litellm/pull/33592)
- feat(ui): require embedding model for semantic auto router by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33313](https://github.com/BerriAI/litellm/pull/33313)
- feat(scim): ingest and round-trip SCIM entitlements and roles user attributes by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;33587](https://github.com/BerriAI/litellm/pull/33587)
- refactor(ui): migrate 5 simple tables onto shared DataTable by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33548](https://github.com/BerriAI/litellm/pull/33548)
- fix(model\_armor): restore reference attachments via skip\_unscannable\_attachments and remove the attachment count cap by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33554](https://github.com/BerriAI/litellm/pull/33554)
- fix(mcp): keep the MCP reference intact when the semantic filter narrows tools by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;33584](https://github.com/BerriAI/litellm/pull/33584)
- fix(sso): stop enforcing UI session budget on CLI login tokens by [@&#8203;mubashir1osmani](https://github.com/mubashir1osmani) in [#&#8203;33312](https://github.com/BerriAI/litellm/pull/33312)
- test: e2e staging leftovers by [@&#8203;mubashir1osmani](https://github.com/mubashir1osmani) in [#&#8203;33613](https://github.com/BerriAI/litellm/pull/33613)
- test(e2e/claude\_code): add GPT-5.6 Sol/Terra/Luna columns for OpenAI, Azure OpenAI, and Bedrock Mantle by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;33474](https://github.com/BerriAI/litellm/pull/33474)
- test(e2e): otel streaming spans record a real ttft below span duration by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33588](https://github.com/BerriAI/litellm/pull/33588)
- fix(mcp): make the preemptive-401 OAuth challenge decision mode-aware by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;33586](https://github.com/BerriAI/litellm/pull/33586)
- fix(ui): show all teams in policy attachment form for admins by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33628](https://github.com/BerriAI/litellm/pull/33628)
- refactor(ui): migrate AI Hub, public hub, and MCP Toolsets tables onto shared DataTable by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33629](https://github.com/BerriAI/litellm/pull/33629)
- test(e2e): datadog log delivery for streamed routes, read back from the real datadog api by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33566](https://github.com/BerriAI/litellm/pull/33566)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33640](https://github.com/BerriAI/litellm/pull/33640)
- fix(vertex\_ai): surface Gemini grounding toolUsePromptTokenCount in Usage by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33533](https://github.com/BerriAI/litellm/pull/33533)
- test(e2e): harness fixes for stage job green (skips + router/UI/budget) by [@&#8203;mubashir1osmani](https://github.com/mubashir1osmani) in [#&#8203;33634](https://github.com/BerriAI/litellm/pull/33634)
- fix(router): resolve prompt cache minimum per model instead of a flat 1024 by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;33637](https://github.com/BerriAI/litellm/pull/33637)
- fix(logging): classify async anthropic\_messages and generate\_content as async by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33589](https://github.com/BerriAI/litellm/pull/33589)
- fix(ui): remove Chat item from dashboard leftnav by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33647](https://github.com/BerriAI/litellm/pull/33647)
- fix(router): tag-aware pre-routing strategy selection for shared model\_name by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33691](https://github.com/BerriAI/litellm/pull/33691)
- fix(proxy): enforce max\_parallel\_requests as a per-slot concurrency gauge by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;32441](https://github.com/BerriAI/litellm/pull/32441)
- fix(proxy): stop treating upstream model body field as a LiteLLM model on auth-enforced pass-through routes by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;33710](https://github.com/BerriAI/litellm/pull/33710)
- fix(mcp): expand toolset grants in shared permission primitives so tools/call honors them by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;33612](https://github.com/BerriAI/litellm/pull/33612)
- feat(complexity-router): user-triggered escalation keywords by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33656](https://github.com/BerriAI/litellm/pull/33656)
- fix(fireworks\_ai): bill prompt-cache hits at cache\_read rate by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33714](https://github.com/BerriAI/litellm/pull/33714)
- fix(pricing): mark realtime-only gpt-realtime models as mode realtime by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33728](https://github.com/BerriAI/litellm/pull/33728)
- fix(rag): track LLM completion usage and spend for /v1/rag/query by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;32438](https://github.com/BerriAI/litellm/pull/32438)
- feat(anthropic): add enable\_anthropic\_prompt\_caching for automatic cache\_control injection by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;33573](https://github.com/BerriAI/litellm/pull/33573)
- fix(anthropic): self-heal on missing thinking-signature errors from Bedrock/Vertex by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33719](https://github.com/BerriAI/litellm/pull/33719)
- fix(proxy): resolve router\_settings.plugins dotted paths and load plugins from installed packages by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33644](https://github.com/BerriAI/litellm/pull/33644)
- test(e2e): budget refusals are 429 for bare keys and team caps block every team key by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33632](https://github.com/BerriAI/litellm/pull/33632)
- feat(router): add router plugin reference catalog by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33746](https://github.com/BerriAI/litellm/pull/33746)
- test(e2e): assert an org budget block is a 429 naming the organization by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33638](https://github.com/BerriAI/litellm/pull/33638)
- fix(proxy): bill partial streamed spend when the client disconnects mid-stream by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;33736](https://github.com/BerriAI/litellm/pull/33736)
- test(e2e): delete unreferenced Grafana panel docs by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;33743](https://github.com/BerriAI/litellm/pull/33743)
- docs(tests/e2e): align skip-vs-fail docs with the hard-fail contract and scope the no-unit-tests rule by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;33755](https://github.com/BerriAI/litellm/pull/33755)
- refactor(e2e): replace bespoke result reporter with standard JUnit report by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;33758](https://github.com/BerriAI/litellm/pull/33758)
- test(e2e): user budget across keys and team member budget isolation by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;33745](https://github.com/BerriAI/litellm/pull/33745)
- refactor(e2e): remove bob\_the\_builder; drive remediation from a Grafana alert (provisioned outside the repo) by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;33749](https://github.com/BerriAI/litellm/pull/33749)
- feat(mcp): per-server outcomes for aggregate tools/list and truthful single-server REST statuses by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;33153](https://github.com/BerriAI/litellm/pull/33153)
- test(e2e): mcp suite for key-without-access denial by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;33752](https://github.com/BerriAI/litellm/pull/33752)
- chore(ci): merge oss branch by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33784](https://github.com/BerriAI/litellm/pull/33784)
- chore(ci): merge oss branch - July 17th by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33793](https://github.com/BerriAI/litellm/pull/33793)
- fix(ui): migrate tag deletion to shared DeleteResourceModal by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33795](https://github.com/BerriAI/litellm/pull/33795)
- build(rust): raise pyo3 to 0.29 so the native bridge compiles on Python 3.14 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33798](https://github.com/BerriAI/litellm/pull/33798)
- chore(guardrails): remove docstring from singulr module for consistency by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33800](https://github.com/BerriAI/litellm/pull/33800)
- fix(ui): stop credential edit from persisting the masked api key by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33797](https://github.com/BerriAI/litellm/pull/33797)
- build(deps): allow redisvl, pypdf, and openapi-core on Python 3.14 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33801](https://github.com/BerriAI/litellm/pull/33801)
- test(proxy): make streaming-cancel mocks awaitable for the disconnect slot release by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33802](https://github.com/BerriAI/litellm/pull/33802)
- test(e2e): a member's team budget cuts off only that member's key by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33718](https://github.com/BerriAI/litellm/pull/33718)
- test(e2e): a user's max\_budget follows the person across personal and team keys by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33762](https://github.com/BerriAI/litellm/pull/33762)
- test(e2e): skip flaky OpenAI GPT cells; raise multi-window max\_tokens by [@&#8203;mubashir1osmani](https://github.com/mubashir1osmani) in [#&#8203;33799](https://github.com/BerriAI/litellm/pull/33799)
- chore: remove accidentally committed dist tarball and ignore dist/ by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33805](https://github.com/BerriAI/litellm/pull/33805)
- fix(passthrough): stop classifying plain 'predict'/'search' paths as Vertex by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33658](https://github.com/BerriAI/litellm/pull/33658)
- build(deps): bump mcp lock to 1.28.1 to clear image-scan findings by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33803](https://github.com/BerriAI/litellm/pull/33803)
- test(pricing): pin the realtime mode assertion to the bundled cost map by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33806](https://github.com/BerriAI/litellm/pull/33806)
- fix(proxy): derive session id from Anthropic metadata.user\_id for session affinity by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33723](https://github.com/BerriAI/litellm/pull/33723)
- test(e2e): budget reset diagonal for team, org, user, and [#&#8203;32005](https://github.com/BerriAI/litellm/issues/32005) team-member keys by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33771](https://github.com/BerriAI/litellm/pull/33771)
- fix(proxy): source /v1/models token limits from the cost map instead of Router.get\_model\_group\_info by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33721](https://github.com/BerriAI/litellm/pull/33721)
- fix(fireworks\_ai): correct glm-5p2 prompt-cache read price to $0.14/1M by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;33796](https://github.com/BerriAI/litellm/pull/33796)
- feat(proxy): add x-litellm-model-name response header with deployment model string by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33698](https://github.com/BerriAI/litellm/pull/33698)
- feat: add Straiker guardrail integration by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33781](https://github.com/BerriAI/litellm/pull/33781)
- fix(vertex\_ai): exclude Gemini Google Search grounding tokens from input token billing by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33742](https://github.com/BerriAI/litellm/pull/33742)
- feat(fireworks\_ai): map litellm session id to x-session-affinity header for prompt caching by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33717](https://github.com/BerriAI/litellm/pull/33717)
- feat(ui): configure Anthropic automatic prompt caching from the Admin UI by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;33581](https://github.com/BerriAI/litellm/pull/33581)
- fix(router): enforce context-window pre-call checks for Responses API input by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33706](https://github.com/BerriAI/litellm/pull/33706)
- fix(otel): restore proxy-level error.\* attributes on v2 failure spans (LIT-4179) by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33664](https://github.com/BerriAI/litellm/pull/33664)
- fix(mcp): persist config.yaml DCR clients in a server-scoped store so refresh survives token expiry by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;33768](https://github.com/BerriAI/litellm/pull/33768)
- refactor(ui): consolidate Add/Edit credential modals into one CredentialModal by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;32572](https://github.com/BerriAI/litellm/pull/32572)
- feat(mcp): add ID-JAG (identity assertion authorization grant) support for MCP egress by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;31516](https://github.com/BerriAI/litellm/pull/31516)
- refactor(ui): migrate policy attachments table onto shared DataTable by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33827](https://github.com/BerriAI/litellm/pull/33827)
- docs(litellm-rust): add provider coding standards by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33833](https://github.com/BerriAI/litellm/pull/33833)
- test(e2e): rename Gateway to ProxyClient and expose it as a session-scoped pytest fixture by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;33750](https://github.com/BerriAI/litellm/pull/33750)
- feat(messages): route Azure Anthropic /messages through Rust behind rust:true by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33616](https://github.com/BerriAI/litellm/pull/33616)
- test(e2e): add Locust throughput load test that runs last by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;33748](https://github.com/BerriAI/litellm/pull/33748)
- fix(proxy): resolve team wildcard credentials for vector store files by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;33649](https://github.com/BerriAI/litellm/pull/33649)
- refactor(e2e): fold claude\_code HTTP probes onto shared ProxyClient methods by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;33760](https://github.com/BerriAI/litellm/pull/33760)
- test(e2e): harden stage flakes for batches, UI, and MCP by [@&#8203;mubashir1osmani](https://github.com/mubashir1osmani) in [#&#8203;33831](https://github.com/BerriAI/litellm/pull/33831)
- fix(e2e): migrate load suite from e2e\_gateway to ProxyClient by [@&#8203;mubashir1osmani](https://github.com/mubashir1osmani) in [#&#8203;33839](https://github.com/BerriAI/litellm/pull/33839)
- chore(e2e): remove tests/e2e/docker-compose.yml by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33837](https://github.com/BerriAI/litellm/pull/33837)
- test(e2e): cover /v1/responses openai basic nonstream and stream by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33830](https://github.com/BerriAI/litellm/pull/33830)
- test(e2e): cover /v1/responses openai cost\_logged and tool\_use by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33835](https://github.com/BerriAI/litellm/pull/33835)
- test(e2e): cover /v1/responses OpenAI vision and Anthropic basic by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33838](https://github.com/BerriAI/litellm/pull/33838)
- test(e2e): spendlog cost for streaming /v1/messages via responses bridge by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;33753](https://github.com/BerriAI/litellm/pull/33753)
- fix(docker): bake prisma CLI and engines at a fixed path so fresh-DB migrations work for any uid offline by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33853](https://github.com/BerriAI/litellm/pull/33853)
- feat(chat-ui): add personal Logs view scoped to the current user by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33829](https://github.com/BerriAI/litellm/pull/33829)
- chore: bump litellm-proxy-extras 0.4.78 -> 0.4.79 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33855](https://github.com/BerriAI/litellm/pull/33855)
- docs(litellm-rust): require the official Rust Style Guide in agent rules by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33867](https://github.com/BerriAI/litellm/pull/33867)
- fix(router): treat malformed configured token limits as absent on /v1/models by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33864](https://github.com/BerriAI/litellm/pull/33864)
- chore: rebuild admin UI bundle for the rc release by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33857](https://github.com/BerriAI/litellm/pull/33857)
- docs(rust): add provider abstraction standards by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33865](https://github.com/BerriAI/litellm/pull/33865)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33868](https://github.com/BerriAI/litellm/pull/33868)
- chore(release): backport [#&#8203;33929](https://github.com/BerriAI/litellm/issues/33929) to rc/1.94.0 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;34033](https://github.com/BerriAI/litellm/pull/34033)
- chore(release): backport [#&#8203;33810](https://github.com/BerriAI/litellm/issues/33810), [#&#8203;33733](https://github.com/BerriAI/litellm/issues/33733) to rc/1.94.0 and bump litellm-proxy-extras to 0.4.79.post1 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;34215](https://github.com/BerriAI/litellm/pull/34215)
- chore(ui): rebuild Next.js bundle on rc/1.94.0 so the Cost Optimization page ships by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;34216](https://github.com/BerriAI/litellm/pull/34216)
- chore(release): backport auth, CLI SSO and guardrail fixes to rc/1.94.0 and refresh flagged dependencies by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;34640](https://github.com/BerriAI/litellm/pull/34640)
- chore(release): backport [#&#8203;33899](https://github.com/BerriAI/litellm/issues/33899), [#&#8203;33978](https://github.com/BerriAI/litellm/issues/33978), [#&#8203;34582](https://github.com/BerriAI/litellm/issues/34582), [#&#8203;34675](https://github.com/BerriAI/litellm/issues/34675) to rc/1.94.0 and bump litellm-proxy-extras to 0.4.79.post2 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;34855](https://github.com/BerriAI/litellm/pull/34855)
- fix(ui): backport cache leakage card layout fix to rc/1.94.0 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;34964](https://github.com/BerriAI/litellm/pull/34964)
- fix(ui): add missing cost-optimization page description on rc/1.94.0 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;34967](https://github.com/BerriAI/litellm/pull/34967)
- feat(ui): mark Cost Optimization as beta in the left nav ([#&#8203;34984](https://github.com/BerriAI/litellm/issues/34984)) by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;34987](https://github.com/BerriAI/litellm/pull/34987)
- chore: rebuild Admin UI bundle for v1.94.0 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;34982](https://github.com/BerriAI/litellm/pull/34982)
- fix(cost-optimization): backport the savings chart axis fix and methodology popovers to rc/1.94.0 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;34994](https://github.com/BerriAI/litellm/pull/34994)
- chore: rebuild Admin UI bundle for rc/1.94.0 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;34995](https://github.com/BerriAI/litellm/pull/34995)

**Full Changelog**: <https://github.com/BerriAI/litellm/compare/v1.93.0...v1.94.0>

### [`v1.94.0`](https://github.com/BerriAI/litellm/releases/tag/v1.94.0)

##### Verify Docker Image Signature

All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](https://github.com/BerriAI/litellm/commit/0112e53046018d726492c814b3644b7d376029d0).

**Verify using the pinned commit hash (recommended):**

A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \
  ghcr.io/berriai/litellm:v1.94.0
```

**Verify using the release tag (convenience):**

Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/v1.94.0/cosign.pub \
  ghcr.io/berriai/litellm:v1.94.0
```

Expected output:

```
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
```

***

##### What's Changed

- feat(ui): working Test Connection for the complexity auto router by [@&#8203;akapur99](https://github.com/akapur99) in [#&#8203;32950](https://github.com/BerriAI/litellm/pull/32950)
- fix(xecguard): use StandardLoggingGuardrailInformation in logging hook by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;32911](https://github.com/BerriAI/litellm/pull/32911)
- feat(ui): adopt openapi-react-query ($api) and convert useCustomers by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;32949](https://github.com/BerriAI/litellm/pull/32949)
- refactor(ui): colocate the mcp-servers view, keeping the shared mcp\_tools surface by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;32968](https://github.com/BerriAI/litellm/pull/32968)
- refactor(ui): convert endpoint usage charts to shadcn/recharts by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;32723](https://github.com/BerriAI/litellm/pull/32723)
- fix(proxy-auth): stop unrecognized model namespaces slipping through provider wildcard keys by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;32979](https://github.com/BerriAI/litellm/pull/32979)
- feat(router): random-pick multi-model complexity tiers by [@&#8203;krrish-berri-2](https://github.com/krrish-berri-2) in [#&#8203;32967](https://github.com/BerriAI/litellm/pull/32967)
- fix(xecguard): sanitize scan result before recording it for logging by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;32935](https://github.com/BerriAI/litellm/pull/32935)
- fix(auto\_router): filter embedding models in complexity tab dropdowns, require all tiers, inline validation by [@&#8203;akapur99](https://github.com/akapur99) in [#&#8203;32978](https://github.com/BerriAI/litellm/pull/32978)
- fix(anthropic): translate raw adaptive thinking for pre-4.6 models on chat completions and Bedrock Converse by [@&#8203;akapur99](https://github.com/akapur99) in [#&#8203;32944](https://github.com/BerriAI/litellm/pull/32944)
- feat(router): add Router(plugins=\[...]) routing-plugin pipeline by [@&#8203;krrish-berri-2](https://github.com/krrish-berri-2) in [#&#8203;32972](https://github.com/BerriAI/litellm/pull/32972)
- feat(router): soft-floor adaptive mode for complexity router by [@&#8203;krrish-berri-2](https://github.com/krrish-berri-2) in [#&#8203;32947](https://github.com/BerriAI/litellm/pull/32947)
- docs(github): add QA runbook section to the PR template by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;32965](https://github.com/BerriAI/litellm/pull/32965)
- fix(model\_cost): add supports\_reasoning: false to Gemini image generation models by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;32836](https://github.com/BerriAI/litellm/pull/32836)
- build(dev-env): add make bootstrap and unprovisioned-checkout preflight to pre-commit by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;32981](https://github.com/BerriAI/litellm/pull/32981)
- ci(ui): report only error-level knip findings in CI by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;32971](https://github.com/BerriAI/litellm/pull/32971)
- feat(batches): track cost for unmanaged Bedrock batches, generalize the flag by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;32315](https://github.com/BerriAI/litellm/pull/32315)
- fix(guardrails): walk custom\_tool\_call\_output items in \_content\_utils by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;32969](https://github.com/BerriAI/litellm/pull/32969)
- fix: show and allow editing team model aliases after team creation by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33047](https://github.com/BerriAI/litellm/pull/33047)
- chore(deps): bump pillow to 12.3.0 to resolve osv-scan CVEs by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33093](https://github.com/BerriAI/litellm/pull/33093)
- feat(mcp): mint gateway-bound envelope at the token endpoint for dcr\_bridge oauth\_delegate by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;32828](https://github.com/BerriAI/litellm/pull/32828)
- fix(mcp): surface rejected delegate-auth upstream tokens as connect-time 401 by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;32741](https://github.com/BerriAI/litellm/pull/32741)
- fix(proxy): track unauthenticated pass-through requests in spend logs by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;32410](https://github.com/BerriAI/litellm/pull/32410)
- feat(lasso): send source.type for Used By attribution by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33090](https://github.com/BerriAI/litellm/pull/33090)
- fix(responses): continue MCP gateway tool turns from the final response and surface failures by [@&#8203;thibault-linktree](https://github.com/thibault-linktree) in [#&#8203;33025](https://github.com/BerriAI/litellm/pull/33025)
- fix(responses): continue MCP gateway tool turns from the final response and surface failures by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;33099](https://github.com/BerriAI/litellm/pull/33099)
- fix(completion): forward aws credential kwargs into litellm\_params so the responses bridge keeps WIF auth by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;32956](https://github.com/BerriAI/litellm/pull/32956)
- fix(ui): respect litellm\_key\_header\_name in BYOK credential save and workflow runs fetches by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33103](https://github.com/BerriAI/litellm/pull/33103)
- refactor(ui): standardize debounce waits behind shared DEBOUNCE\_WAIT\_MS constant by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33040](https://github.com/BerriAI/litellm/pull/33040)
- feat(ui): rebuild the Virtual Keys table on the shared DataTable by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;32991](https://github.com/BerriAI/litellm/pull/32991)
- fix: redact async complete streaming response for custom callbacks by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33106](https://github.com/BerriAI/litellm/pull/33106)
- build(ui): bump [@&#8203;tanstack/react-pacer](https://github.com/tanstack/react-pacer) from 0.2.0 to 0.22.1 by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33041](https://github.com/BerriAI/litellm/pull/33041)
- fix(ui): address Virtual Keys redesign review nits by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33112](https://github.com/BerriAI/litellm/pull/33112)
- fix(openai/responses): clamp max\_output\_tokens below API minimum by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33098](https://github.com/BerriAI/litellm/pull/33098)
- fix(prometheus): read v3 rate limiter remaining values for per-key model gauges by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;33119](https://github.com/BerriAI/litellm/pull/33119)
- fix(ui): drop w-full from page-content wrappers to remove 32px horizontal overflow by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33118](https://github.com/BerriAI/litellm/pull/33118)
- refactor(ui): migrate straightforward value debounces to react-pacer by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33042](https://github.com/BerriAI/litellm/pull/33042)
- feat(mcp): interactive SSO sign-in for dcr\_bridge oauth\_delegate DCR clients by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;32946](https://github.com/BerriAI/litellm/pull/32946)
- test(proxy): add regression tests for management\_endpoints edge cases by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;32976](https://github.com/BerriAI/litellm/pull/32976)
- fix(auto-router): correct Responses API tool\_choice shape and propagate alias litellm\_params by [@&#8203;krrish-berri-2](https://github.com/krrish-berri-2) in [#&#8203;32974](https://github.com/BerriAI/litellm/pull/32974)
- fix(ui): render the sidebar scrollbar with shadcn ScrollArea by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33124](https://github.com/BerriAI/litellm/pull/33124)
- refactor(ui): migrate callback debounce sites to react-pacer with regression tests by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;33043](https://github.com/BerriAI/litellm/pull/33043)
- chore: add CODEOWNERS for ui and proxy UI build artifacts by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33131](https://github.com/BerriAI/litellm/pull/33131)
- feat(mcp): client-held refresh envelope for the dcr\_bridge oauth\_delegate flow by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;32980](https://github.com/BerriAI/litellm/pull/32980)
- feat(ui): rebuild the Teams table on the shared DataTable by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33128](https://github.com/BerriAI/litellm/pull/33128)
- fix(mcp): relay upstream OAuth token and DCR rejections instead of a generic 500 by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;33113](https://github.com/BerriAI/litellm/pull/33113)
- fix(keys): persist key\_type so the UI shows correct key scope instead of "All Proxy…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: OSV-Scan Fails on Every PR: Pillow Needs Upgrade 12.2.0 -> 12.3.0 But Lockfile Update Requires Further Write Access

4 participants