Commit graph

8121 commits

Author SHA1 Message Date
mateo-berri
8b1a19fb02 test: give cost-map guard next() a default so a renamed rule fails with a clear assertion 2026-07-20 16:50:15 -07:00
mateo-berri
23b5b7d199 fix(vertex,azure): model-aware mid-conversation system for Claude /v1/messages
Azure AI Foundry and Vertex AI serve Claude on the first-party Anthropic
Messages contract, which was verified live to be byte-identical to
api.anthropic.com: a leading role:"system" entry in messages is rejected on
every model ("messages.0: use the top-level 'system' parameter"), and a
mid-conversation role:"system" reminder is accepted in place on Claude 4.8+/5
but 400s on Claude 4.7 and older ("role 'system' is not supported on this
model"). This is the same contract Bedrock Invoke already handles model-aware
(PRs #32578/#32831/#32882); Vertex and Azure did no hoisting at all, so a Claude
Code session on an older Vertex/Azure Claude model hard-400s on its reminder
turns, and the only thing sparing 4.8+/5 was that nothing was hoisted

Extract Bedrock's model-gated normalization into the shared
AnthropicMessagesConfig base as _normalize_system_role_messages and call it from
the Vertex and Azure messages configs. Flagged models (4.8+/5) hoist only the
leading run of system entries and keep mid-conversation reminders in place so
the top-level system prefix stays byte-identical and the prompt cache is
preserved; unflagged models hoist every system entry so the request returns a
completion instead of a 400

Add supports_mid_conversation_system to the azure_ai and vertex_ai Claude 4.8+/5
cost-map entries. Exact cost-map hits win over the claude-mid-conversation-system
fallback rule, so without the explicit flag those models would be treated as
unsupported and hoist every reminder, collapsing the prompt cache (the exact
customer regression). A per-provider test guards this so future 4.8+/5 entries
cannot silently miss the flag

Closes the Vertex/Azure gap from the customer RCA
2026-07-20 16:48:29 -07:00
mateo-berri
1e6a34850d fix(router): propagate capability flags to shared backend cost map key 2026-07-20 16:46:30 -07:00
tin-berri
b086cd32a6
Merge pull request #33183 from BerriAI/litellm_mcp_walker_consolidation
refactor(mcp): consolidate exception-tree walkers into one shared faults traversal
2026-07-20 16:43:26 -07:00
Yassin Kortam
e906dbe4a1
perf(streaming): build per-chunk Delta directly instead of setattr/delattr churn (#33992)
Continues #29761. Delta.__init__ set roughly ten attributes through pydantic's
__setattr__ and then deleted the five OpenAI omits on every chunk. Those keys
are extra fields (extra='allow'), so this builds __pydantic_extra__ and
__pydantic_fields_set__ directly after the parent init instead of round-tripping
each field through __setattr__/__delattr__. The resulting __dict__,
__pydantic_extra__, __pydantic_fields_set__ and model_dump output (including
exclude_unset, which the streaming path relies on) are byte-identical to the
previous behavior; a serialization-contract test locks that. A TYPE_CHECKING
block re-declares the extra attributes with their concrete types so type
checkers still see delta.content and friends.

Co-authored-by: Jay Gowdy <jgowdy@godaddy.com>
2026-07-20 16:40:43 -07:00
Tin Chi Lo
8e0d3af81b Merge origin/litellm_internal_staging into litellm_mcp_walker_consolidation
Staging now contains #33153, whose final rounds made _extract_upstream_auth_failure a thin delegate
to upstream_auth_challenge and introduced the response-level iterator this branch predates. The
resolution completes the consolidation both branches were converging on: iter_exception_tree
(faults/traversal.py) is the one tree walk, _iter_upstream_responses is rebuilt on top of it instead
of carrying a second copy of the traversal, the manager keeps the delegate, and the semantic filter
port from this branch stands. Test conflicts were append-append and both sides are kept
2026-07-20 16:29:24 -07:00
Mateo Wang
fa59cfa6da
Merge pull request #34041 from BerriAI/litellm_lit4544_shared_key_model_info_leak
fix(router): stop custom model_info leaking onto shared backend cost map key
2026-07-20 19:22:57 -04:00
tin-berri
f61e16c734
Merge pull request #32259 from BerriAI/litellm_mcp_v2_client_credentials
feat(mcp): migrate client_credentials (M2M) onto the v2 resolver arm
2026-07-20 16:22:14 -07:00
tin-berri
43e4af73f0
Merge pull request #33631 from BerriAI/litellm_lit4517_messages_mcp_gateway
feat(mcp): support MCP servers on the Anthropic /v1/messages API
2026-07-20 16:22:03 -07:00
tin-berri
f9b10eb296
Merge pull request #33886 from BerriAI/litellm_lit4582_cache_control_present
fix(anthropic): only inject cache_control when the request carries none
2026-07-20 16:20:55 -07:00
yucheng-berri
432954a2ab
fix(cache): make in-memory and disk cache increments atomic (#34013)
Some checks failed
CodSpeed Benchmarks / benchmarks (push) Waiting to run
GitHub Actions Security Analysis / zizmor (push) Waiting to run
LiteLLM Rust / rustfmt, clippy, test (push) Has been cancelled
* fix(cache): make in-memory and disk increments atomic

* refactor(cache): narrow in-memory increment lock scope

* fix(cache): address follow-up review on increment tests/types

* fix(cache): refresh atomic increment coverage

* test(cache): widen increment race window with non-zero _SlowInt seed

The zero seed was falsy, so InMemoryCache.increment_cache's `get_cache(...) or 0`
and DiskCache.get_cache's truthiness guard both discarded the _SlowInt before
__add__ could run, leaving the sleep-based window-widening inert. Seed a non-zero
value and return _SlowInt from __add__ so the sleep fires on every read-modify-write
in both backends, making the concurrency regression deterministic.

* test(cache): cover InMemoryCache.async_increment delegation

Add a focused async test asserting async_increment accumulates through the
locked sync path, exercising the previously uncovered delegation line.

---------

Co-authored-by: Emerson Gomes <emerson.gomes@thalesgroup.com>
2026-07-20 15:51:01 -07:00
mateo-berri
3f712e3fde fix(router): stop custom model_info leaking onto shared backend cost map key 2026-07-20 15:49:19 -07:00
Mateo Wang
885a637c6e
Merge pull request #34037 from BerriAI/litellm_fix_model_info_unpack_flake
test(proxy): make model_info endpoint tests hermetic to kill an order/merge-skew flake
2026-07-20 18:28:13 -04:00
mateo-berri
c2bd8699be fix(proxy): require admin opt-in for request-body bedrock_tags
Caller-supplied bedrock_tags land as AWS resource tags under the proxy's
AWS identity, letting an authenticated caller forge ownership or
cost-allocation labels. Add bedrock_tags to _BANNED_REQUEST_BODY_PARAMS
so per-request tags need general_settings.allow_client_side_credentials
or configurable_clientside_auth_params on the deployment, matching the
aws_bedrock_project_id precedent. Deployment-level bedrock_tags in
litellm_params are unaffected.

Also stop an explicit empty bedrock_tags list in litellm_params from
falling through to optional_params
2026-07-20 15:00:41 -07:00
mateo-berri
249e1f8ce7 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_lit_4162_bedrock_batch_tags
# Conflicts:
#	tests/test_litellm/test_router.py
2026-07-20 14:45:02 -07:00
mateo-berri
44604620a4 test(proxy): make model_info endpoint tests hermetic to kill an order/merge-skew flake
The model_info / get_model_info_with_id endpoint tests drove refactored
endpoints with bare, unspec'd MagicMock routers and models. Because the
mocks were unspec'd, any attribute or method the (refactored) endpoints
newly read auto-materialized a child MagicMock, and whether that child
was reached depended on process-global state (premium_user, and the real
get_available_models_for_user chain reading litellm globals) that sibling
tests in the same xdist worker mutate. When reached, the MagicMock either
unpacked to empty (a, b = mock.method() -> 'not enough values to unpack
(expected 2, got 0)') or leaked into RouterModelInfo(**model_info) and
failed Pydantic str validation. Pass in isolation, fail under xdist.

The original TestModelInfoEndpoint failure (#33807 CI) was the same class
surfaced by merge skew: #33721 added a get_configured_token_limits unpack
to create_model_info_response, and CI's merge commit ran that against the
un-updated bare-mock test before the #33742 band-aid landed.

Fix (test-only, no product change):
- TestModelInfoEndpoint: mock the real seam (get_available_models_for_user),
  configure the router methods the endpoint actually calls, return a real
  Deployment, and drop the dead proxy_server.get_key_models/get_team_models/
  get_complete_model_list patches the refactor had stranded.
- TestGetModelInfoWithIdBlocked: spec the model mock so unset enterprise
  columns read as None instead of child MagicMocks.
- test_ProxyConfig_get_model_info_with_id_missing_model_id_raises: pin
  premium_user so the asserted AttributeError no longer flips with the
  ambient license global.
2026-07-20 14:28:09 -07:00
mateo-berri
f5dc1a3010 test(router): prove request-level bedrock_tags override deployment-level tags for acreate_batch 2026-07-20 14:26:35 -07:00
devin-ai-integration[bot]
0b8817afbb
perf(bedrock): audio transcription via rust core (py->rust bridge) (#33990)
* feat(bedrock): add audio transcription via Converse with py->rust bridge

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

* fix(ci): exclude rust transcription rollout flag from docs check

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

* fix(bedrock): await rust/python fallback in async transcription dispatch

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

* test(bedrock): cover audio transcription rust dispatch

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

* refactor(bedrock): route audio transcription through rust

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

* refactor(bedrock): move rust transcription dispatch out of main

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

* test(bedrock): include rust transcription coverage shard

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

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
2026-07-20 14:09:41 -07:00
Tin Chi Lo
051cdd1dce fix(cli): tolerate an unreadable prior config when carrying the autoroute key forward
_load_persisted_master_key documents leniency on any unreadable prior
state but only caught parse errors; a permissions failure or non-UTF-8
bytes in config.yaml crashed configure instead of skipping the
carry-forward. Catch OSError and UnicodeDecodeError too and pin the
undecodable-file case with a regression test.
2026-07-20 13:25:25 -07:00
Tin Chi Lo
0f62ff41b6 fix(cli): stable port and persisted master key for lite autoroute up
lite autoroute up minted a fresh master key and picked a fresh OS-ephemeral
port on every run, so any client configured against one session (an
already-open Claude Code session, a hand-configured script) broke on the
next run. The port is now a stable default (5483, overridable with --port)
that refuses loudly when busy or when 4000 is requested, since proxy_cli
silently rebinds a busy 4000 to a random port. The master key is minted
once, persisted in the generated config.yaml, reused by every later up,
and carried forward when configure regenerates the config.
2026-07-20 13:08:12 -07:00
Tin
d2342296ae fix(mcp): keep the minted M2M bearer authoritative over injected Authorization headers
_resolve_v2_auth dropped the resolved client_credentials auth when extra_headers
already carried Authorization (MCPJWTSigner, static_headers), so the upstream got
the injected header instead of the minted token and the one-shot 401 refetch was
lost. M2M now joins token_exchange and authorization_code in the authoritative
set; the conflicting header is dropped
2026-07-20 12:00:12 -07:00
Tin
8bfd8baab8 fix(mcp): remember the rotated M2M bearer for later requests in the session
The auth object is the httpx client's auth for the whole MCP session; after a
401 recovery it kept sending the rejected token first, burning a 401 round trip
and the single retry on every subsequent call
2026-07-20 11:54:47 -07:00
Tin
5b64239afc fix(mcp): skip the M2M cache write when the token is already expired at mint
An expires_in of zero or below computes a ttl of 0; the entry could never be
served but still occupied a slot in the bounded backend, where it could evict
a live token. The mint still serves the current request and the next get
re-fetches under the per-server lock
2026-07-20 11:54:47 -07:00
Tin Chi Lo
7705f0b975 fix(mcp): bound the M2M lock dict and cap short-lived token TTL at real expiry
Greptile P2s: the per-server lock dict now evicts its oldest entry past
max_locks so ephemeral server ids (REST tools preview) cannot grow it
unbounded, and the min-cache floor is capped at the token's actual lifetime so
an expires_in below the skew is never served past expiry
2026-07-20 11:54:47 -07:00
Tin Chi Lo
4b1c9d4498 test(mcp): update _create_mcp_client graft tests for migrated M2M arm
The graft test pinned the pre-migration contract (M2M defers to v1). Replaced
with two tests pinning the new one: a complete-config M2M server resolves via
the v2 arm into ClientCredentialsBearerAuth, and an incomplete-config server
fails closed with a 500 misconfigured naming the missing grant fields
2026-07-20 11:54:47 -07:00
Tin Chi Lo
d4035a07c7 feat(mcp): migrate client_credentials (M2M) onto the v2 resolver arm
Replaces the not_implemented stub with a live arm: ClientCredentialsTokenSource
mints and caches the M2M token (rotation-aware identity key, expires_in-driven
TTL, audience and token_endpoint_auth_method support) and
ClientCredentialsBearerAuth retries an upstream 401 exactly once with a freshly
minted token. to_server_spec owns oauth2_flow=client_credentials servers and
fails closed on incomplete grant config instead of connecting unauthenticated
2026-07-20 11:54:47 -07:00
tin-berri
ce88999ed1
Merge pull request #33182 from BerriAI/litellm_lit3637_session_token
feat(mcp): identity-only session tokens for the gateway DCR front door
2026-07-20 11:24:17 -07:00
Yuneng Jiang
b7e7fab741
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/ticket-implementation-review-409e1d 2026-07-20 10:04:46 -07:00
Yuneng Jiang
479e997eed
feat(spend): raise /spend/logs/v2 page_size cap to 1000
Clients exporting large spend-log ranges were forced into 100-row pages,
which meant a bounded COUNT plus an increasingly deep OFFSET scan per
request. Larger pages reduce both the request count and the cumulative
OFFSET cost for the same result set.

The handler already excludes the heavy JSON columns (messages, response,
proxy_server_request) from the paginated SELECT and bounds the COUNT via
SPEND_LOGS_PAGINATION_COUNT_CAP, so per-row cost does not grow with page
size. 1000 matches the ceiling already used by the user and user-agent
analytics list endpoints.
2026-07-20 09:55:38 -07:00
devin-ai-integration[bot]
3fcd19d7ad
fix(fireworks_ai): restore Content-Type application/json header (fixes 415) (#33929)
* fix(fireworks_ai): set Content-Type application/json in validate_environment

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

* refactor(fireworks_ai): delegate chat validate_environment to OpenAIGPTConfig

Instead of re-adding the JSON Content-Type default inside FireworksAIMixin,
FireworksAIConfig now delegates header construction to OpenAIGPTConfig and only
layers the Fireworks-specific x-session-affinity header on top, so the
Content-Type default can no longer drift away from the OpenAI base and reintroduce
the 415.

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

* test(fireworks_ai): cover missing api key error path in chat validate_environment

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

---------

Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-20 09:52:35 -07:00
yucheng-berri
bd44c9e305
fix(langfuse): send v4 ingestion header for otel callback (#33907)
Some checks are pending
CodSpeed Benchmarks / benchmarks (push) Waiting to run
LiteLLM Rust / rustfmt, clippy, test (push) Waiting to run
GitHub Actions Security Analysis / zizmor (push) Waiting to run
* fix(langfuse): send v4 ingestion header for otel callback

* refactor(langfuse): inline otel ingestion header literals

* test(langfuse): assert v4 ingestion header on dynamic key config paths

* style: apply ruff format to langfuse otel header changes

* chore(langfuse): drop stale development annotation on json import

---------

Co-authored-by: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com>
2026-07-18 20:36:51 -07:00
tin-berri
ac1b4b35ce
Merge pull request #33174 from BerriAI/litellm_lit3637_aggregate_dcr
feat(mcp): always-on aggregate gateway DCR discovery front door
2026-07-18 19:25:26 -07:00
devin-ai-integration[bot]
cc45d18e9c
feat(complexity-router): add return_raw_model_name toggle for response model field (#33875)
* feat(complexity-router): optionally return raw model name

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

* test(proxy): restore asyncio import

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

* chore(tests): preserve staging asyncio import

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

* test(proxy): drop unused local asyncio import

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

* feat(dashboard): add complexity router raw model toggle

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

* refactor(complexity-router): move metadata key constant to constants.py

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

* style(proxy-tests): preserve module spacing

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

---------

Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-18 19:24:56 -07:00
Tin Chi Lo
a22182f3c0 feat(mcp): add jti claim for per-mint session token uniqueness
Some checks failed
LiteLLM Rust / rustfmt, clippy, test (push) Has been cancelled
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled
2026-07-18 19:15:47 -07:00
Tin Chi Lo
7390f29b23 feat(mcp): identity-only session tokens for the gateway DCR front door 2026-07-18 19:15:47 -07:00
devin-ai-integration[bot]
f2e340cf2b
feat(rust): port BaseAWSLLM auth (credential resolution + SigV4) to litellm-core as a base provider (#33888)
* feat(rust): add feature-gated Bedrock AWS auth

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

* refactor(rust): move Bedrock auth into core

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

* fix(rust): fall through caller identity lookup errors

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

* test(rust): add live Bedrock proof and CI coverage

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

* refactor(rust): share in-memory cache with Bedrock auth

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

* fix(rust): preserve web identity credential expiry

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

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
2026-07-18 19:12:00 -07:00
yuneng-jiang
f17a6ce009
Merge pull request #33903 from BerriAI/litellm_/quirky-heyrovsky-faafca
fix(proxy): treat malformed cost-map token limits as absent on /v1/models
2026-07-18 19:08:03 -07:00
Yuneng Jiang
ab02127b50
fix(proxy): treat malformed cost-map token limits as absent on /v1/models
create_model_info_response cast cost-map max_input_tokens / max_output_tokens
with unguarded int(). The surrounding try/except covers only the get_model_info
lookup, so a deployment whose model_info carries a non-numeric limit (e.g.
"128,000" or an empty string) raised inside the per-model listing loop and
failed the entire GET /v1/models and /models response with a 500, taking healthy
deployments down with it. A deployment's model_info is registered into
litellm.model_cost verbatim, so the malformed value reaches the cost map and not
just the router index.

Router.get_configured_token_limits already coerced this safely for the
deployment path; the cost-map path was missed, so the two together still
regressed. Both now share coerce_token_limit in litellm_core_utils, which
returns None for a malformed value so the listing omits that one limit instead
of failing, matching the graceful degradation the endpoint had before the
cost-map switch.
2026-07-18 18:56:24 -07:00
devin-ai-integration[bot]
7891388975
feat(rust): 1:1 port of OpenAI Responses API WebSockets to litellm-rust (#33849)
* feat(rust): add OpenAI Responses WebSocket gateway

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

* test(rust): cover Responses WebSocket gateway behavior

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

* fix(rust): align Responses WebSocket parity

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

* feat(rust): expose Responses WebSockets through bridge

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

* fix(rust): reject non-openai responses deployments early

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

* fix(rust): align Responses WebSocket bridge semantics

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

* refactor(rust): move Responses instrumentation into core

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

* fix(rust): preserve Responses WebSocket callback dispatch

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

* build(deps): authorize vcrpy and locust licenses in liccheck

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

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
2026-07-19 01:55:35 +00:00
Tin Chi Lo
8a57067d4a refactor(mcp): scope the root-path helper to the aggregate front door only
The SERVER_ROOT_PATH fix for the per-server pass-through challenge belongs with its sibling
in exceptions.py (both fabricate a per-server resource_metadata URL and both omit the root
segment), and both are pre-existing paths unrelated to the aggregate discovery this PR adds.
Reverting the server.py change keeps this PR to the aggregate front door and avoids leaving
the two per-server challenge builders inconsistent; the per-server root-path fix lands as its
own change covering both sites.
2026-07-18 18:49:52 -07:00
Tin Chi Lo
70bc9523ba test(mcp): isolate MCP discovery tests from a leaked SERVER_ROOT_PATH
tests/test_litellm/proxy/test_custom_proxy.py sets SERVER_ROOT_PATH at import time (its app
mounts under a custom path) and never restores it, so in a shared shard the value leaks into the
process. The discovery routes and the 401 challenges now read SERVER_ROOT_PATH to path-insert it
where they previously ignored it, so a leaked value rewrites every resource_metadata URL and the
exact-URL assertions in the delegate, pass-through, and aggregate challenge tests fail depending
on shard order

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

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

The 401 challenges built the resource_metadata URL as {base}/.well-known/oauth-protected-resource/mcp
with no SERVER_ROOT_PATH segment, but the routes are registered with the path-inserted root segment,
so a proxy mounted under a sub-path pointed DCR clients at a URL that 404s. Both the aggregate
challenge and the pre-existing per-server pass-through challenge now derive the path from one
well_known_root_suffix helper that the route registrations also use, so the advertised URL cannot
drift from the served route
2026-07-18 18:45:18 -07:00
Tin Chi Lo
14b1647cd6 refactor(mcp): make the aggregate DCR front door always-on, remove the mcp_gateway_dcr flag
The flag guarded no breaking change: the aggregate discovery lives at new /mcp-suffixed
routes, the challenge only fires at aggregate scope, and the authorize/token/register/admission
arms self-gate on the llm_dcrc_/llm_session_ prefixes. Bare-origin and per-server discovery are
left exactly as they were, and a server literally named mcp keeps its own discovery via
disambiguation, so turning it on for everyone changes nothing about existing flows.
2026-07-18 18:45:18 -07:00
Tin Chi Lo
6d7a80ac75 feat(mcp): aggregate gateway DCR discovery front door behind mcp_gateway_dcr 2026-07-18 18:45:18 -07:00
devin-ai-integration[bot]
a198e0b0ca
feat(messages): route native Anthropic /messages through Rust behind LITELLM_RUST env var (#33848)
* feat(messages): route native Anthropic /messages through Rust behind RUST env var

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

* test(docs): exclude RUST rollout flag from env-key documentation check

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

* refactor(messages): rename RUST rollout env var to LITELLM_RUST

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

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
2026-07-18 18:27:20 -07:00
tin-berri
3f3295b33f
feat(spend): track prompt compression saved tokens in daily spend aggregates (#33810)
* feat(spend): track prompt compression saved tokens in daily spend aggregates

Native compression interception now records tokens_before/after/saved into the
request litellm_metadata so savings land in the SpendLog metadata JSON under a
typed compression_savings key. A single normalizer
(extract_compression_saved_tokens) sums that key with Headroom guardrail
tokens_saved; the two writers are disjoint and run at different stages, so
summing never double-counts. The spend-log redactor now preserves purely
numeric compression stats inside guardrail_response so Headroom savings
survive the store_prompts_in_spend_logs=false default. compression_saved_tokens
is threaded through BaseDailySpendTransaction, queue aggregation, the daily
upsert blocks, a new BigInt column on all six daily spend tables, and the
daily activity read path (SpendMetrics, DailySpendMetadata, raw-SQL rollups)

* fix(spend): normalize legacy guardrail shapes and float token stats in compression savings reader

* feat(spend): aggregate compression and prompt caching dollar savings in daily rollups

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

* test(spend): update daily spend aggregation fixtures for savings columns

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

* feat(ui): add Cost Optimization dashboard page

New left-nav Cost Optimization page under Observability that surfaces money saved by prompt compression and prompt caching. It reads the daily activity rollup (userDailyActivityCall / get_daily_activity) and never scans SpendLogs, so it stays fast at 1M+ rows.

Renders a Total saved card, per-driver Compression and Prompt caching cards, a savings-over-time area chart, and a savings-by-driver donut, all aggregated in memory from the per-day metrics.compression_savings_spend and metrics.prompt_caching_savings_spend fields.

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

---------

Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-18 17:47:54 -07:00
Tin Chi Lo
e0648571ed fix(anthropic): carry the stand-down judgment inside written-back injection points 2026-07-18 17:11:03 -07:00
Devin AI
118b47a8a3 fix(proxy): keep tag drain inside try and cover requeue paths with tests
Move the destructive daily-tag Redis drain back inside the try so a Redis
read failure still releases the pod lock via the finally block, and use a
covariant Mapping for the restore signature. Add regression tests for the
daily-tag requeue-on-failure/no-requeue-on-success paths and the RedisError
swallow branch in restore_transactions_to_redis.
2026-07-18 23:45:23 +00:00
Tin Chi Lo
0268d01516 fix(anthropic): only inject cache_control when the request carries none 2026-07-18 16:44:27 -07:00
Devin AI
bde00952b6 fix(proxy): requeue Redis spend buffer transactions when DB commit fails
The Redis transaction buffer leader drains the spend buffers with a
destructive lpop before committing to the database. When the DB commit
failed after exhausting retries, the popped transactions were only logged
and then lost, permanently undercounting key/user/team/org/end-user/
team-member/tag/agent and daily spend after a database outage.

Track each popped category and re-push the ones that were not committed
back to their Redis buffers so a later scheduler tick retries them.
Categories that already committed are not re-queued, so their spend is not
double-counted. The daily tag spend path gets the same treatment.
2026-07-18 23:19:21 +00:00