Commit graph

39410 commits

Author SHA1 Message Date
Cursor Agent
6697fdb03d
Merge PR #28008 (gym-cmd/litellm:feat/v1.84.0-mcp-gateway-jwt-auth) into litellm_feat/v1.84.0-mcp-gateway-jwt-auth
# Conflicts:
#	litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py
#	litellm/proxy/_experimental/mcp_server/server.py
#	litellm/proxy/management_endpoints/mcp_management_endpoints.py
#	litellm/proxy/proxy_server.py
#	litellm/types/mcp_server/mcp_server_manager.py
#	tests/test_litellm/interactions/test_openapi_compliance.py
#	tests/test_litellm/proxy/_experimental/mcp_server/auth/test_user_api_key_auth_mcp.py
#	tests/test_litellm/proxy/management_endpoints/test_mcp_management_endpoints.py

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-05-21 03:41:08 +00:00
gym-cmd
0447ba6a9b fix(mcp): use resource metadata for passthrough reauth 2026-05-21 00:30:45 +01:00
gym-cmd
2636bbcdc7 test(proxy): cover issuer-scoped JWT auth 2026-05-20 23:52:51 +01:00
Cursor Agent
7ba78155d7
fix(mcp): restrict passthrough probe challenge to OAuth passthrough servers
The probe filter previously matched any server with Authorization in
extra_headers, including gateway-managed OAuth2 servers. Those would
then receive the resource_metadata= WWW-Authenticate challenge meant
for pass-through servers, instead of the authorization_uri= challenge
pointing at the gateway AS metadata. Use srv.is_oauth_passthrough so
only genuine pass-through servers get the resource-metadata challenge.

Co-authored-by: Yassin Kortam <yassin@berri.ai>
2026-05-20 18:32:41 +00:00
Cursor Agent
719adfd043
fix(mcp): handle passthrough OAuth metadata and startup auth errors
- discoverable_endpoints: For pass-through MCP servers, when upstream
  oauth-protected-resource returns a non-200/non-dict response, raise
  HTTP 502 instead of falling through to default gateway metadata.
  Falling through would direct MCP clients at the gateway, which is
  not the authorization server for pass-through configs.

- mcp_server_manager: Wrap _get_tools_from_server in startup tool name
  mapping with try/except. Since _get_tools_from_server now re-raises
  MCPUpstreamAuthError, an upstream 401 from a pass-through server at
  startup (when no user token is present) would otherwise abort the
  loop and leave subsequent servers unmapped.

Co-authored-by: Yassin Kortam <yassin@berri.ai>
2026-05-20 18:19:28 +00:00
Cursor Agent
d1d9ff0121
refactor(mcp): dedupe MCPUpstreamAuthError->HTTPException + thread client_ip into delegate-auth gate
Co-authored-by: Yassin Kortam <yassin@berri.ai>
2026-05-20 18:07:36 +00:00
gym-cmd
96e4de3c48 style(mcp): format discoverable endpoints 2026-05-20 18:56:11 +01:00
Cursor Agent
f0eb54ea9f
fix(mcp): correct passthrough probe 401 + slashed-name cold start parser
- _check_passthrough_upstream_auth now emits
  'Bearer resource_metadata="..."' pointing at the gateway's
  oauth-protected-resource well-known URL, mirroring the
  pre-emptive 401 path. Pass-through servers don't use the gateway
  as an authorization server, so the previous 'authorization_uri='
  challenge sent clients to the wrong metadata endpoint.

- _parse_mcp_server_names_from_path now accepts server names that
  contain a single slash (e.g. custom_solutions/user_123), mirroring
  MCPRequestHandler._extract_target_server_names_from_path. Without
  this, the cold-start bypass missed slashed-name servers and the
  generic admission error propagated instead of the spec-compliant
  401 challenge.

- _is_mcp_passthrough_cold_start drops the unused scope parameter
  from its signature.

Co-authored-by: Yassin Kortam <yassin@berri.ai>
2026-05-20 17:52:55 +00:00
Claude
ee38ba16e3
fix(mcp,tests): sync stubs and cold-start assertions with delegate-check
The merge of base-branch _target_servers_delegate_auth_to_upstream
into process_mcp_request inserts an additional
get_mcp_server_by_name(name) lookup ahead of the cold-start path,
which breaks two test patterns:

1. lookup_by_name(name) side-effect stubs in
   TestMCPDelegateAuthToUpstream are called positionally by the
   delegate check, then again by the cold-start path with
   client_ip=... — raising TypeError: unexpected keyword argument
   'client_ip'. Accept **_kwargs to match the real signature.

2. TestMCPPassthroughColdStartAdmission assertions count the lookup
   exactly once with client_ip=..., but the delegate check now adds
   a positional-only call ahead of it. Switch assert_called_once_with
   to assert_any_call for the cold-start invocation, and assert
   client_ip was *not* passed for the aggregate /mcp test where
   cold-start must not fire.

Both updates align with CLAUDE.md guidance to keep monkeypatch stubs in
sync with the real signature when an optional parameter is added.

Co-authored-by: Claude <claude@anthropic.com>
2026-05-20 17:42:30 +00:00
Cursor Agent
78cb412419
fix(mcp): tighten cold-start bypass against CSV paths + dedupe upstream auth probe
- Return None from _parse_mcp_server_names_from_path for CSV multi-server
  paths (/mcp/a,b). The regex previously truncated at the first comma and
  silently passed a single server name to the cold-start gate.
- Switch _is_mcp_passthrough_cold_start to all-targets semantics, matching
  _target_servers_use_oauth2: one non-passthrough target in a co-targeted
  set must not flip the anonymous-admission bypass open for the others.
- Drop the redundant HTTPStatusError block in _extract_upstream_auth_failure
  - any HTTPStatusError carries a .response, so the preceding generic block
  already handles 401/403 detection.

Co-authored-by: Yassin Kortam <yassin@berri.ai>
2026-05-20 17:32:46 +00:00
Claude
326c6bb84f
fix(mcp): add upstream auth pre-flight in SSE handler
Mirror handle_streamable_http_mcp by calling _check_passthrough_upstream_auth
after the cold-start 401 emitter so expired/invalid upstream tokens surface a
proper 401 + WWW-Authenticate challenge before the SSE session commits 200
headers, instead of letting list_tools silently return [] when the upstream
rejects the token.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-20 17:31:20 +00:00
Cursor Agent
a86f6e7eb6
fix(mcp): surface MCPUpstreamAuthError as 401 in SSE/HTTP transport handlers
Both handle_sse_mcp and handle_streamable_http_mcp only caught
HTTPException to preserve 401 + WWW-Authenticate challenges, but
MCPUpstreamAuthError (raised when a pass-through server's upstream
rejects a bearer token mid-session) inherits from Exception. It was
falling through to the generic handler and surfacing as an opaque 500.

Mirror the REST endpoint behavior: translate MCPUpstreamAuthError into
an HTTPException(status_code=e.status_code) with the upstream
www-authenticate header so standards-compliant MCP clients trigger the
upstream OAuth flow.

Co-authored-by: Yassin Kortam <yassin@berri.ai>
2026-05-20 17:18:22 +00:00
Cursor Agent
5d8f74f1c1
fix(jwt): strip internal _litellm_* claims in global JWKS auth path
Prevents identity spoofing where a token signed by the global JWKS
could inject _litellm_jwt_issuer and other _litellm_* claims that
downstream getters trust. The issuer-scoped path already strips these
via _apply_issuer_claim_mappings; mirror that behavior for the global
fallback path.

Co-authored-by: Yassin Kortam <yassin@berri.ai>
2026-05-20 17:06:37 +00:00
Claude
6f82537c35
Merge branch 'litellm_internal_staging' into litellm_feat/v1.84.0-mcp-gateway-jwt-auth
Resolved conflict in tests/local_testing/conftest.py by keeping the
branch-specific comment that describes the ministral-8b-2512 backfill
case (this branch adds that entry).

Co-authored-by: Claude <claude@anthropic.com>
2026-05-20 17:06:12 +00:00
Sameer Kankute
99a63d5180
feat(gemini): add gemini-3.1-flash-lite model cost map (#28320)
* feat(gemini): add gemini-3.1-flash-lite model cost map entries

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

* Update model_prices_and_context_window.json

* Update source URL for model pricing information

* Sync source URL for gemini-3.1-flash-lite in backup JSON

* fix(model_cost_map): add mistral/ministral-8b-2512 entry

Mistral rotated the 'mistral/mistral-tiny' alias to return
'ministral-8b-2512' as the response model, which is not in the cost map.
This caused test_completion_mistral_api and
test_completion_mistral_api_modified_input to fail in
completion_cost lookup. Add the entry mirroring the existing
openrouter/mistralai/ministral-8b-2512 pricing.

* test(cost_calculator): assert output_cost_per_reasoning_token for gemini-3.1-flash-lite

* fix(tests): backfill local backup entries into runtime model_cost

litellm.model_cost is loaded from LITELLM_MODEL_COST_MAP_URL (pinned to
main) at import time, so any pricing entries added to the in-tree backup
on this branch aren't visible at test runtime until they also land on
main. The Mistral cassette currently returns model=ministral-8b-2512
and the cost-calculator lookup in test_completion_mistral_api /
test_completion_mistral_api_modified_input fails despite the entry
existing in the local backup. Backfill missing backup entries into
litellm.model_cost in the local_testing conftest so these lookups
succeed against the cassette state the branch is being tested with.

* fix(tests): guard conftest backfill against empty local cost map

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-05-20 10:03:14 -07:00
Claude
a6492bf424
fix(auth): address remaining greptile style findings
- get_request_route: require root_path to match whole path segments before
  stripping, so '/apifoo' isn't truncated to 'foo' when root_path='/api'.
- get_mcp_client_ip: collapse the two trusted-proxy validation branches into
  a single is_request_from_trusted_proxy call so the return value drives
  control flow instead of being discarded for the side-effect warning.

Co-authored-by: Claude <claude@anthropic.com>
2026-05-20 16:59:07 +00:00
gym-cmd
eae390b16d fix(mcp): respect client ip for delegated auth 2026-05-20 17:44:22 +01:00
Claude
1e5053adf1
fix(mcp,auth): restore client_ip in oauth2 target check, drop from delegate check
The merge of staging into the PR branch (d42a66adb6) misplaced the
client_ip=client_ip kwarg: it landed inside _target_servers_delegate_auth_to_upstream
(which never accepted client_ip and isn't called with it), while the
sibling _target_servers_use_oauth2 has client_ip in its signature but
stopped passing it through to get_mcp_server_by_name. That left ruff
flagging F821 on the undefined name and lint failing.

Move client_ip back into _target_servers_use_oauth2's lookup (matching
the call site that already forwards IPAddressUtils.get_mcp_client_ip)
and drop it from _target_servers_delegate_auth_to_upstream so its body
matches its signature again.
2026-05-20 16:37:02 +00:00
Claude
488fcc25e1
fix(jwt,mcp): fall back to global JWKS on unknown issuer; prune fetch locks
- handle_jwt._get_configured_issuer now returns None for tokens whose 'iss'
  is not in the configured issuers list, letting auth_jwt fall through to
  the legacy JWT_PUBLIC_KEY_URL path instead of hard-raising. This keeps
  existing tokens from non-configured IdPs working when an operator adds
  the new 'issuers' list to a live deployment.

- discoverable_endpoints._prune_oauth_metadata_cache now also prunes
  entries in _OAUTH_METADATA_FETCH_LOCKS whose cache entry has been
  evicted and whose lock isn't currently held, bounding the locks dict
  to match the cache it guards.

Co-authored-by: Claude <claude@anthropic.com>
2026-05-20 16:35:23 +00:00
gym-cmd
b4df1a9bde Merge remote-tracking branch 'upstream/litellm_internal_staging' into feat/v1.84.0-mcp-gateway-jwt-auth-public
# Conflicts:
#	.github/workflows/test-unit-proxy-db.yml
#	litellm-proxy-extras/pyproject.toml
#	litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py
#	litellm/proxy/_experimental/mcp_server/oauth_utils.py
#	litellm/proxy/_experimental/mcp_server/server.py
#	litellm/proxy/_experimental/out/404.html
#	litellm/proxy/_experimental/out/__next.__PAGE__.txt
#	litellm/proxy/_experimental/out/__next._full.txt
#	litellm/proxy/_experimental/out/__next._head.txt
#	litellm/proxy/_experimental/out/__next._index.txt
#	litellm/proxy/_experimental/out/__next._tree.txt
#	litellm/proxy/_experimental/out/_next/static/8TZ2JbOi7SZ6BCj9ScTHW/_buildManifest.js
#	litellm/proxy/_experimental/out/_next/static/8TZ2JbOi7SZ6BCj9ScTHW/_clientMiddlewareManifest.json
#	litellm/proxy/_experimental/out/_next/static/8TZ2JbOi7SZ6BCj9ScTHW/_ssgManifest.js
#	litellm/proxy/_experimental/out/_next/static/L07LLek3NGtynjTxlCdLS/_buildManifest.js
#	litellm/proxy/_experimental/out/_next/static/L07LLek3NGtynjTxlCdLS/_clientMiddlewareManifest.json
#	litellm/proxy/_experimental/out/_next/static/L07LLek3NGtynjTxlCdLS/_ssgManifest.js
#	litellm/proxy/_experimental/out/_next/static/LpD6ruZoEpvYpT5IvMEoa/_buildManifest.js
#	litellm/proxy/_experimental/out/_next/static/LpD6ruZoEpvYpT5IvMEoa/_clientMiddlewareManifest.json
#	litellm/proxy/_experimental/out/_next/static/LpD6ruZoEpvYpT5IvMEoa/_ssgManifest.js
#	litellm/proxy/_experimental/out/_not-found.html
#	litellm/proxy/_experimental/out/_not-found.txt
#	litellm/proxy/_experimental/out/_not-found/__next._full.txt
#	litellm/proxy/_experimental/out/_not-found/__next._head.txt
#	litellm/proxy/_experimental/out/_not-found/__next._index.txt
#	litellm/proxy/_experimental/out/_not-found/__next._not-found.__PAGE__.txt
#	litellm/proxy/_experimental/out/_not-found/__next._not-found.txt
#	litellm/proxy/_experimental/out/_not-found/__next._tree.txt
#	litellm/proxy/_experimental/out/api-reference.html
#	litellm/proxy/_experimental/out/api-reference.txt
#	litellm/proxy/_experimental/out/api-reference/__next.!KGRhc2hib2FyZCk.api-reference.__PAGE__.txt
#	litellm/proxy/_experimental/out/api-reference/__next.!KGRhc2hib2FyZCk.api-reference.txt
#	litellm/proxy/_experimental/out/api-reference/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/api-reference/__next._full.txt
#	litellm/proxy/_experimental/out/api-reference/__next._head.txt
#	litellm/proxy/_experimental/out/api-reference/__next._index.txt
#	litellm/proxy/_experimental/out/api-reference/__next._tree.txt
#	litellm/proxy/_experimental/out/chat.html
#	litellm/proxy/_experimental/out/chat.txt
#	litellm/proxy/_experimental/out/chat/__next._full.txt
#	litellm/proxy/_experimental/out/chat/__next._head.txt
#	litellm/proxy/_experimental/out/chat/__next._index.txt
#	litellm/proxy/_experimental/out/chat/__next._tree.txt
#	litellm/proxy/_experimental/out/chat/__next.chat.__PAGE__.txt
#	litellm/proxy/_experimental/out/chat/__next.chat.txt
#	litellm/proxy/_experimental/out/experimental/api-playground.html
#	litellm/proxy/_experimental/out/experimental/api-playground.txt
#	litellm/proxy/_experimental/out/experimental/api-playground/__next.!KGRhc2hib2FyZCk.experimental.api-playground.__PAGE__.txt
#	litellm/proxy/_experimental/out/experimental/api-playground/__next.!KGRhc2hib2FyZCk.experimental.api-playground.txt
#	litellm/proxy/_experimental/out/experimental/api-playground/__next.!KGRhc2hib2FyZCk.experimental.txt
#	litellm/proxy/_experimental/out/experimental/api-playground/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/experimental/api-playground/__next._full.txt
#	litellm/proxy/_experimental/out/experimental/api-playground/__next._head.txt
#	litellm/proxy/_experimental/out/experimental/api-playground/__next._index.txt
#	litellm/proxy/_experimental/out/experimental/api-playground/__next._tree.txt
#	litellm/proxy/_experimental/out/experimental/budgets.html
#	litellm/proxy/_experimental/out/experimental/budgets.txt
#	litellm/proxy/_experimental/out/experimental/budgets/__next.!KGRhc2hib2FyZCk.experimental.budgets.__PAGE__.txt
#	litellm/proxy/_experimental/out/experimental/budgets/__next.!KGRhc2hib2FyZCk.experimental.budgets.txt
#	litellm/proxy/_experimental/out/experimental/budgets/__next.!KGRhc2hib2FyZCk.experimental.txt
#	litellm/proxy/_experimental/out/experimental/budgets/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/experimental/budgets/__next._full.txt
#	litellm/proxy/_experimental/out/experimental/budgets/__next._head.txt
#	litellm/proxy/_experimental/out/experimental/budgets/__next._index.txt
#	litellm/proxy/_experimental/out/experimental/budgets/__next._tree.txt
#	litellm/proxy/_experimental/out/experimental/caching.html
#	litellm/proxy/_experimental/out/experimental/caching.txt
#	litellm/proxy/_experimental/out/experimental/caching/__next.!KGRhc2hib2FyZCk.experimental.caching.__PAGE__.txt
#	litellm/proxy/_experimental/out/experimental/caching/__next.!KGRhc2hib2FyZCk.experimental.caching.txt
#	litellm/proxy/_experimental/out/experimental/caching/__next.!KGRhc2hib2FyZCk.experimental.txt
#	litellm/proxy/_experimental/out/experimental/caching/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/experimental/caching/__next._full.txt
#	litellm/proxy/_experimental/out/experimental/caching/__next._head.txt
#	litellm/proxy/_experimental/out/experimental/caching/__next._index.txt
#	litellm/proxy/_experimental/out/experimental/caching/__next._tree.txt
#	litellm/proxy/_experimental/out/experimental/claude-code-plugins.html
#	litellm/proxy/_experimental/out/experimental/claude-code-plugins.txt
#	litellm/proxy/_experimental/out/experimental/claude-code-plugins/__next.!KGRhc2hib2FyZCk.experimental.claude-code-plugins.__PAGE__.txt
#	litellm/proxy/_experimental/out/experimental/claude-code-plugins/__next.!KGRhc2hib2FyZCk.experimental.claude-code-plugins.txt
#	litellm/proxy/_experimental/out/experimental/claude-code-plugins/__next.!KGRhc2hib2FyZCk.experimental.txt
#	litellm/proxy/_experimental/out/experimental/claude-code-plugins/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/experimental/claude-code-plugins/__next._full.txt
#	litellm/proxy/_experimental/out/experimental/claude-code-plugins/__next._head.txt
#	litellm/proxy/_experimental/out/experimental/claude-code-plugins/__next._index.txt
#	litellm/proxy/_experimental/out/experimental/claude-code-plugins/__next._tree.txt
#	litellm/proxy/_experimental/out/experimental/old-usage.html
#	litellm/proxy/_experimental/out/experimental/old-usage.txt
#	litellm/proxy/_experimental/out/experimental/old-usage/__next.!KGRhc2hib2FyZCk.experimental.old-usage.__PAGE__.txt
#	litellm/proxy/_experimental/out/experimental/old-usage/__next.!KGRhc2hib2FyZCk.experimental.old-usage.txt
#	litellm/proxy/_experimental/out/experimental/old-usage/__next.!KGRhc2hib2FyZCk.experimental.txt
#	litellm/proxy/_experimental/out/experimental/old-usage/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/experimental/old-usage/__next._full.txt
#	litellm/proxy/_experimental/out/experimental/old-usage/__next._head.txt
#	litellm/proxy/_experimental/out/experimental/old-usage/__next._index.txt
#	litellm/proxy/_experimental/out/experimental/old-usage/__next._tree.txt
#	litellm/proxy/_experimental/out/experimental/prompts.html
#	litellm/proxy/_experimental/out/experimental/prompts.txt
#	litellm/proxy/_experimental/out/experimental/prompts/__next.!KGRhc2hib2FyZCk.experimental.prompts.__PAGE__.txt
#	litellm/proxy/_experimental/out/experimental/prompts/__next.!KGRhc2hib2FyZCk.experimental.prompts.txt
#	litellm/proxy/_experimental/out/experimental/prompts/__next.!KGRhc2hib2FyZCk.experimental.txt
#	litellm/proxy/_experimental/out/experimental/prompts/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/experimental/prompts/__next._full.txt
#	litellm/proxy/_experimental/out/experimental/prompts/__next._head.txt
#	litellm/proxy/_experimental/out/experimental/prompts/__next._index.txt
#	litellm/proxy/_experimental/out/experimental/prompts/__next._tree.txt
#	litellm/proxy/_experimental/out/experimental/tag-management.html
#	litellm/proxy/_experimental/out/experimental/tag-management.txt
#	litellm/proxy/_experimental/out/experimental/tag-management/__next.!KGRhc2hib2FyZCk.experimental.tag-management.__PAGE__.txt
#	litellm/proxy/_experimental/out/experimental/tag-management/__next.!KGRhc2hib2FyZCk.experimental.tag-management.txt
#	litellm/proxy/_experimental/out/experimental/tag-management/__next.!KGRhc2hib2FyZCk.experimental.txt
#	litellm/proxy/_experimental/out/experimental/tag-management/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/experimental/tag-management/__next._full.txt
#	litellm/proxy/_experimental/out/experimental/tag-management/__next._head.txt
#	litellm/proxy/_experimental/out/experimental/tag-management/__next._index.txt
#	litellm/proxy/_experimental/out/experimental/tag-management/__next._tree.txt
#	litellm/proxy/_experimental/out/guardrails.html
#	litellm/proxy/_experimental/out/guardrails.txt
#	litellm/proxy/_experimental/out/guardrails/__next.!KGRhc2hib2FyZCk.guardrails.__PAGE__.txt
#	litellm/proxy/_experimental/out/guardrails/__next.!KGRhc2hib2FyZCk.guardrails.txt
#	litellm/proxy/_experimental/out/guardrails/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/guardrails/__next._full.txt
#	litellm/proxy/_experimental/out/guardrails/__next._head.txt
#	litellm/proxy/_experimental/out/guardrails/__next._index.txt
#	litellm/proxy/_experimental/out/guardrails/__next._tree.txt
#	litellm/proxy/_experimental/out/index.html
#	litellm/proxy/_experimental/out/index.txt
#	litellm/proxy/_experimental/out/login.html
#	litellm/proxy/_experimental/out/login.txt
#	litellm/proxy/_experimental/out/login/__next._full.txt
#	litellm/proxy/_experimental/out/login/__next._head.txt
#	litellm/proxy/_experimental/out/login/__next._index.txt
#	litellm/proxy/_experimental/out/login/__next._tree.txt
#	litellm/proxy/_experimental/out/login/__next.login.__PAGE__.txt
#	litellm/proxy/_experimental/out/login/__next.login.txt
#	litellm/proxy/_experimental/out/logs.html
#	litellm/proxy/_experimental/out/logs.txt
#	litellm/proxy/_experimental/out/logs/__next.!KGRhc2hib2FyZCk.logs.__PAGE__.txt
#	litellm/proxy/_experimental/out/logs/__next.!KGRhc2hib2FyZCk.logs.txt
#	litellm/proxy/_experimental/out/logs/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/logs/__next._full.txt
#	litellm/proxy/_experimental/out/logs/__next._head.txt
#	litellm/proxy/_experimental/out/logs/__next._index.txt
#	litellm/proxy/_experimental/out/logs/__next._tree.txt
#	litellm/proxy/_experimental/out/mcp/oauth/callback.html
#	litellm/proxy/_experimental/out/mcp/oauth/callback.txt
#	litellm/proxy/_experimental/out/mcp/oauth/callback/__next._full.txt
#	litellm/proxy/_experimental/out/mcp/oauth/callback/__next._head.txt
#	litellm/proxy/_experimental/out/mcp/oauth/callback/__next._index.txt
#	litellm/proxy/_experimental/out/mcp/oauth/callback/__next._tree.txt
#	litellm/proxy/_experimental/out/mcp/oauth/callback/__next.mcp.oauth.callback.__PAGE__.txt
#	litellm/proxy/_experimental/out/mcp/oauth/callback/__next.mcp.oauth.callback.txt
#	litellm/proxy/_experimental/out/mcp/oauth/callback/__next.mcp.oauth.txt
#	litellm/proxy/_experimental/out/mcp/oauth/callback/__next.mcp.txt
#	litellm/proxy/_experimental/out/model-hub.html
#	litellm/proxy/_experimental/out/model-hub.txt
#	litellm/proxy/_experimental/out/model-hub/__next.!KGRhc2hib2FyZCk.model-hub.__PAGE__.txt
#	litellm/proxy/_experimental/out/model-hub/__next.!KGRhc2hib2FyZCk.model-hub.txt
#	litellm/proxy/_experimental/out/model-hub/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/model-hub/__next._full.txt
#	litellm/proxy/_experimental/out/model-hub/__next._head.txt
#	litellm/proxy/_experimental/out/model-hub/__next._index.txt
#	litellm/proxy/_experimental/out/model-hub/__next._tree.txt
#	litellm/proxy/_experimental/out/model_hub.html
#	litellm/proxy/_experimental/out/model_hub.txt
#	litellm/proxy/_experimental/out/model_hub/__next._full.txt
#	litellm/proxy/_experimental/out/model_hub/__next._head.txt
#	litellm/proxy/_experimental/out/model_hub/__next._index.txt
#	litellm/proxy/_experimental/out/model_hub/__next._tree.txt
#	litellm/proxy/_experimental/out/model_hub/__next.model_hub.__PAGE__.txt
#	litellm/proxy/_experimental/out/model_hub/__next.model_hub.txt
#	litellm/proxy/_experimental/out/model_hub_table.html
#	litellm/proxy/_experimental/out/model_hub_table.txt
#	litellm/proxy/_experimental/out/model_hub_table/__next._full.txt
#	litellm/proxy/_experimental/out/model_hub_table/__next._head.txt
#	litellm/proxy/_experimental/out/model_hub_table/__next._index.txt
#	litellm/proxy/_experimental/out/model_hub_table/__next._tree.txt
#	litellm/proxy/_experimental/out/model_hub_table/__next.model_hub_table.__PAGE__.txt
#	litellm/proxy/_experimental/out/model_hub_table/__next.model_hub_table.txt
#	litellm/proxy/_experimental/out/models-and-endpoints.html
#	litellm/proxy/_experimental/out/models-and-endpoints.txt
#	litellm/proxy/_experimental/out/models-and-endpoints/__next.!KGRhc2hib2FyZCk.models-and-endpoints.__PAGE__.txt
#	litellm/proxy/_experimental/out/models-and-endpoints/__next.!KGRhc2hib2FyZCk.models-and-endpoints.txt
#	litellm/proxy/_experimental/out/models-and-endpoints/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/models-and-endpoints/__next._full.txt
#	litellm/proxy/_experimental/out/models-and-endpoints/__next._head.txt
#	litellm/proxy/_experimental/out/models-and-endpoints/__next._index.txt
#	litellm/proxy/_experimental/out/models-and-endpoints/__next._tree.txt
#	litellm/proxy/_experimental/out/onboarding.html
#	litellm/proxy/_experimental/out/onboarding.txt
#	litellm/proxy/_experimental/out/onboarding/__next._full.txt
#	litellm/proxy/_experimental/out/onboarding/__next._head.txt
#	litellm/proxy/_experimental/out/onboarding/__next._index.txt
#	litellm/proxy/_experimental/out/onboarding/__next._tree.txt
#	litellm/proxy/_experimental/out/onboarding/__next.onboarding.__PAGE__.txt
#	litellm/proxy/_experimental/out/onboarding/__next.onboarding.txt
#	litellm/proxy/_experimental/out/organizations.html
#	litellm/proxy/_experimental/out/organizations.txt
#	litellm/proxy/_experimental/out/organizations/__next.!KGRhc2hib2FyZCk.organizations.__PAGE__.txt
#	litellm/proxy/_experimental/out/organizations/__next.!KGRhc2hib2FyZCk.organizations.txt
#	litellm/proxy/_experimental/out/organizations/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/organizations/__next._full.txt
#	litellm/proxy/_experimental/out/organizations/__next._head.txt
#	litellm/proxy/_experimental/out/organizations/__next._index.txt
#	litellm/proxy/_experimental/out/organizations/__next._tree.txt
#	litellm/proxy/_experimental/out/playground.html
#	litellm/proxy/_experimental/out/playground.txt
#	litellm/proxy/_experimental/out/playground/__next.!KGRhc2hib2FyZCk.playground.__PAGE__.txt
#	litellm/proxy/_experimental/out/playground/__next.!KGRhc2hib2FyZCk.playground.txt
#	litellm/proxy/_experimental/out/playground/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/playground/__next._full.txt
#	litellm/proxy/_experimental/out/playground/__next._head.txt
#	litellm/proxy/_experimental/out/playground/__next._index.txt
#	litellm/proxy/_experimental/out/playground/__next._tree.txt
#	litellm/proxy/_experimental/out/policies.html
#	litellm/proxy/_experimental/out/policies.txt
#	litellm/proxy/_experimental/out/policies/__next.!KGRhc2hib2FyZCk.policies.__PAGE__.txt
#	litellm/proxy/_experimental/out/policies/__next.!KGRhc2hib2FyZCk.policies.txt
#	litellm/proxy/_experimental/out/policies/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/policies/__next._full.txt
#	litellm/proxy/_experimental/out/policies/__next._head.txt
#	litellm/proxy/_experimental/out/policies/__next._index.txt
#	litellm/proxy/_experimental/out/policies/__next._tree.txt
#	litellm/proxy/_experimental/out/settings/admin-settings.html
#	litellm/proxy/_experimental/out/settings/admin-settings.txt
#	litellm/proxy/_experimental/out/settings/admin-settings/__next.!KGRhc2hib2FyZCk.settings.admin-settings.__PAGE__.txt
#	litellm/proxy/_experimental/out/settings/admin-settings/__next.!KGRhc2hib2FyZCk.settings.admin-settings.txt
#	litellm/proxy/_experimental/out/settings/admin-settings/__next.!KGRhc2hib2FyZCk.settings.txt
#	litellm/proxy/_experimental/out/settings/admin-settings/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/settings/admin-settings/__next._full.txt
#	litellm/proxy/_experimental/out/settings/admin-settings/__next._head.txt
#	litellm/proxy/_experimental/out/settings/admin-settings/__next._index.txt
#	litellm/proxy/_experimental/out/settings/admin-settings/__next._tree.txt
#	litellm/proxy/_experimental/out/settings/logging-and-alerts.html
#	litellm/proxy/_experimental/out/settings/logging-and-alerts.txt
#	litellm/proxy/_experimental/out/settings/logging-and-alerts/__next.!KGRhc2hib2FyZCk.settings.logging-and-alerts.__PAGE__.txt
#	litellm/proxy/_experimental/out/settings/logging-and-alerts/__next.!KGRhc2hib2FyZCk.settings.logging-and-alerts.txt
#	litellm/proxy/_experimental/out/settings/logging-and-alerts/__next.!KGRhc2hib2FyZCk.settings.txt
#	litellm/proxy/_experimental/out/settings/logging-and-alerts/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/settings/logging-and-alerts/__next._full.txt
#	litellm/proxy/_experimental/out/settings/logging-and-alerts/__next._head.txt
#	litellm/proxy/_experimental/out/settings/logging-and-alerts/__next._index.txt
#	litellm/proxy/_experimental/out/settings/logging-and-alerts/__next._tree.txt
#	litellm/proxy/_experimental/out/settings/router-settings.html
#	litellm/proxy/_experimental/out/settings/router-settings.txt
#	litellm/proxy/_experimental/out/settings/router-settings/__next.!KGRhc2hib2FyZCk.settings.router-settings.__PAGE__.txt
#	litellm/proxy/_experimental/out/settings/router-settings/__next.!KGRhc2hib2FyZCk.settings.router-settings.txt
#	litellm/proxy/_experimental/out/settings/router-settings/__next.!KGRhc2hib2FyZCk.settings.txt
#	litellm/proxy/_experimental/out/settings/router-settings/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/settings/router-settings/__next._full.txt
#	litellm/proxy/_experimental/out/settings/router-settings/__next._head.txt
#	litellm/proxy/_experimental/out/settings/router-settings/__next._index.txt
#	litellm/proxy/_experimental/out/settings/router-settings/__next._tree.txt
#	litellm/proxy/_experimental/out/settings/ui-theme.html
#	litellm/proxy/_experimental/out/settings/ui-theme.txt
#	litellm/proxy/_experimental/out/settings/ui-theme/__next.!KGRhc2hib2FyZCk.settings.txt
#	litellm/proxy/_experimental/out/settings/ui-theme/__next.!KGRhc2hib2FyZCk.settings.ui-theme.__PAGE__.txt
#	litellm/proxy/_experimental/out/settings/ui-theme/__next.!KGRhc2hib2FyZCk.settings.ui-theme.txt
#	litellm/proxy/_experimental/out/settings/ui-theme/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/settings/ui-theme/__next._full.txt
#	litellm/proxy/_experimental/out/settings/ui-theme/__next._head.txt
#	litellm/proxy/_experimental/out/settings/ui-theme/__next._index.txt
#	litellm/proxy/_experimental/out/settings/ui-theme/__next._tree.txt
#	litellm/proxy/_experimental/out/skills.html
#	litellm/proxy/_experimental/out/skills.txt
#	litellm/proxy/_experimental/out/skills/__next.!KGRhc2hib2FyZCk.skills.__PAGE__.txt
#	litellm/proxy/_experimental/out/skills/__next.!KGRhc2hib2FyZCk.skills.txt
#	litellm/proxy/_experimental/out/skills/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/skills/__next._full.txt
#	litellm/proxy/_experimental/out/skills/__next._head.txt
#	litellm/proxy/_experimental/out/skills/__next._index.txt
#	litellm/proxy/_experimental/out/skills/__next._tree.txt
#	litellm/proxy/_experimental/out/teams.html
#	litellm/proxy/_experimental/out/teams.txt
#	litellm/proxy/_experimental/out/teams/__next.!KGRhc2hib2FyZCk.teams.__PAGE__.txt
#	litellm/proxy/_experimental/out/teams/__next.!KGRhc2hib2FyZCk.teams.txt
#	litellm/proxy/_experimental/out/teams/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/teams/__next._full.txt
#	litellm/proxy/_experimental/out/teams/__next._head.txt
#	litellm/proxy/_experimental/out/teams/__next._index.txt
#	litellm/proxy/_experimental/out/teams/__next._tree.txt
#	litellm/proxy/_experimental/out/test-key.html
#	litellm/proxy/_experimental/out/test-key.txt
#	litellm/proxy/_experimental/out/test-key/__next.!KGRhc2hib2FyZCk.test-key.__PAGE__.txt
#	litellm/proxy/_experimental/out/test-key/__next.!KGRhc2hib2FyZCk.test-key.txt
#	litellm/proxy/_experimental/out/test-key/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/test-key/__next._full.txt
#	litellm/proxy/_experimental/out/test-key/__next._head.txt
#	litellm/proxy/_experimental/out/test-key/__next._index.txt
#	litellm/proxy/_experimental/out/test-key/__next._tree.txt
#	litellm/proxy/_experimental/out/tools/mcp-servers.html
#	litellm/proxy/_experimental/out/tools/mcp-servers.txt
#	litellm/proxy/_experimental/out/tools/mcp-servers/__next.!KGRhc2hib2FyZCk.tools.mcp-servers.__PAGE__.txt
#	litellm/proxy/_experimental/out/tools/mcp-servers/__next.!KGRhc2hib2FyZCk.tools.mcp-servers.txt
#	litellm/proxy/_experimental/out/tools/mcp-servers/__next.!KGRhc2hib2FyZCk.tools.txt
#	litellm/proxy/_experimental/out/tools/mcp-servers/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/tools/mcp-servers/__next._full.txt
#	litellm/proxy/_experimental/out/tools/mcp-servers/__next._head.txt
#	litellm/proxy/_experimental/out/tools/mcp-servers/__next._index.txt
#	litellm/proxy/_experimental/out/tools/mcp-servers/__next._tree.txt
#	litellm/proxy/_experimental/out/tools/vector-stores.html
#	litellm/proxy/_experimental/out/tools/vector-stores.txt
#	litellm/proxy/_experimental/out/tools/vector-stores/__next.!KGRhc2hib2FyZCk.tools.txt
#	litellm/proxy/_experimental/out/tools/vector-stores/__next.!KGRhc2hib2FyZCk.tools.vector-stores.__PAGE__.txt
#	litellm/proxy/_experimental/out/tools/vector-stores/__next.!KGRhc2hib2FyZCk.tools.vector-stores.txt
#	litellm/proxy/_experimental/out/tools/vector-stores/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/tools/vector-stores/__next._full.txt
#	litellm/proxy/_experimental/out/tools/vector-stores/__next._head.txt
#	litellm/proxy/_experimental/out/tools/vector-stores/__next._index.txt
#	litellm/proxy/_experimental/out/tools/vector-stores/__next._tree.txt
#	litellm/proxy/_experimental/out/usage.html
#	litellm/proxy/_experimental/out/usage.txt
#	litellm/proxy/_experimental/out/usage/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/usage/__next.!KGRhc2hib2FyZCk.usage.__PAGE__.txt
#	litellm/proxy/_experimental/out/usage/__next.!KGRhc2hib2FyZCk.usage.txt
#	litellm/proxy/_experimental/out/usage/__next._full.txt
#	litellm/proxy/_experimental/out/usage/__next._head.txt
#	litellm/proxy/_experimental/out/usage/__next._index.txt
#	litellm/proxy/_experimental/out/usage/__next._tree.txt
#	litellm/proxy/_experimental/out/users.html
#	litellm/proxy/_experimental/out/users.txt
#	litellm/proxy/_experimental/out/users/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/users/__next.!KGRhc2hib2FyZCk.users.__PAGE__.txt
#	litellm/proxy/_experimental/out/users/__next.!KGRhc2hib2FyZCk.users.txt
#	litellm/proxy/_experimental/out/users/__next._full.txt
#	litellm/proxy/_experimental/out/users/__next._head.txt
#	litellm/proxy/_experimental/out/users/__next._index.txt
#	litellm/proxy/_experimental/out/users/__next._tree.txt
#	litellm/proxy/_experimental/out/virtual-keys.html
#	litellm/proxy/_experimental/out/virtual-keys.txt
#	litellm/proxy/_experimental/out/virtual-keys/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/virtual-keys/__next.!KGRhc2hib2FyZCk.virtual-keys.__PAGE__.txt
#	litellm/proxy/_experimental/out/virtual-keys/__next.!KGRhc2hib2FyZCk.virtual-keys.txt
#	litellm/proxy/_experimental/out/virtual-keys/__next._full.txt
#	litellm/proxy/_experimental/out/virtual-keys/__next._head.txt
#	litellm/proxy/_experimental/out/virtual-keys/__next._index.txt
#	litellm/proxy/_experimental/out/virtual-keys/__next._tree.txt
#	litellm/proxy/auth/auth_utils.py
#	litellm/proxy/management_endpoints/internal_user_endpoints.py
#	litellm/proxy/management_endpoints/mcp_management_endpoints.py
#	litellm/proxy/proxy_server.py
#	litellm/types/mcp_server/mcp_server_manager.py
#	pyproject.toml
#	tests/test_litellm/interactions/test_openapi_compliance.py
#	tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_common_utils.py
#	tests/test_litellm/proxy/_experimental/mcp_server/test_discoverable_endpoints.py
#	tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py
#	tests/test_litellm/proxy/_experimental/mcp_server/test_openapi_to_mcp_generator.py
#	tests/test_litellm/proxy/auth/test_auth_utils.py
#	tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py
#	tests/test_litellm/proxy/management_endpoints/test_mcp_management_endpoints.py
#	tests/test_litellm/proxy/rag_endpoints/test_rag_endpoints.py
#	tests/test_litellm/proxy/test_proxy_server.py
#	uv.lock
2026-05-20 17:27:02 +01:00
Claude
22e9064673
fix(mcp,jwt): address greptile concurrency and code-quality concerns
- _apply_issuer_claim_mappings now builds a new dict and reads from the
  original token, rather than mutating its input. The change is
  behaviour-preserving (caller passes a fresh jwt.decode result), but
  avoids the surprise-mutation pattern flagged by greptile.
- is_network_error uses isinstance(exc, httpx.TransportError) instead of
  matching type(exc).__name__ against a hand-maintained string set, so
  ReadError / WriteError / ProxyError / etc. are also treated as
  transport-level failures and surfaced as HTTP 502.
- fetch_upstream_oauth_protected_resource now coalesces concurrent
  discovery requests per (server_id, resource_url) through an
  asyncio.Lock so concurrent .well-known calls share a single upstream
  fetch + cache write.
- Drop the redundant 'if trusted_ranges:' branch in get_mcp_client_ip;
  it is always true on the path that reaches it (the prior 'if not
  trusted_ranges:' early-returns).

Co-authored-by: Claude <claude@anthropic.com>
2026-05-20 16:19:16 +00:00
Claude
087a4da116
fix(tests): backfill local model_cost into remote-fetched map
litellm.model_cost is loaded at import time from LITELLM_MODEL_COST_MAP_URL
(pinned to main), so pricing entries that exist only in this branch (e.g.
mistral/ministral-8b-2512, freshly added because Mistral's API now returns
this id from mistral-tiny) are absent at test time and completion_cost
lookups raise 'This model isn't mapped yet'. Backfill the in-tree backup
into litellm.model_cost in the local_testing conftest so cassette-driven
cost calculations resolve against the entries that ship with the branch
under test.

Fixes local_testing_part1 failures on test_completion_mistral_api and
test_completion_mistral_api_modified_input.
2026-05-20 16:17:56 +00:00
Claude
fc31a07013
Merge remote-tracking branch 'origin/litellm_feat/v1.84.0-mcp-gateway-jwt-auth' into litellm_feat/v1.84.0-mcp-gateway-jwt-auth
# Conflicts:
#	litellm/proxy/management_endpoints/mcp_management_endpoints.py
2026-05-20 16:00:27 +00:00
Claude
d42a66adb6
Merge branch 'litellm_internal_staging' into litellm_feat/v1.84.0-mcp-gateway-jwt-auth
Resolved unrelated-history merge using e1fc955464 (the original base of upstream PR #28008) as the 3-way merge base.

Conflict resolutions:
- discoverable_endpoints.py: kept PR's _OAUTH_METADATA_CACHE / prune helpers and PR's inline redirect_uri extraction in /callback; took staging's callback comment that mentions the ops-allowlist code path served by validate_trusted_redirect_uri.
- oauth_utils.py: took staging's version (strict superset: adds MCP_TRUSTED_REDIRECT_ORIGINS ops allowlist, userinfo/backslash netloc rejection, rejection diagnostics).
- server.py: kept both PR's _raise_preemptive_401_for_unauthenticated_servers and staging's _get_forwarded_auth_from_scope / _probe_upstream_auth / _check_passthrough_upstream_auth additions (independent helpers).
- mcp_management_endpoints.py: took staging's verify_exp=False JWT decode option and staging's delegate_auth_to_upstream PKCE bypass block.
- proxy_server.py: took staging's restructured 4-case dynamic_mcp_route, and ported PR's scope['_original_path'] preservation into _mcp_forward_as_path so server.py's OAuth-challenge URL selection still has the public request path.
- types/mcp_server/mcp_server_manager.py: kept both PR's is_oauth_passthrough and staging's has_token_exchange_config properties (additive).
- test_image_edits.py: took staging's _make_test_images() per fix(image_edits).
- test_realtime_guardrails_openai.py: took staging's unicode-punctuation normalization.
- test_fireworks_ai_translation.py: took staging's mocked-payload test_document_inlining_example.
- test_openapi_compliance.py: kept PR's removal of 'steps' per fix(interactions).
- test_discoverable_endpoints.py: took staging's tests (PR's tests assumed the removed get_proxy_base_url import path).
- test_mcp_server.py: took staging's pre-flight upstream-auth tests (cover the helpers we kept).
- test_mcp_management_endpoints.py: took staging's verify_exp=False / delegate_auth_to_upstream tests, and also kept PR's test_mcp_oauth_authorize_token_routes_use_browser_auth_dependency (independent route-wiring assertion).

Co-authored-by: Claude <claude@anthropic.com>
2026-05-20 15:59:22 +00:00
Claude (greptile fixer)
298936f83d
fix(mcp,auth): address greptile review concerns
- handle_sse_mcp now calls _raise_preemptive_401_for_unauthenticated_servers
  so SSE clients to pass-through OAuth MCP servers receive the RFC 9728
  401 + WWW-Authenticate challenge that the streamable-HTTP path already emits.
- get_request_route strips a trailing slash from root_path before length-based
  prefix removal so non-canonical ASGI root_path values like "/litellm/"
  don't strip the leading slash from the returned route.
- _mcp_oauth_user_api_key_auth's cookie JWT decode now passes
  options={"verify_aud": False} so a future revision of the UI session
  JWT containing an aud claim cannot silently downgrade the request to
  unauthenticated.

Co-authored-by: Claude <claude@anthropic.com>
2026-05-20 15:32:09 +00:00
Claude
76c704be34
fix(model_prices): register mistral/ministral-8b-2512
Mistral's API now returns model='ministral-8b-2512' when 'mistral-tiny'
is requested, so test_completion_mistral_api fails with 'This model
isn't mapped yet'. Adding the entry so completion_cost can resolve the
cost for that response.

Author: Claude <noreply@anthropic.com>
2026-05-20 15:26:24 +00:00
yuneng-jiang
37845acb57
fix(tests): migrate realtime + rerank tests off shut-down upstream models (#28191)
* fix(tests): use gpt-realtime in realtime guardrails test

OpenAI shut down gpt-4o-realtime-preview-2024-12-17 on 2026-05-07, so
the live OpenAI realtime guardrails integration test now fails with
model_not_found (session.created never arrives, _wait_for_event times
out). Point OPENAI_REALTIME_URL at the current GA model, gpt-realtime.

Scope limited to this test: the pricing-catalog JSON keeps the retired
entries intentionally (historical cost calc + separate Azure timeline),
and the Azure realtime cost-calc test is unaffected.

* fix(tests): mock nvidia_nim rerank instead of hitting EOL'd endpoint

NVIDIA reached end-of-life for the hosted nvidia/llama-3.2-nv-rerankqa-1b-v2
rerank API on 2026-05-18 with no published replacement, so the live
BaseLLMRerankTest.test_basic_rerank for nvidia_nim now returns HTTP 410
("Gone"). NVIDIA's hosted catalog rotates on a schedule, so swapping in
another live model would only defer the failure.

Override test_basic_rerank in TestNvidiaNim to mock the sync/async HTTP
transport (same pattern as test_nvidia_nim_rerank_ranking_endpoint in this
file) and inject a fake NVIDIA_NIM_API_KEY via monkeypatch. The
request/response transformation and cost calculation stay covered offline.
Scope limited to nvidia_nim; other BaseLLMRerankTest providers untouched.

* fix(tests): migrate remaining realtime tests off shut-down gpt-4o-realtime-preview

OpenAI's 2026-05-07 shutdown removed the entire gpt-4o-realtime-preview
family, including the undated 'gpt-4o-realtime-preview' alias (not just the
dated snapshot fixed earlier). Three live tests still connected with the
dead alias and failed with messages_received=1 (an error event instead of
session.created):

- test_openai_realtime_simple.py: get_model() -> gpt-realtime (drives
  TestOpenAIRealtime.test_realtime_connection / test_realtime_with_query_params)
- test_openai_realtime.py: test_openai_realtime_direct_call_no_intent and
  test_openai_realtime_direct_call_with_intent -> openai/gpt-realtime
  (the with_intent test shares the same dead alias even though it was not
  in the failing set this run)

Mocked unit tests (test_realtime_query_params_construction,
test_realtime_query_params_use_normalized_model_name) are left as-is: they
never hit the network and assert string plumbing only.

Also fixes test_text_message_blocked_by_guardrail_no_ai_response, which now
connects (the earlier URL swap worked) but tripped a model-wording-brittle
assertion. The guardrail flow asks the model to voice the block message
verbatim; gpt-4o-realtime-preview complied (output contained 'blocked'),
gpt-realtime refuses verbatim-repeat instructions ('I'm sorry, but I can't
repeat that message.'). Since the original user message is blocked before
it reaches OpenAI, the refusal is still a safe outcome. Assertion #3 now
accepts both voicing and refusal, and adds a hard check that the blocked
phrase never leaks into AI output.

(cherry picked from commit ce87c411bf)
2026-05-20 15:25:30 +00:00
yuneng-jiang
c65e598402
fix(tests): replace shut-down gpt-4o-audio-preview with gpt-audio-1.5 (#28281)
* fix(tests): replace shut-down gpt-4o-audio-preview with gpt-audio-1.5

OpenAI shut down gpt-4o-audio-preview on 2026-05-07, so the live audio
calls in test_stream_chunk_builder_openai_audio_output_usage and
test_standard_logging_payload_audio now hard-fail with a model-not-found
error on every PR. The error was not "openai-internal", so the except
block swallowed it and execution fell through to an unbound
completion/response (UnboundLocalError).

Switch both tests to gpt-audio-1.5, OpenAI's recommended successor
(GA, not deprecated, already present in the litellm cost map so the
response_cost assertion still resolves). Also broaden the except to
skip with the real error in the reason instead of crashing, so a
transient upstream blip can't reintroduce the UnboundLocalError.

* fix(tests): narrow audio-test skip to model-not-found, re-raise the rest

Address review feedback: an unconditional skip on any exception would
silently mask a litellm-internal regression in the audio path (broken
param transformation, serialization, bad header) instead of failing CI.

Skip only on the upstream-unavailable class (model_not_found / "does not
exist" / openai-internal) and re-raise everything else, so genuine
regressions still fail loudly. The UnboundLocalError is still fixed
because the handler either skips or raises - it never falls through.

* fix(tests): add budget_exceeded to expected Interaction status enum

Staging added budget_exceeded to the Interaction OpenAPI status enum; the staging merge into this branch picked up the spec change but not the matching test update, so test_status_enum_values failed in CI. Align the test's expected list (exact-match by design) with the live spec.

* fix(tests): mock HTTP fetch in test_img_url_token_counter

The test parameterized a live third-party image URL (blog.purpureus.net) which now 404s, causing get_image_dimensions to fall through to its base64 decode path and crash with 'not enough values to unpack' on every PR run. Mock safe_get with a tiny 1x1 PNG so the URL branch is still exercised without any network dependency.

* fix(tests): swap gpt-4o-audio-preview to gpt-audio-1.5 in test_gpt4o_audio

OpenAI shut down gpt-4o-audio-preview on 2026-05-07, so both live tests in test_gpt4o_audio.py (test_audio_output_from_model and test_audio_input_to_model) hard-fail model_not_found on every PR. Swap the hardcoded model to OpenAI's successor gpt-audio-1.5 (same chat-completions audio surface; already in the litellm cost map). Mirror the narrowed-skip pattern from the prior audio fixes: skip on model_not_found / does-not-exist / openai-internal, re-raise everything else so genuine litellm regressions still fail CI loudly.

(cherry picked from commit 92de7423ef)
2026-05-20 15:24:55 +00:00
Yuneng Jiang
76700e7bb3
test(fireworks): mock remaining live smoke tests
test_completion_fireworks_ai and test_completion_cost_fireworks_ai
made real Fireworks calls and broke whenever Fireworks rotated its
serverless catalog (no externally-verifiable model list exists).
They also asserted nothing — just printed.

Mock the HTTP post and assert real behavior instead: the request is
built with the right model/messages and the OpenAI-compatible
response parses back; the cost path yields a non-zero cost against
the local cost map. No network, no model dependency, stronger than
the old smoke checks.

(cherry picked from commit b5db7ed37d)
2026-05-20 15:24:49 +00:00
Yuneng Jiang
a0b61f6dcb
test(fireworks): replace deprecated llama-v3p3-70b-instruct model
Fireworks removed llama-v3p3-70b-instruct from serverless, so every
live test using it now fails with NotFoundError ("Model not found,
inaccessible, and/or not deployed").

Swap the 6 references (3 files) to the currently-served
accounts/fireworks/models/deepseek-v3p1 — the canonical model in
Fireworks' current docs examples and present in LiteLLM's cost map.
test_get_model_params_fireworks_ai is a pure pricing-heuristic test
(no network) asserting the >16b branch, so it uses llama-v3p1-70b-
instruct instead to keep the "fireworks-ai-above-16b" assertion and
branch coverage intact.

(cherry picked from commit 39a1d438f2)
2026-05-20 15:24:45 +00:00
Yuneng Jiang
93d3ed7ee5
fix(tests): drop dall-e-only test classes; route live image tests via gpt-image-1
Second wave of failures from the 2026-05-12 DALL-E shutdown:
- tests/image_gen_tests/test_image_edits.py::TestOpenAIImageEditDallE2
  and tests/image_gen_tests/test_image_generation.py::TestOpenAIDalle3
  are explicitly named for the deprecated models and can't pass; remove.
  gpt-image-1 coverage already exists in sibling classes.
- tests/local_testing/test_router.py image gen tests use dall-e-3 only
  as a routing example; swap to gpt-image-1.
- tests/local_testing/test_custom_callback_input.py image_generation
  success/failure paths swapped to gpt-image-1.

(cherry picked from commit 945b10ded4)
2026-05-20 15:24:41 +00:00
yuneng-jiang
a119324127
test: replace dall-e-3 with gpt-image-1 in health check and router tests (#27813)
OpenAI returns 'The model dall-e-3 does not exist' for the test account,
breaking test_openai_img_gen_health_check and test_image_generation.
Switch to gpt-image-1, matching the existing TestOpenAIGPTImage1 pattern.

(cherry picked from commit aee58db880)
2026-05-20 15:24:38 +00:00
Claude
806b8be451
fix(mcp,jwt): address greptile review concerns
- Cache _get_agent_object_permission via user_api_key_cache (sentinel for
  no-permission rows) so MCP requests from agent keys don't hit the DB on
  every tool-list / tool-call.
- Re-raise HTTPException in handle_sse_mcp so 401 + WWW-Authenticate
  challenges (and other HTTP errors) propagate to SSE clients instead of
  being swallowed as 500.
- Normalise booleans in _validate_token_response so admin rules written as
  JSON-style "true" / "false" match upstream responses that return
  Python True / False.
- Treat configured JWT issuer claim mappings as advisory: when a mapped
  field is absent or empty, leave the normalised claim unset instead of
  raising, matching the global litellm_jwtauth path.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-20 15:08:54 +00:00
gym-cmd
216e055910 fix(interactions): align status enum with openapi spec 2026-05-20 12:35:45 +01:00
Artem Dudarev
f6359cbf04 fix(mcp): forward Authorization in pass-through when x-litellm-api-key is admission
Commit 3753970cc9 widened the Authorization strip to cover all
is_oauth_passthrough servers — protecting against the LiteLLM admission
key leaking upstream when the caller used Authorization for admission,
but also silently stripping legitimate upstream OAuth bearers when the
caller used x-litellm-api-key for admission.

That broke transparent OAuth pass-through (EAI-506 V5/V6): standards-
compliant MCP clients (OpenCode, Claude Code, mcp-inspector) complete
PKCE against the upstream IdP and send the resulting token as plain
Authorization: Bearer per the MCP spec — with the wider strip in place,
that token never reaches the upstream and tools/list returns empty.

Narrow the strip: skip Authorization for pass-through servers only when
the caller did NOT supply x-litellm-api-key. When x-litellm-api-key is
present, admission is unambiguous and Authorization is free to carry
the upstream OAuth bearer.

The original security guarantee is preserved — a client that sends only
Authorization (no x-litellm-api-key) still has it stripped, so the
LiteLLM key cannot leak upstream via that path.

Tests:
- new: forwards Authorization when x-litellm-api-key is present
- new: still strips Authorization when only Authorization is present
- existing pass-through + M2M tests unchanged

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 11:41:37 +01:00
Sameer Kankute
e59e34bed3
Gemini managed agents support (#28270)
Some checks are pending
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Waiting to run
Unit Tests: Proxy DB Operations / auth-checks (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / budgets (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / custom-logging (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / db-and-spend (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / key-generation (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / logging-misc (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-runtime (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-server-core (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / schema-migration (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-utils (push) Blocked by required conditions
Unit Tests: Security / security (push) Waiting to run
* Add support for environment variable in interactions api

* Add sdk  support for gemini create agent

* Add agents endpoint support via proxy

* Add outputs of each api

* Add routing for model and agents param

* Remove redundant condition in get_provider_agents_api_config

LlmProviders.GEMINI.value is literally the string "gemini", so the
second clause of the or was checking the exact same thing as the first.

Co-authored-by: Sameer Kankute <Sameerlite@users.noreply.github.com>

* fix: forward query-param credentials to list/get/delete/versions Gemini agent endpoints

The list_gemini_agents, get_gemini_agent, delete_gemini_agent, and
list_gemini_agent_versions endpoints previously constructed a hardcoded
data dict with no mechanism to pass provider credentials.  Unlike
create_gemini_agent (POST, reads litellm_params_template from body),
these GET/DELETE endpoints gave no way for multi-tenant callers to
supply a per-request api_key or other LiteLLM params.

Fix:
- Add _merge_query_params_into_data() helper that reads query parameters
  from the request and merges them into the data dict without overwriting
  already-set keys (e.g. path params like 'name').
- Support a JSON-encoded litellm_params_template query parameter
  (matching the POST body pattern) as well as flat key=value pairs
  (e.g. api_key=AIza...).
- Apply the helper in all four affected endpoints.
- Add 13 unit tests covering the helper and each endpoint.

Co-authored-by: Sameer Kankute <Sameerlite@users.noreply.github.com>

* fix: pass model=None for managed agent proxy endpoints to prevent agent name polluting data["model"]

Endpoints acreate_agent, aget_agent, adelete_agent, and alist_agent_versions
were passing model=<agent_name> to base_process_llm_request. This caused
common_processing_pre_call_logic to write the agent name into self.data["model"],
which then triggered spurious model-alias mapping, rate-limiting lookups, and
logging tied to a non-existent model deployment.

The agent name is already carried in data["name"] and is passed correctly to
the SDK functions (litellm.interactions.agents.*). There is no reason to also
set model=<agent_name>; the correct value is model=None for all five managed-agent
management routes.

Adds tests/test_litellm/proxy/google_endpoints/test_managed_agents_model_param.py
to verify all five managed-agent endpoints pass model=None.

Co-authored-by: Sameer Kankute <Sameerlite@users.noreply.github.com>

* fix: address greptile P1/P2 review comments

P1 (router.py): Restore fallback/retry support for acreate_interaction
and create_interaction. Both were silently moved to _init_interactions_api_endpoints
(direct call, no fallbacks). Moved them back to _ageneric_api_call_with_fallbacks
so users with configured fallback models keep retry behaviour.

P1 security (agents_endpoints.py): Remove flat query-param credential
path (e.g. ?api_key=AIza...) from _merge_query_params_into_data.
Credentials in URL query strings appear verbatim in server access logs,
CDN edge logs, and browser history. Only the JSON-encoded
litellm_params_template query param (matching the POST body pattern) is
retained.

P2 (interactions/http_handler.py): Extract _BaseHTTPHandler with shared
_handle_error, _sync_client, and _async_client helpers. InteractionsHTTPHandler
now extends _BaseHTTPHandler. The _async_client reads the provider from
litellm_params instead of hardcoding GEMINI.

P2 (interactions/agents/http_handler.py): AgentsHTTPHandler now extends
InteractionsHTTPHandler (which inherits _BaseHTTPHandler) so all shared
HTTP infrastructure is reused rather than duplicated. Removes the
hardcoded LlmProviders.GEMINI from the async client path.

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

* fix: address CI failures from greptile review fixes

- black: format interactions/agents/main.py and utils.py
- tests: update test_gemini_agents_endpoints.py to match new
  _merge_query_params_into_data behaviour (flat credential params are
  rejected; only JSON-encoded litellm_params_template is accepted)
- ci: add test_gemini_agents_endpoints.py to endpoints-and-responses
  shard in test-unit-proxy-db.yml so assert-shard-coverage passes
- tests: add _initialize_managed_agents_endpoints and
  _init_managed_agents_api_endpoints test coverage so router_code_coverage
  passes; also fix TestRouterCreateInteractionRouting to reflect that
  acreate_interaction now correctly routes through
  _ageneric_api_call_with_fallbacks (restoring fallback support)

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

* fix: remove InteractionsHTTPHandler._handle_error override to fix type errors

AgentsHTTPHandler extends InteractionsHTTPHandler and calls
self._handle_error(provider_config=agents_api_config) where
agents_api_config is BaseAgentsAPIConfig. Python MRO resolved _handle_error
to InteractionsHTTPHandler._handle_error which expected BaseInteractionsAPIConfig,
causing 10 mypy arg-type errors in interactions/agents/http_handler.py.

Removing the redundant override lets both classes inherit _BaseHTTPHandler._handle_error
(provider_config: Any) which is structurally correct for both config types.

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

* fix: agent-only interactions and managed agents provider routing

Resolve None custom_llm_provider in agents HTTP client lookup and set
custom_llm_provider on GenericLiteLLMParams for all agent CRUD paths.

Stop mapping agent names to proxy model routing; route interactions
through _init_interactions_api_endpoints with fallbacks only when model
is set. Consolidate duplicate router elif branches for interaction APIs.

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

* Fix greptile review

* test(agents): add unit tests for managed agents SDK and HTTP handler

Adds coverage for the new `litellm.interactions.agents` surface area:
- main.py: sync/async entry points (create/list/get/delete/list_versions),
  provider config lookup, logging-obj helper, async error wrapping
- http_handler.py: every CRUD method (sync + async paths), `_is_async`
  dispatch branches, and provider error mapping through GeminiAgentsConfig
- utils.py: get_provider_agents_api_config for supported / unsupported
  providers

Brings patch coverage on these files from <25% to ~100% so codecov/patch
is satisfied.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

* docs(gemini-agents): fix misleading credential-passing examples in GET/DELETE docstrings (#28293)

The four GET/DELETE endpoint docstrings (list_gemini_agents,
get_gemini_agent, delete_gemini_agent, list_gemini_agent_versions)
documented passing per-request credentials as flat query parameters
(e.g. ?api_key=AIza...). However, _merge_query_params_into_data only
reads the JSON-encoded litellm_params_template query parameter and
intentionally ignores flat params (URL query strings appear verbatim
in access logs, browser history, and Referer headers).

Callers following the documented curl examples would have their
credentials silently dropped and hit auth failures against Gemini.

Update the examples to use the supported JSON-encoded
litellm_params_template query parameter, matching _merge_query_params_into_data's own docstring.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

* refactor(agents): rename provider-agnostic agent response types

Move GeminiAgent{ListResponse,DeleteResult,VersionsResponse} to
provider-neutral names (AgentListResponse, AgentDeleteResult,
AgentVersionsResponse) so the BaseAgentsAPIConfig interface no longer
references Gemini-specific type names.

* fix(gemini-agents): close veria-flagged credential-escalation gaps

Two high-severity findings from the veria-ai PR review are addressed:

1. **api_base override could leak the shared Gemini key**
   GeminiAgentsConfig.validate_environment falls back to GOOGLE_API_KEY /
   GEMINI_API_KEY when no api_key is supplied. Combined with caller-controlled
   api_base on the proxy CRUD endpoints, an authenticated user could redirect
   the outbound request to an attacker-controlled host and capture the
   operator's shared Gemini key from the x-goog-api-key header. The config
   now refuses env-fallback whenever api_base is explicitly overridden.

2. **Managed-agent CRUD exposed to ordinary LLM keys**
   The new /v1beta/agents routes live in google_routes (i.e. llm_api_routes),
   so any non-admin LLM key can reach them. Unlike /v1beta/models/...:
   generateContent these endpoints are NOT model-routed and have no
   model_list-supplied credentials, so env-fallback would let any LLM key
   list / create / delete agents inside the operator's Gemini project. Each
   endpoint now calls _enforce_caller_supplied_provider_key, which requires
   non-admin callers to supply their own Gemini api_key via
   litellm_params_template. Proxy admins keep the env-fallback convenience.

Tests cover non-admin rejection, admin allow-through, the api_base override
guard, and SDK env-fallback when api_base is not overridden.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

* test(router): restore strict assert_called_once_with on interactions default-provider test

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Sameer Kankute <Sameerlite@users.noreply.github.com>
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-05-19 16:02:03 -07:00
Sameer Kankute
3c3d131f01
Day 0 support : Gemini 3.5 Flash (#28268)
* Add day 0 support for gemini 3.5 flash

* Fix pricing

* Fix greptile review

* Fix failing test

* Fix tests

* Fix: revert tool removing logic

* fix greptile and test

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-05-19 15:50:54 -07:00
Yassin Kortam
cde4b1a94d
feat: propagate team_id and team_alias to all child OTEL spans (#28273)
- Add `_set_team_attributes_on_span` helper to stamp team_id/team_alias
  onto any span, ensuring these attributes are not limited to the root
  litellm_request span
- Add `_set_team_attributes_from_kwargs` helper to extract team metadata
  from the standard_logging_object in kwargs and apply them to a span
- Apply team attributes to raw request spans via `_maybe_log_raw_request`
  so downstream consumers can filter traces by team without needing the
  root span
- Apply team attributes to guardrail spans so guardrail activity can be
  correlated to teams in tracing backends
- Apply team attributes to exception logging spans to preserve team
  context during failure paths
- Add comprehensive unit tests covering all new helpers, including edge
  cases where metadata or standard_logging_object is absent

Co-authored-by: Yassin Kortam <yassinkortam@g.ucla.edu>
2026-05-19 15:31:25 -07:00
yuneng-jiang
1480ec698b
chore(ci): bump versions (#28287)
* bump: version 0.4.72 → 0.4.73

* bump: version 1.86.0 → 1.87.0

* uv lock
2026-05-19 15:10:37 -07:00
yuneng-jiang
92de7423ef
fix(tests): replace shut-down gpt-4o-audio-preview with gpt-audio-1.5 (#28281)
* fix(tests): replace shut-down gpt-4o-audio-preview with gpt-audio-1.5

OpenAI shut down gpt-4o-audio-preview on 2026-05-07, so the live audio
calls in test_stream_chunk_builder_openai_audio_output_usage and
test_standard_logging_payload_audio now hard-fail with a model-not-found
error on every PR. The error was not "openai-internal", so the except
block swallowed it and execution fell through to an unbound
completion/response (UnboundLocalError).

Switch both tests to gpt-audio-1.5, OpenAI's recommended successor
(GA, not deprecated, already present in the litellm cost map so the
response_cost assertion still resolves). Also broaden the except to
skip with the real error in the reason instead of crashing, so a
transient upstream blip can't reintroduce the UnboundLocalError.

* fix(tests): narrow audio-test skip to model-not-found, re-raise the rest

Address review feedback: an unconditional skip on any exception would
silently mask a litellm-internal regression in the audio path (broken
param transformation, serialization, bad header) instead of failing CI.

Skip only on the upstream-unavailable class (model_not_found / "does not
exist" / openai-internal) and re-raise everything else, so genuine
regressions still fail loudly. The UnboundLocalError is still fixed
because the handler either skips or raises - it never falls through.

* fix(tests): add budget_exceeded to expected Interaction status enum

Staging added budget_exceeded to the Interaction OpenAPI status enum; the staging merge into this branch picked up the spec change but not the matching test update, so test_status_enum_values failed in CI. Align the test's expected list (exact-match by design) with the live spec.

* fix(tests): mock HTTP fetch in test_img_url_token_counter

The test parameterized a live third-party image URL (blog.purpureus.net) which now 404s, causing get_image_dimensions to fall through to its base64 decode path and crash with 'not enough values to unpack' on every PR run. Mock safe_get with a tiny 1x1 PNG so the URL branch is still exercised without any network dependency.

* fix(tests): swap gpt-4o-audio-preview to gpt-audio-1.5 in test_gpt4o_audio

OpenAI shut down gpt-4o-audio-preview on 2026-05-07, so both live tests in test_gpt4o_audio.py (test_audio_output_from_model and test_audio_input_to_model) hard-fail model_not_found on every PR. Swap the hardcoded model to OpenAI's successor gpt-audio-1.5 (same chat-completions audio surface; already in the litellm cost map). Mirror the narrowed-skip pattern from the prior audio fixes: skip on model_not_found / does-not-exist / openai-internal, re-raise everything else so genuine litellm regressions still fail CI loudly.
2026-05-19 14:48:30 -07:00
ryan-crabbe-berri
727a471ae9
[Refactor] UI - Spend Logs: consolidate filter state and extract components (#25847)
* [Refactor] UI - Spend Logs: consolidate filter state, extract components, remove dead code

- Lift filter state into index.tsx and pass to hook (removes selectedX vars + sync useEffect)
- Move main useQuery into useLogFilterLogic hook (removes isMainQueryEnabled toggle)
- Delete dead RequestViewer component (300 lines, replaced by LogDetailsDrawer)
- Extract LogsTableToolbar component (search, date range, pagination, live tail)
- Extract filter options config to filter_options.ts
- Remove dead code: handleRefresh, handleSelectLog, handleCloseDrawer, formatTimeUnit,
  showFilters/showColumnDropdown state, dropdownRef/filtersRef

* Fix PR feedback: use antd Switch instead of Tremor in new file, fix typo

* Collapse dual-path filtering into single React Query

All 10 filter keys now go through the useQuery — the imperative
performSearch / debouncedSearch / backendFilteredLogs path is deleted.
Filter values are debounced via useDebouncedValue(300ms) before hitting
the query key so text inputs don't fire per-keystroke.

Removed: performSearch, debouncedSearch, backendFilteredLogs,
lastSearchTimestamp, hasBackendFilters, clientDerivedFilteredLogs,
the sort/page/time refetch useEffect, and the filteredLogs chooser memo.

* Clean up remaining smells: remove isFetchingDeferred, internalize selectedTimeInterval, fix circular import

- Remove useDeferredValue/isButtonLoading — pass logsQuery.isFetching directly
- Move selectedTimeInterval into LogsTableToolbar as internal state
- Move PaginatedResponse type from index.tsx to log_filter_logic.tsx

* Fix quick-select dropdown overlapping sidebar

* Fix stale quick-select label after Reset Filters

Move selectedTimeInterval back to parent so handleFilterReset can
reset it to the 24-hour default. The toolbar receives it as a prop.

* refactor useLogFilterLogic tests for controlled-hook + backend-query shape

The hook no longer owns filter state or does client-side filtering — it
receives filters/setFilters as props and drives filteredLogs from a
useQuery over uiSpendLogsCall. Reshape the tests around that contract:
introduce a controlled harness that owns filter state, collapse the 10
per-filter assertions into a single it.each over filterKey → API param,
and drop the client-side passthrough tests (the .min test file and the
"return all logs when no filters" / "empty when logs null" cases) that
no longer correspond to any hook behavior.

* cover new useLogFilterLogic invariants: activeTab gate, filterByCurrentUser fallback, debounce negative, partial merge

Follow-up to the test refactor. Adds coverage for invariants the
refactored hook contract introduced but that the first pass didn't
assert:

- query enablement: expand the single accessToken-null case into an
  it.each over all four credential props (accessToken, token, userRole,
  userID), plus a separate test for activeTab !== "request logs"
- filterByCurrentUser: when true with a blank User ID filter, the
  outbound request carries user_id = userID
- debounce: also assert the negative case — no call in the first 100ms
  after a filter change (first waiting out the initial mount fire)
- handleFilterChange: partial updates merge without clobbering other
  filter keys (protects the spread + default-fill semantics)
- handleFilterReset: calls setCurrentPage(1) alongside restoring
  filters

* fix typo dropping the live-tail banner border

Tailwind silently ignores unknown classes, so border-greem-200 was
leaving the auto-refresh banner with only its bg-green-50 fill and no
outline.

* memoize columns and derived table data in SpendLogsTable

The table's columns array, four-pass data pipeline, and sort-change
handler were all being rebuilt on every parent render. That made every
filter click re-instance all 23 TanStack-Table columns, re-run
filter/reduce/map over all rows, and recreate per-row click closures —
all before the intentional 300ms debounce timer even got a chance to
fire.

Local measurement (40 rows, dev mode):

    filter click → query fires: 1957ms → 1217ms (−38%)

Wrap createColumns in useMemo keyed on sortBy/sortOrder, hoist
onSortChange into a useCallback, and move the searchedLogs /
sessionComposition / sessionRepresentativeMap / filteredData derivations
into a single useMemo keyed on filteredLogs.data + searchTerm.

These were pre-existing issues on main — not regressions from the
hook refactor — but the refactor made them user-visible because the
new query debounce put render cost on the critical path.

* apply dropdown filters instantly, debounce only text inputs

Dropdown selects now bypass the 300ms debounce so a click updates the
table immediately. Text inputs (Key Hash, Error Message, Request ID,
User ID) still debounce. handleFilterReset also clears the pending
debounced value so a half-typed text filter can't re-fire after reset.

* fix(ui/spend-logs): restore lost loading/debounce behavior + cover dropped tests

Regressions from the spend-logs-view refactor:
- debounce the 'Public model / search tool' text filter (was firing a
  backend query per keystroke) via TEXT_FILTER_KEYS
- restore Fetch-button smoothing through table repaint using
  useDeferredValue on the rendered data (explicit staleness)
- show AntDLoadingSpinner during the auth-resolve phase instead of a
  blank screen on first load
- only live-tail-poll while the tab is visible
  (refetchIntervalInBackground: false)
- extract getLiveTailRefetchInterval helper for the poll decision

Tests:
- LogDetailContent: retries display (>0 / 0 / absent), overhead-absent
- log_filter_logic: regression guard that the public-model filter
  debounces; getLiveTailRefetchInterval unit tests
- logs_utils: getTimeRangeDisplay quick-select window labels

* test(ui/spend-logs): cover the cold-load auth-not-ready spinner guard

Asserts SpendLogsTable shows a loading spinner (not a blank screen)
while credentials are unresolved, and renders the table once present.
2026-05-19 10:58:48 -07:00
harish-berri
cff3e0b75e
refactor(bedrock/sagemaker): switch to lazy loading for response stre… (#28189)
Some checks failed
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Has been cancelled
Unit Tests: Security / security (push) Has been cancelled
Unit Tests: Proxy DB Operations / auth-checks (push) Has been cancelled
Unit Tests: Proxy DB Operations / budgets (push) Has been cancelled
Unit Tests: Proxy DB Operations / custom-logging (push) Has been cancelled
Unit Tests: Proxy DB Operations / db-and-spend (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-runtime (push) Has been cancelled
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Has been cancelled
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Has been cancelled
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Has been cancelled
Unit Tests: Proxy DB Operations / key-generation (push) Has been cancelled
Unit Tests: Proxy DB Operations / logging-misc (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-server-core (push) Has been cancelled
Unit Tests: Proxy DB Operations / schema-migration (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-utils (push) Has been cancelled
* refactor(bedrock/sagemaker): switch to lazy loading for response stream shapes

- Replace eager loading of BEDROCK_RESPONSE_STREAM_SHAPE and SAGEMAKER_RESPONSE_STREAM_SHAPE with lazy loading via get_bedrock_response_stream_shape() and get_sagemaker_response_stream_shape() respectively.
- This change optimizes performance by avoiding unnecessary imports and logging warnings unless the response stream shapes are actually needed.
- Update relevant classes and tests to utilize the new lazy loading functions, ensuring consistent behavior across the codebase.

* test(bedrock/sagemaker): add fixtures to clear response stream shape cache

- Introduced `_reset_bedrock_response_stream_shape_cache` and `_reset_sagemaker_response_stream_shape_cache` fixtures to prevent lru_cache leakage between tests in their respective modules.
- Updated tests to utilize these fixtures, ensuring that the response stream shape cache is cleared before and after each test run.
- Added `pytest.importorskip("botocore")` to ensure that tests are skipped if the botocore library is not available.
2026-05-18 23:21:04 -07:00
Sameer Kankute
0290c7bc00
fix(proxy): decode bytes and pass-through SSE for Google-native streamGenerateContent (#27444) (#28213)
* fix(proxy): decode bytes and pass-through SSE for Google-native streamGenerateContent (#27444)

* fix(proxy): address Greptile review on Google-native SSE bytes path

Remove unreachable try/except around SSE pass-through yield and add a
unit test covering pre-formatted SSE bytes, terminator padding, and
non-SSE byte fallback wrapping.

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

---------

Co-authored-by: Tai An <antai12232931@outlook.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-18 22:46:11 -07:00
Sameer Kankute
581882879d
feat(ui): add Interactions API endpoint to playground with SSE streaming (#28156)
* feat(ui): add Interactions API support to playground with streaming

Adds /v1beta/interactions as a selectable endpoint in the UI playground.
Uses SSE streaming (stream=true) and parses content.delta events for real-time output.

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

* fix(interactions): remove forced gemini provider so all providers work via interactions API

Proxy endpoint was hardcoding custom_llm_provider="gemini" before routing,
preventing non-Gemini models from using the litellm_responses bridge.
Also reverts the UI Gemini-only model filter.

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

* fix(interactions): fix streaming for non-gemini providers via bridge

Two bugs in LiteLLMResponsesInteractionsStreamingIterator:
1. content.delta was emitted without "type":"text" in delta dict, so the
   UI type-check always failed and no tokens were displayed
2. First OutputTextDeltaEvent was silently dropped (used to emit content.start
   with empty text); fixed by handling ResponsePartAddedEvent for content.start
   so text deltas go directly to content.delta

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

* undo unrelated changes

* fix(ui): extract model from top-level field in interactions bridge events

Co-authored-by: Yassin Kortam <yassin@berri.ai>

* test(interactions): remove tautological gemini-provider assertion

The test_no_forced_gemini_provider_in_request_data check only asserted
against dict literals it had just constructed, so it always passed and
did not exercise the create_interaction endpoint. The endpoint
deliberately defaults custom_llm_provider to gemini, so the assertion
was also factually incorrect. Drop the misleading test.

Co-authored-by: Yassin Kortam <yassin@berri.ai>

* fix(interactions): use ContentPartAddedEvent and guard interaction.start ordering

- ResponsePartAddedEvent corresponds to reasoning summary parts, not text
  content parts. Use ContentPartAddedEvent which is the event emitted before
  text output deltas (type response.content_part.added).
- Mirror the OutputTextDeltaEvent ordering guard: if interaction.start has
  not been sent yet, emit it first before content.start to honor the
  documented event ordering contract.

Co-authored-by: Yassin Kortam <yassin@berri.ai>

* test(interactions): cover ContentPartAddedEvent ordering and no-op paths

* fix(tests): treat corrupt VCR cassette payloads as cache miss + use gpt-realtime in OpenAI realtime guardrails test

VCR redis persister was raising UnicodeDecodeError on cached payloads that
fail to UTF-8 decode (e.g. legacy entries written by another version of
the persister), failing tests at fixture setup instead of degrading to a
cache miss. Wrap decode+deserialize in a try/except so corrupt cache
entries are treated as CassetteNotFoundError, surfacing the failure via
the existing _record_cache_failure / VCRCassetteCacheWarning path.

OpenAI shut down gpt-4o-realtime-preview-2024-12-17 (and the entire
gpt-4o-realtime-preview family) on 2026-05-07. The live realtime
guardrails integration test now fails with model_not_found instead of
receiving session.created. Point OPENAI_REALTIME_URL at the current GA
model gpt-realtime, and relax the assertion in
test_text_message_blocked_by_guardrail_no_ai_response to also accept the
model's refusal-to-repeat the block message (gpt-realtime declines
verbatim-repeat instructions, which is still a safe outcome since the
original user message was blocked before reaching OpenAI). The
BLOCKED_PHRASE leak check is preserved as a hard invariant.

* fix(tests): migrate realtime + nvidia_nim rerank tests off shut-down upstream models

OpenAI shut down the entire gpt-4o-realtime-preview family (including the
undated alias) on 2026-05-07. The live realtime tests still connected
with that dead alias and failed with messages_received=1 (an error event
'The model gpt-4o-realtime-preview does not exist' instead of
session.created). Point the live OpenAI realtime tests at gpt-realtime,
the current GA realtime model:

- test_openai_realtime_simple.py: get_model() -> gpt-realtime
- test_openai_realtime.py: test_openai_realtime_direct_call_no_intent and
  test_openai_realtime_direct_call_with_intent -> openai/gpt-realtime

Mocked unit tests (test_realtime_query_params_construction,
test_realtime_query_params_use_normalized_model_name) are left as-is:
they never hit the network and assert string plumbing only.

NVIDIA reached end-of-life for the hosted
nvidia/llama-3.2-nv-rerankqa-1b-v2 rerank API on 2026-05-18 with no
published replacement, so the live BaseLLMRerankTest.test_basic_rerank
for nvidia_nim now returns HTTP 410 ('Gone'). NVIDIA's hosted catalog
rotates on a schedule, so swapping in another live model would only
defer the failure. Override test_basic_rerank in TestNvidiaNim to mock
the sync/async HTTP transport (same pattern as
test_nvidia_nim_rerank_ranking_endpoint in this file) and inject a fake
NVIDIA_NIM_API_KEY via monkeypatch. The request/response transformation
and cost calculation stay covered offline.

* test(callbacks): harden flaky proxy callback-leak detector

The proxy callback-leak detector (test_check_num_callbacks_on_lowest_latency)
was failing on this PR with 'abs(85 - 95) <= 4' — a bounded one-time
registration jump caused by switching to latency-based-routing
(+LowestLatencyLoggingHandler, +SlackAlerting). The count then plateaus
under load, so this is pollution from the test's own config update, not a
leak.

Replace the brittle two-sample diff threshold with a sampler that settles
past the deliberate config switch and only flags sustained monotonic
per-type growth, with a terminal-burst confirmation pass for leaks that
would otherwise escape the >=2-interval guard. Normalizes instance
addresses so identical callbacks at different memory locations collapse,
and names the leaking type on failure.

* fix(interactions): preserve first text token when both start events are missing

When OutputTextDeltaEvent arrived before any ResponseCreatedEvent or
ContentPartAddedEvent, the double-fallback path emitted interaction.start
and silently dropped the first delta's text — the second delta's
content.start carried only that chunk's delta, and the first token never
made it to any content.delta event consumed by the UI.

Queue a content.start that carries the first delta's text alongside the
interaction.start emission, and drain pending events before pulling the
next upstream chunk.

* chore(ui): remove unused InteractionOutput/InteractionResponse interfaces

Co-authored-by: Yassin Kortam <yassin@berri.ai>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Yassin Kortam <yassin@berri.ai>
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-05-18 19:55:40 -07:00
Mateo Wang
761c280a6e
fix(deepseek): use native /anthropic/v1/messages endpoint and sanitize tools (#28200)
* fix(deepseek): route messages api through anthropic config

Add a DeepSeek-specific Anthropic Messages config so deepseek/... models use the native messages endpoint and preserve thinking blocks. Strip Anthropic custom tool type markers that DeepSeek rejects while keeping hosted tool types intact.

* fix(deepseek): normalize anthropic messages api base

Handle OpenAI-style DeepSeek api_base values ending in /v1 or /v1/messages by stripping those suffixes before adding the /anthropic messages path.

* chore(deepseek): format messages transformation

* chore(deepseek): add test package markers

* fix(deepseek): tighten anthropic url path check and fall back to DEEPSEEK_API_BASE

Author: mateo-berri <277851410+mateo-berri@users.noreply.github.com>

* fix(tests): normalize smart quotes in realtime guardrail refusal check

gpt-realtime nondeterministically returns refusals with Unicode curly
apostrophes (e.g. 'I’m sorry, but I can’t assist with that.'), but the
safe_markers tuple in test_text_message_blocked_by_guardrail_no_ai_response
only contains straight ASCII apostrophes. The substring match then fails
even though the response is a clear refusal, flipping CI red.

Normalize the AI text to ASCII quotes before the marker check so both
straight and curly variants count as safe outcomes.

* fix(deepseek): drop redundant anthropic v1/messages endswith check

* fix(deepseek): strip /beta suffix in anthropic messages URL normalization

Co-authored-by: Yassin Kortam <yassin@berri.ai>

---------

Co-authored-by: Felipe Rodrigues Gare Carnielli <felipe.gare@hotmail.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Yassin Kortam <yassin@berri.ai>
2026-05-18 18:14:13 -07:00
Mateo Wang
761ab19209
fix(bedrock): sanitize batch metadata to prevent Pydantic ValidationError (#28202)
* fix(bedrock): sanitize batch metadata to prevent Pydantic ValidationError

Proxy guardrail hooks (Model Armor, OpenAI Moderations) and internal
processing inject non-string values (dicts, floats) into the request
metadata. When the Bedrock batch handler passes this metadata directly
to LiteLLMBatch (which inherits OpenAI's Batch Pydantic model with
metadata: Dict[str, str]), Pydantic raises a ValidationError. This
causes the router retry loop to re-submit the same Bedrock job
multiple times before ultimately failing.

Add _get_openai_compatible_batch_metadata() that serializes non-string
values to JSON strings via safe_dumps, skips None values and internal
logging keys, ensuring the response object always validates.

* test(bedrock): add tests for batch metadata sanitization

Covers _get_openai_compatible_batch_metadata: string passthrough, dict/float
serialization, None/internal key exclusion, and LiteLLMBatch compatibility.

---------

Co-authored-by: Noah Nistler <60981020+noahnistler@users.noreply.github.com>
2026-05-18 18:00:18 -07:00
yuneng-jiang
a7f3dbcbe3
test(callbacks): harden flaky proxy callback-leak detector (#28195)
* test(callbacks): TEMP diagnostic probe for callback-leak flake

Hardened leak detector (sample N, flag sustained monotonic per-type
growth, normalize instance addresses) + a temporary always-fail probe
on test_check_num_callbacks_on_lowest_latency that dumps the per-type
series and raw reprs via the JUnit failure message, to settle real-leak
vs bounded-pollution on CCI. Diagnostic block is clearly marked and
will be reverted before the PR.

* test(callbacks): harden proxy callback-leak detector, drop diagnostic

CCI diagnostic confirmed the 85->95 jump is a bounded one-time
registration from the test's own switch to latency-based-routing
(+LowestLatencyLoggingHandler, +SlackAlerting), flat at 95 for 2.5 min
under load — not a leak. Final detector: settle past the deliberate
config/update, sample N times, flag only sustained monotonic per-type
growth, normalize instance addresses, name the leaking type on failure.
Removes the temporary always-fail probe.

* test(callbacks): address review - drop redundant settle, close terminal-burst blind spot

- test_check_num_callbacks: remove leftover sleep(30) before sleep(SETTLE_SECONDS) (60s -> 30s dead wait).
- Add _terminal_suspects + _detect_leaks_confirmed: when monotonic net growth is confined to the final interval (escapes the >=2-interval guard), take one confirmation sample. A real ongoing leak keeps climbing and is flagged; a one-time terminal registration plateaus and is ignored.
2026-05-18 16:39:02 -07:00
Sameer Kankute
73e32a31bf
feat(prometheus): add user_email and user_alias to user budget metrics (#28155)
* feat(prometheus): add user_email and user_alias to user budget metrics

User budget Prometheus gauges now expose human-readable labels alongside
user_id, matching team and API key budget metrics for Grafana filtering.

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

* fix(prometheus): gate user budget email/alias labels behind opt-in flag

Address greptile review: adding labels to existing metrics is a
breaking cardinality change. Gate behind
prometheus_user_budget_label_include_email_alias=True (default: False)
so existing dashboards and recording rules are unaffected.

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-18 16:28:14 -07:00
Sameer Kankute
36c494fdd2
Litellm oss staging (#28161)
* fix(opentelemetry): JSON-serialize dict metadata fields for OTEL span attributes (#27451) (#27455)

Squash-merged by litellm-agent from Anai-Guo's PR.

* feat(dashscope): add embeddings and reranks(qwen3-rerank) support via OpenAI-compatible endpoint (#27508)

Squash-merged by litellm-agent from yimao's PR.

* fix(vertex_ai/gemini): raise BadRequestError when image_url or url fi… (#24550)

Squash-merged by litellm-agent from krisxia0506's PR.

* fix(vertex_ai): raise error on mid-stream 429/error chunks instead of silently swallowing (#23711)

Squash-merged by litellm-agent from krisxia0506's PR.

* fix: raise BadRequestError for file content blocks missing 'file' sub… (#24503)

Squash-merged by litellm-agent from krisxia0506's PR.

* Fix Gemini MIME detection for extensionless GCS URIs (#27278)

Squash-merged by litellm-agent from krisxia0506's PR.

* fix(vertex_ai/partner_models): drop unused vertexai SDK gate from count_tokens (closes #28084) (#28107)

Squash-merged by litellm-agent from voidborne-d's PR.

* feat(chart): add support for autoscaling behavior in HPA (#27990)

Squash-merged by litellm-agent from FabrizioCafolla's PR.

* feat(proxy): add blocked flag to models for pause/resume from the UI (#27927)

Squash-merged by litellm-agent from Cyberfilo's PR.

* fix: pass socket timeouts to Redis cluster clients (#27920)

Squash-merged by litellm-agent from tomdee's PR.

* Fix/cache token (#28009)

Squash-merged by litellm-agent from escon1004's PR.

* fix(deepseek): forward reasoning_content in multi-turn thinking mode conversations (#28080)

Squash-merged by litellm-agent from Divyansh8321's PR.

* fix(guardrails): return HTTP 400 instead of 500 for blocked requests (#27617)

* fix: reset org and tag budgets (#27326)

* reset org budgets

* reset tag budgets

---------

Co-authored-by: Michael Riad Zaky <michaelr@Mac.localdomain>

* fix(ui): omit allowed_routes from key edit save when unchanged (#27553)

* fix(ui): omit allowed_routes from key edit save when unchanged

When a team admin opens Edit Settings on a key with key_type=AI APIs and
saves without changing anything, the UI re-sends the existing allowed_routes
value, which the backend's _check_allowed_routes_caller_permission gate
rejects for non-proxy-admins (LIT-2681).

Strip allowed_routes from the patch in handleSubmit when it deep-equals the
original keyData.allowed_routes. The backend treats absence as "leave alone,"
so no-op saves now succeed for non-admins. Admins explicitly editing the
field still send the new value.

* fix(ui): order-insensitive allowed_routes diff + cover null-original case

Address Greptile review:

- Switch the "is allowed_routes unchanged" check to a Set-based comparison so
  a server-side reorder of the array doesn't register as a user edit and
  re-trigger LIT-2681.
- Add two regression tests: (1) keyData.allowed_routes is null and the form
  is untouched — patch should strip the field; (2) server returned routes in
  a different order than the user originally entered — patch should still
  recognize the value as unchanged.

* chore(ui): strip ticket refs and tighten comments in key edit fix

- Remove internal-tracker references from in-code comments
- Tighten the WHY comment in handleSubmit to two lines
- Drop redundant test-block comments — test names already describe the case

* fix(ui): annotate Set<string> generic in allowed_routes diff to fix tsc

* fix(guardrails): return HTTP 400 instead of 500 for guardrail-blocked requests

GuardrailRaisedException and BlockedPiiEntityError both lacked a
status_code attribute.  When these exceptions reached the proxy
exception handler (getattr(e, 'status_code', 500)), the fallback
defaulted to HTTP 500 — making intentional guardrail blocks
indistinguishable from server errors and causing unnecessary client
retries.

Changes:
- Add status_code=400 (keyword-only) to GuardrailRaisedException
- Add status_code=400 (keyword-only) to BlockedPiiEntityError
- Update _is_guardrail_intervention() to recognize both exceptions
  so downstream loggers record 'guardrail_intervened' instead of
  'guardrail_failed_to_respond'
- Add 6 unit tests for default/custom status codes and getattr pattern
- Strengthen existing blocked-action test with status_code assertion

Fixes #24348

---------

Co-authored-by: Michael-RZ-Berri <michael@berri.ai>
Co-authored-by: Michael Riad Zaky <michaelr@Mac.localdomain>
Co-authored-by: ryan-crabbe-berri <ryan@berri.ai>
Co-authored-by: Krrish Dholakia <krrish+github@berri.ai>

* fix(router/proxy): address Greptile P1+P2 review comments on PR #28161

- router: raise ServiceUnavailableError (503) instead of RouterRateLimitErrorBasic (429)
  when a specifically-addressed deployment is administratively blocked; 429 misleads
  retry-enabled clients into spinning forever against a paused model
- proxy_server: compute get_fully_blocked_model_names() once before both branches in
  model_list() instead of duplicating the call in each branch
- deepseek: upgrade silent debug log to warning when injecting placeholder
  reasoning_content so callers are clearly notified of degraded multi-turn quality
- tests: update two blocked-deployment assertions to expect ServiceUnavailableError

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

* fix: address bug detection findings (cache token order, mutable defaults)

Co-authored-by: Yassin Kortam <yassin@berri.ai>

* fix: address bugs in async pass-through, anthropic cache token detection, rerank tests

- async_get_available_deployment_for_pass_through: enforce blocked check on specific deployments
- cost_calculator: detect anthropic-style usage by attribute presence (not truthiness) to avoid mixing OpenAI cached_tokens into anthropic normalization when read=0
- dashscope rerank tests: pass request to httpx.Response constructions for consistency

Co-authored-by: Yassin Kortam <yassin@berri.ai>

* fix code qa

* fix(vertex_ai/gemini): strip MIME parameters from GCS contentType

GCS object metadata's contentType field can include parameters such as
'text/html; charset=utf-8'. Strip them in _apply_gemini_mime_type_aliases
so downstream get_file_extension_from_mime_type sees a bare MIME type.

Co-authored-by: Yassin Kortam <yassin@berri.ai>

* fix(vertex_ai/gemini): clarify mime-type error message string concatenation

Co-authored-by: Yassin Kortam <yassin@berri.ai>

---------

Co-authored-by: Tai An <antai12232931@outlook.com>
Co-authored-by: Vincent <yimao1231@gmail.com>
Co-authored-by: Kris Xia <xiajiayi0506@gmail.com>
Co-authored-by: d 🔹 <liusway405@gmail.com>
Co-authored-by: Fabrizio Cafolla <developer@fabriziocafolla.com>
Co-authored-by: Filippo Menghi <113345637+Cyberfilo@users.noreply.github.com>
Co-authored-by: Tom Denham <tom@tomdee.co.uk>
Co-authored-by: escon1004 <70471150+escon1004@users.noreply.github.com>
Co-authored-by: Divyansh Singhal <97736786+Divyansh8321@users.noreply.github.com>
Co-authored-by: robin-fiddler <robin@fiddler.ai>
Co-authored-by: Michael-RZ-Berri <michael@berri.ai>
Co-authored-by: Michael Riad Zaky <michaelr@Mac.localdomain>
Co-authored-by: ryan-crabbe-berri <ryan@berri.ai>
Co-authored-by: Krrish Dholakia <krrish+github@berri.ai>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Yassin Kortam <yassin@berri.ai>
2026-05-18 16:27:44 -07:00
Sameer Kankute
477b63c5ea
fix(caching): replay openai/responses bridge cache hits as chat streams (#28158)
* fix(caching): replay openai/responses bridge cache hits as chat streams

When chat completions route through openai/responses, cached ModelResponse
payloads under aresponses keys were deserialized as ResponsesAPIResponse
(500) or re-translated as responses events (empty streaming deltas). Deserialize
chat-shaped cache entries as acompletion and bypass the responses stream iterator
for cached CustomStreamWrapper replay.

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

* fix(caching): map responses bridge call_type for sync vs async stream replay

Co-authored-by: Yassin Kortam <yassin@berri.ai>

* fix: handle ModelResponse cache return in responses bridge and drop dead acompletion check

Co-authored-by: Yassin Kortam <yassin@berri.ai>

* fix(caching): detect chat cache hits via object field before choices fallback

Prefer chat.completion object type over the broad choices-key heuristic so
Responses API cached payloads are not misclassified if their schema changes.

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

* test(caching): cover responses bridge cache-hit paths in CI-tracked test suite

The new bridge cache replay logic in caching_handler.py and the
preformatted-stream guard in litellm_responses_transformation/handler.py
were exercised only by tests under tests/local_testing/, which the
responses-caching-types and misc shards do not run. Codecov flagged the
patch as 29.72% covered.

Add equivalent unit tests under tests/test_litellm/ so the responses,
caching, types, and misc shards execute them and ship their coverage
data to Codecov:

- _is_chat_completion_cached_dict happy/sad paths
- aresponses streaming bridge cache hit -> CustomStreamWrapper
- responses non-streaming bridge cache hit -> ModelResponse
- legacy ResponsesAPIResponse stream + non-stream replay
- _is_preformatted_cached_chat_stream true/false
- completion/acompletion early return on cached ModelResponse
- completion/acompletion skip rewrap on preformatted cached stream

* fix: add negative guard on object field in _is_chat_completion_cached_dict

Co-authored-by: Yassin Kortam <yassin@berri.ai>

* fix(vcr): treat corrupt cassette payloads as cache miss

* test: bump EOL'd NVIDIA rerank and OpenAI realtime models in CI

The NVIDIA hosted rerank endpoint for nvidia/llama-3_2-nv-rerankqa-1b-v2
reached end-of-life on 2026-05-18 and now returns HTTP 410 Gone, breaking
TestNvidiaNim::test_basic_rerank. Switch to nvidia/nv-rerankqa-mistral-4b-v3,
which is still hosted on the NVIDIA API catalog and is already listed in
model_prices_and_context_window.json.

OpenAI also retired the gpt-4o-realtime-preview-2024-12-17 model used by
test_realtime_guardrails_openai (now returns model_not_found). Switch the
realtime test URL to the GA gpt-realtime alias.

Unrelated to the responses-bridge cache fix in this PR, but committing
here to unblock CI per maintainer guidance.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

* test(realtime): switch retired gpt-4o-realtime-preview to gpt-realtime

OpenAI removed gpt-4o-realtime-preview and all its date snapshots on
2026-05-18 (every variant now returns model_not_found), breaking the
live-WebSocket OpenAI realtime tests in CI:

  - test_openai_realtime_direct_call_no_intent
  - test_openai_realtime_direct_call_with_intent
  - TestOpenAIRealtime.test_realtime_connection
  - TestOpenAIRealtime.test_realtime_with_query_params

Point each of those to the current GA alias gpt-realtime (verified live).
Pure unit/mock tests that just assert the string value (e.g. in
test_realtime_query_params_construction and the
test_realtime_query_params_use_normalized_model_name mock) are left
alone since they do not depend on model availability.

Also relax the AI-response assertion in
test_text_message_blocked_by_guardrail_no_ai_response: gpt-realtime
occasionally produces a polite refusal ("I'm sorry, but I can't say
that") when the cancel arrives after the model has already started
generating, which is the expected outcome (no real AI content) but does
not contain the words 'blocked' or 'guardrail'. The primary guardrail
behaviour (guardrail_violation error event + transcript_delta block
message) is still asserted unchanged.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

* test(nvidia_nim): mock rerank live API instead of hitting EOL'd endpoint

NVIDIA reached end-of-life for the hosted nvidia/llama-3.2-nv-rerankqa-1b-v2
rerank API on 2026-05-18 (returns HTTP 410 Gone), and the proposed
replacement nv-rerankqa-mistral-4b-v3 returns HTTP 404 for the CI account,
breaking TestNvidiaNim::test_basic_rerank.

Override test_basic_rerank to mock the HTTP transport (same pattern as
test_nvidia_nim_rerank_ranking_endpoint above) so the request/response
transformation and cost calculation stay covered without depending on
NVIDIA's hosted catalog rotation. The model identifier reverts to the
original llama-3.2-nv-rerankqa-1b-v2 since the request never leaves
the test process.

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Yassin Kortam <yassin@berri.ai>
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-05-18 16:27:06 -07:00