Commit graph

168 commits

Author SHA1 Message Date
Refael Iliaguyev
e065a2575b
fix(proxy): send a real error event when a /v1/messages stream fails (#41826)
* fix(proxy): send a real error event when a /v1/messages stream fails

When a stream failed halfway through, the proxy wrote the error as a plain
`data: {"error": ...}` line with no `event:` in front of it. Anthropic clients
pick stream events by that name, so they skip the line and the request looks
like it simply stopped with nothing in it.

Write the failure as an `event: error` frame with Anthropic's own payload, and
take the error type from the status code

* fix(proxy): use the shared Anthropic error mapping for the stream error frame

The first pass added a third copy of the status to error-type table, and it
disagreed with the documented one: 529 came out as `api_error` rather than
`overloaded_error`, and 413 as `invalid_request_error` rather than
`request_too_large`, which hides the two failures a client can actually act on.

Drop that copy and put the frame builder next to the table litellm already
keeps in anthropic_interface/exceptions. The bridged adapter path was building
the same frame inline, so it uses the shared one now too

* fix(proxy): seal a torn SSE frame before the /v1/messages error event

An upstream that drops mid-frame leaves the client inside an open event,
so the error frame that follows is glued onto the torn data line and the
Anthropic SDK raises a JSON decode error instead of an APIStatusError.
Close the open frame with a ping event the SDK skips before writing the
error event, and add the e2e stream-cut edge with Bedrock, Anthropic
boundary, and Anthropic mid-frame legs.

* fix(proxy): keep the SSE tail unchanged on a chunk that is not text

A serializer that hands a dict or model object through as-is has no bytes
the frame tail can learn from, so advance_sse_tail leaves it alone instead
of slicing it.

* fix(proxy): answer a /v1/messages stream that fails before its first byte as a JSON error carrying its status

* test: move the Anthropic error frame tests into tests/unit

* test(e2e): cut the upstream stream only after content has been relayed

* test(e2e): carry split SSE lines across chunks and always tear a data line mid-frame

* test(e2e): find the next data line across a chunk boundary before tearing it

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-09-25 15:36:35 -07:00
devin-ai-integration[bot]
601c75a475
fix(proxy): record streamed /v1/responses container ownership before the response.completed frame (#43140)
* test(e2e): cover Azure code_interpreter container files by native id with a service-account key

* test(e2e): require the code_interpreter tool, skip at collection, and scope the container call timeout

* fix(e2e): fail the containers suite when the Azure credentials are missing instead of skipping

* fix(proxy): record streamed /v1/responses container ownership before the response.completed frame

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-09-25 13:45:43 -07:00
devin-ai-integration[bot]
9915df6875
test(e2e): cover Azure code_interpreter container files by native id with a service-account key (#43122)
* test(e2e): cover Azure code_interpreter container files by native id with a service-account key

* test(e2e): require the code_interpreter tool, skip at collection, and scope the container call timeout

* fix(e2e): fail the containers suite when the Azure credentials are missing instead of skipping

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-09-24 22:16:18 -07:00
devin-ai-integration[bot]
020e5dee9b
fix(anthropic): keep the replayed prefix byte-stable for preserved thinking on chat completions (#42630)
* feat(anthropic): placement policy for mid-conversation system messages

Pure functions over the OpenAI-format message list: split off the leading
system run, keep later system messages as role=system at a placement Anthropic
accepts on models flagged supports_mid_conversation_system (after a user turn,
before an assistant turn or the end, never adjacent), and convert them to user
turns in place elsewhere, keeping tool_result first in a merged user turn.

* fix(anthropic): keep mid-conversation system out of the chat completions system prompt

translate_system_message hoisted every role=system message, at any index, into
the top-level system block. On a conversation carrying a mid-session reminder
that rewrites the cached prefix, so the provider re-bills the whole history at
cache-write pricing on every turn (#36559). #36968 fixed this on /v1/messages;
the chat completions path, shared by first-party Anthropic, Vertex, Azure AI
and Bedrock Invoke, still hoisted.

Only the leading system run becomes the system prompt now. Later system
messages go through the placement policy, and anthropic_messages_pt emits a
system message instead of rejecting the role. The caller's message list is no
longer mutated. Tests pin the two-turn prefix invariant across all four chat
configs and both flag states.

* refactor(anthropic): single-source the converted system note

The /v1/messages pass-through and the chat completions path must prefix a
converted system turn with the same operator note.

* test(e2e): prove the prompt cache survives a mid-conversation system reminder on chat completions

Same priming and assertions as the /v1/messages cases, through
/v1/chat/completions with OpenAI-format messages, for first-party Anthropic
and Bedrock Invoke on a flagged (Opus 4.8) and an unflagged (Haiku 4.5) model.
The reminder sits between the assistant turn and the next user turn, the shape
OpenAI-style agent frameworks send, which is the placement the chat path has
to translate.

* test(anthropic): cover the cache_control rebuild shapes and type the test helpers

Codecov flagged the 5m ttl branch and the empty-system path of the wire
builder; both now have a test. Greptile asked for full typing on the new
test helpers.

* refactor(anthropic): read the mid-conversation flag through a public supports_ helper

supports_mid_conversation_system joins the other supports_* helpers in
litellm.utils, so the chat transformation stops importing the private
_supports_factory.

* chore(typing): declare the mid-conversation type aliases with TypeAlias

The Final sweep tightened LIT010, which exempts TypeAlias declarations but
counts a bare alias assignment as an unannotated binding.

* fix(anthropic): let add_code_execution_tool take the pass-through message union

The translator now emits role=system inside messages for models that accept it,
so anthropic_messages_pt returns the pass-through union. add_code_execution_tool
still declared the narrower user/assistant union while only ever reading
content, so upstream's strip_advisor_blocks_from_messages call in between made
the mismatch visible to the type checker.

* fix(bedrock): keep mid-conversation system messages in place on converse path

* fix: ruff format + multi tool_result order + regression test

* fix: satisfy type-discipline gate + update osv ignore for mlflow PYSEC-2026-3865

* fix(bedrock): restore role narrowing in hoisted system loop for basedpyright budget

* test(bedrock): cover mid-conversation system conversion branches

- non-dict guard in _opens_with_tool_result
- in-place conversion without tool context
- str/list cache_control preservation in mid-conversation path
- drop unreachable non-system guard in hoisted loop

* Place type-discipline suppressions on the lines the gate scans

* Narrow hoisted loop to system role so basedpyright sees the right TypedDict

* fix(anthropic): place mid-conversation system runs by their neighbours only

A run after an assistant turn now slides behind the user turn that
immediately follows it, and a run that ends the array or precedes an
assistant turn becomes a user turn in place. No later message can move
an earlier run, so a client that replays the conversation with more
turns appended sends a byte-identical prefix and preserved thinking
blocks keep their binding

* refactor(bedrock): share the converted system note with the anthropic module

Converse imports CONVERTED_SYSTEM_NOTE instead of carrying its own copy
of the same text, and the reordering helpers lose their comments

* test: pin the replayed request prefix across preserved-thinking turns

One test per audited feature, through the real entrypoint: the chat
transformations for anthropic, bedrock invoke, vertex and converse, the
modify_params dummy tool result, dotprompt with unchanged variables, and
Presidio masking against an in-process fake. Each serializes system,
tools and the earlier messages of turn N and N+1 and asserts they match.
The e2e mid-conversation system test imports its content blocks from
models.py again and is marked provider_live

* fix(anthropic): move mid-conversation system placement into prompt_templates

The prompt factory imported the placement helper from the Anthropic provider
package, whose common_utils reads a factory constant at import time, so loading
the factory first raised ImportError. The module now sits next to
anthropic_messages_pt and every consumer imports core utils

A user turn with content [] or None puts no block on the wire, so a system run
anchored to it landed first in messages or behind an assistant turn. Such a run
now converts in place; empty strings and empty text blocks still anchor because
the factory fills them with a placeholder

* fix(anthropic): anchor system messages only on user turns that reach the wire

* fix(bedrock): type the converse system-message helpers over the message TypedDicts

* fix(anthropic): read replayed pydantic messages in the Converse helpers and convert a system run whose assistant follower sends nothing

A history that replays the previous turn as the litellm.Message object
was invisible to the Converse system-message helpers, so a mid-conversation
system stayed between a tool call and its result or reached Converse as
role: system. The helpers now read fields through the shared
message_field and parts_of accessors and drop the local role predicate.

Flagged placement anchored a system run on any assistant follower, but
anthropic_messages_pt drops an assistant turn that puts no block on the
wire (content None, an empty list, an unsigned thinking part), so the
system landed directly before the next user turn, which Anthropic
rejects. Such a run now converts in place. An empty or whitespace text
turn still anchors, since the converter pads it with a placeholder.

* fix(anthropic): treat bridged encrypted reasoning as a vanishing assistant turn for system placement

An assistant turn whose only blocks carry Responses API encrypted reasoning is
dropped by anthropic_messages_pt, so a mid-conversation system run anchored
before it landed directly before the next user turn. The unsignable-thinking
predicate now lives in common_utils and both the factory and the placement
policy consult it.

* fix(anthropic): let an inline thinking part hide separate thinking_blocks in system placement

anthropic_messages_pt skips an assistant turn's separate thinking_blocks as soon
as its content list carries an inline thinking or redacted_thinking part, so a
turn whose inline part is unsigned puts nothing on the wire even when the
separate block is signed. The placement policy now mirrors that rule.

---------

Co-authored-by: Shifat Islam Santo <shifatislamsanto764@gmail.com>
Co-authored-by: ege-arhan <egearhany@gmail.com>
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-09-24 22:01:20 -07:00
devin-ai-integration[bot]
57bead9842
test(e2e): pin end-user and tag attribution from Codex-style headers on /v1/responses (#43093)
* test(e2e): pin end-user and tag attribution from Codex-style headers on /v1/responses

Codex CLI has no body field for the end user, so its config.toml
http_headers attach x-litellm-customer-id or x-litellm-end-user-id plus
x-litellm-tags to every /v1/responses call. The proxy already honors
those headers on the Responses route, but nothing in the e2e stack
pinned it. The new case sends that exact wire shape with each standard
customer header and fails unless the spend row carries the end user,
the tags, the aresponses call type, and a nonzero cost.

* test(e2e): assert the customer's /customer/info total matches the header-attributed Responses row

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-09-24 18:33:49 -07:00
devin-ai-integration[bot]
c9e8a04139
feat(vertex): native batch JSONL passthrough with cost tracking (#42810)
* feat(vertex): native batch JSONL passthrough with cost tracking

Add a per-request `passthrough=true` multipart field on `POST /v1/files`
(and the same kwarg on `litellm.create_file`) that uploads a native
Vertex AI batch JSONL to the deployment's GCS bucket unchanged, so rows
using `googleSearch` and other Gemini-only features run as written and
the output, `groundingMetadata` included, comes back untouched.

Passthrough is sticky through the GCS object path
(`litellm-vertex-files/passthrough/...`), so batch create and output
retrieval inherit it without new state. Native output rows are costed
from their `usageMetadata` with the deployment's model and model_info,
in the polling and retrieve paths and for the existing global
`disable_vertex_batch_output_transformation` flag, which billed $0
before.

The proxy requires the target to resolve to vertex_ai deployments only,
refuses `passthrough` with a non-batch purpose, a non-default
`target_storage`, or pre-call guardrails, and validates native rows on
`request` instead of the OpenAI batch keys.

* refactor(vertex): keep native batch row pricing inside the Vertex adapter

Moves native Vertex batch row detection, response parsing, and per-row
pricing from litellm/batches/batch_utils.py into
litellm/llms/vertex_ai/batches/transformation.py, so batch_utils only
aggregates the rows it gets back. Adds tests/test_litellm/files to the
misc unit shard so the new test directory is claimed by a shard.

* fix(files): say what a passthrough batch upload takes when a row is not native

The missing-key 400 listed bare key names, so an OpenAI-shaped row under
passthrough=true read "Each line must be a JSON object with keys request".
The batch line shape now carries its own hint, and the passthrough one says
a passthrough upload takes native Vertex batch rows with a request key

* fix(batches): bill native Vertex embedding batch rows on the native cost path

A native Vertex output row whose response holds an embedding was validated as a
generateContent response, so the documented tokenCount-only shape counted as a failed
row. Price embedding rows from their own usage (promptTokenCount, else tokenCount) with
the helper the transformed embeddings path already used, and drop the prompt-details
helper nothing calls anymore.

* fix(batches): keep modality batch rates on native Vertex embedding rows

An embedding row that carries usageMetadata was billed from promptTokenCount alone, so
its promptTokensDetails no longer reached the audio, image, and video batch rates the
way it did before the native cost path. Run every row with usageMetadata through the
Gemini usage parser and keep the flat tokenCount fallback for embedding rows without it.

* fix(batches): price native Vertex batch rows by modelVersion under a wildcard deployment

A `vertex_ai/*` deployment hands the batch cost path `*` as the deployment model, which
no cost map resolves, so every native (passthrough or flag-on) row was billed at $0. A
wildcard deployment model now defers to the row's own `modelVersion`, the way the
transformed path already prices by the row's `model`.

Also moves the native passthrough tests under tests/test_litellm, the tree codecov
reads, and covers the raw upload chunking, the embedding output translation, the
unpriceable-row path, and the flag-on dispatch.

* fix(batches): keep explicit deployment prices for native Vertex rows without a modelVersion

Under a wildcard deployment a native batch row that carries no modelVersion (an embedding
row, or a generateContent row Vertex returned without one) was billed at $0 even when the
deployment's model_info sets explicit batch prices, because the cost calculator was never
called. The row now falls back to the wildcard name, which the cost calculator prices from
the explicit model_info, and only a row with neither a modelVersion nor a deployment model
is billed at $0 with the warning

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-09-24 12:35:34 -07:00
devin-ai-integration[bot]
8477fe4108
fix(proxy): list key and team model aliases in GET /v1/models (#42908)
* fix(proxy): list key and team model aliases in GET /v1/models

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

* fix(proxy): keep alias listing helpers within the type discipline budget

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

* test(proxy): cover alias rows on GET /v1/models and /v1/models/{id}

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

* fix(proxy): apply team then key aliases like chat completions and keep the alias as the retrieved id

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

* fix(proxy): apply key aliases twice like chat completions and skip only malformed alias entries

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

* fix(proxy): apply the global model_alias_map between the key alias passes like chat completions

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

* fix(proxy): list only the caller's own aliases and never rewrite a listed model id on retrieval

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

* style(proxy): ruff format model_info alias lookup

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

* fix(proxy): hide undiscoverable names from model retrieval so an alias named like one resolves to its target

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

* fix(proxy): keep undiscoverable models retrievable by id while excluding them from the alias guard

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

* refactor(proxy): pass an immutable name sequence into the model_info alias guard

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

* test(proxy): type the model list alias test helpers

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

* test(proxy): annotate the new alias listing test fixtures and helpers

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

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-24 06:41:13 -07:00
yujonglee
630c4624f6
test(e2e): add secret manager lanes for HashiCorp Vault and CyberArk Conjur (#42503)
* test(e2e): add a HashiCorp Vault secret manager lane

key_management_system had no end-to-end coverage: the Rust crates and the
Python unit tests all run against mocked managers. This adds a secret_manager
suite that drives a proxy configured with hashicorp_vault against a real Vault.

The tests seed a fresh secret name per test with the runner's OPENAI_API_KEY and
register a deployment pointing at os.environ/<name>. The proxy's env never holds
that name, so get_secret's os.environ fallback cannot mask a broken manager, and
a bogus value in Vault must come back as the provider's 401. Virtual keys are
checked written to and removed from Vault under prefix_for_stored_virtual_keys.

The setting is global to the proxy, so the lane has its own config and the
secret_manager_vault opt-in marker, and stays out of the per-PR selector.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(e2e): make the secret manager suite backend-agnostic

One marker and opt-in (secret_manager / E2E_SECRET_MANAGER=<system>) pick the
backend from secret_backends.BACKENDS. The tests reach the manager through a
SecretStore protocol, and each backend contributes a secret_store_<system>.py
module, a registry entry, and gateway/secret_manager_<system>_ci_config.yml.
requires_capability deselects tests a backend cannot support (CyberArk does
not delete), and test_secret_backends.py checks every lane config against its
backend without a live stack.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(e2e): add a CyberArk Conjur secret manager lane

Adds cyberark as the second secret_manager backend: a Conjur store over its
REST API (policy-declared variables, raw-text values, policy-patch teardown),
its lane config, and a registry entry without deletes_stored_keys, since the
proxy's CyberArk delete answers not_supported and Conjur keeps the key.

secret_manager/backend.sh up|down <system> boots any backend in Docker and
writes proxy.env and tests.env, so every lane runs the same way; the registry
test checks the script boots exactly the registered backends. e2e_http gains
send_text_external for APIs that speak raw text rather than JSON.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(e2e): give the secret manager suite a client with .proxy and address review

The shared resources fixture reads client.proxy, so a bare ProxyClient errored every
live test at setup. backend.sh now writes its env under a per-user directory with
umask 077, the markerless unit tests are gone per tests/e2e/AGENTS.md, and routine
comments are trimmed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 18:02:32 -07:00
devin-ai-integration[bot]
8ee6bab529
fix(bedrock): treat blank AWS_S3_* env vars as unset for batch jobs (#42528)
* test(e2e): pin bedrock batch create with blank S3 env vars

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

* fix(bedrock): treat blank S3 env vars as unset for batch jobs

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

* test(e2e): trim blank S3 env gateway config

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

* test(e2e): register blank_s3_env capability and clean gateway tempdir

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

* test(e2e): move blank S3 env batch test to its own module

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

---------

Co-authored-by: yucheng <yucheng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-22 17:55:22 -07:00
devin-ai-integration[bot]
392e807172
feat(logging): add normalized_error cluster key to error_information (#41715)
* feat(logging): add normalized_error cluster key to error_information

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

* fix(logging): stop classifying parameter length errors as context window errors

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

* test(e2e): assert failure spend rows share normalized_error across provider wording

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

* fix(logging): map agent model access denials and ignore non-string proxy error types

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

* test(logging): cover budget exceeded errors with custom wording

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

* fix(logging): cluster router no-healthy and provider-budget wording correctly

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

* fix(logging): let the exception class win over router fallback wording

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

* fix(logging): cluster peer closed connection errors as provider connection errors

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

* fix(logging): cluster tag routing denials as 403_MODEL_ACCESS_DENIED

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

---------

Co-authored-by: shivam <shivam@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: yucheng <yucheng@berri.ai>
2026-09-22 15:56:50 -07:00
devin-ai-integration[bot]
f7ae9efad2
test(e2e): hold every worker under an idle RSS budget before any traffic (#42552)
* test(e2e): hold every worker under an idle RSS budget before any traffic

The harness reads /debug/memory/summary on every replica once at collection
time, right after the readiness gate and before this pytest process sends any
traffic, and the memory suite's first test fails when any worker idles past
E2E_MEMORY_IDLE_RSS_BUDGET_MB (768 MB by default) or gives no reading at all.

A v1.100.x worker with a database idled at 836-886 MB where v1.101.0rc1 idled
at 544 MB on the same database: prisma-client-py's default recursive type
depth generated 91k TypedDict classes that v1.101.0's recursive_type_depth = -1
cut to 19k. The budget starts at the rc1 reading plus headroom.

* test(e2e): read idle RSS only when the idle budget test is selected

Gate the collection-time /debug/memory/summary read on a selected test using
the idle_rss fixture and skip it under --collect-only, so sessions that never
run the idle budget test pay no round trip. Drop the markerless unit test file
the e2e guide bans and assert live that every configured replica was measured

* test(e2e): take the idle RSS read after collection settles

Read every replica's RSS from a tryfirst pytest_collection_finish hook so -k
and -m deselection has already run, and only when a selected test still asks
for the idle_rss fixture and the run is not --collect-only

* test(e2e): record the heaviest idle RSS reading as junit properties

* test(e2e): attach the idle RSS properties from the harness's setup hook

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-09-22 14:47:14 -07:00
devin-ai-integration[bot]
989d7b87b2
fix(proxy): attribute provider and model_info on pre_call_hook rejections (#41077)
* fix(proxy): attribute provider and model info on pre-call rejected requests

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

* fix(proxy): keep pre-call rejections out of deployment cooldown and prometheus deployment state

Stamp model_info only into the logging metadata so the router's failure callbacks do not
count a key-level 429 or guardrail 403 against the deployment, treat a resolved plus an
unresolved deployment as ambiguous provider attribution, and stop the prometheus deployment
counters and deployment_state from treating a proxy-side reject as a selected deployment

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

* fix(proxy): skip deployment attribution when the rejected body's model is not a string

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

* fix(prometheus): bucket non-string request models as other instead of raising in failure hook

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

* fix(proxy): resolve team deployments and treat guardrail rejects as proxy-side in failure attribution

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

* fix(prometheus): flag pre-routing rejects instead of matching exception names

Post-call GuardrailRaisedException failures kept their deployment labels on main but lost them
on this branch because every GuardrailRaisedException was treated as a pre-routing reject.
The proxy failure path now flags litellm_params with proxy_rejected_before_routing only when
it adds deployment attribution itself, and the Prometheus logger keys deployment selection
off that flag

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

* fix(proxy): key pre-routing reject flag off provider handoff, not caller metadata

Caller-supplied metadata.model_info (kept for keys allowed to override pricing) no longer
suppresses proxy_rejected_before_routing. The hook now checks the logging object's
first_api_call_start_time, which only the provider handoff sets, so Prometheus never
records a deployment failure for a request that was rejected before routing.

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

* test(e2e): poll for both served and rejected spend rows before asserting attribution

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

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: yucheng <yucheng@berri.ai>
2026-09-22 14:14:13 -07:00
devin-ai-integration[bot]
1c602334ee
test(e2e): one request lands the same spend on every surface (#42540)
* test(e2e): one request lands the same spend on every surface

One priced chat request must show the same response_cost on the spend log row, /key/info, /team/info, the usage export's /user/daily/activity/aggregated row, and the litellm_spend_metric Prometheus sample; each is a separate writer, so the test fails naming the surface that drifted

* fix(e2e): scrape every replica's /metrics/ and enable prometheus in the replay lane

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-09-22 13:42:00 -07:00
devin-ai-integration[bot]
c6c3881d7f
fix(proxy): share model rate-limit buckets between a model_group_alias and its target (#42516)
* fix(proxy): share model rate-limit buckets between a model_group_alias and its target

A request sent under a model_group_alias counted in its own per-key, per-team,
per-org, and per-project model bucket, so a key could double a deployment's
default_api_key_rpm_limit / tpm_limit by alternating the alias and the model
group name, and a metadata model_rpm_limit / model_tpm_limit keyed by the
model group never applied to alias requests. The limiter now resolves the
requested name to its model group before keying any model bucket, looks the
limit up by the requested name first and the model group second, and charges
post-call tokens to the same bucket.

* fix(proxy): charge the model group resolved at admission when reconciling reserved tokens

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-09-22 13:31:58 -07:00
devin-ai-integration[bot]
cb2f22533c
feat(proxy): opt-in include_guardrail_response returns guardrail_information in the response (#42327)
* feat(proxy): opt-in include_guardrail_response returns guardrail_information in the response

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

* fix(proxy): read include_guardrail_response from the request metadata bucket the router did not reseed

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

* style(proxy): format common request processing

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

* fix(proxy): redact matched content in guardrail_information and stop mutating cached responses

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

* fix(proxy): traverse guardrail diagnostics iteratively

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

* fix(proxy): annotate guardrail traversal cast

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

* refactor(proxy): reuse core redaction helper for guardrail_information

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

* chore(proxy): justify response rebind when attaching guardrail information

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

---------

Co-authored-by: yucheng <yucheng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-22 12:43:30 -07:00
devin-ai-integration[bot]
7516327898
test(e2e): assert a cooldown reaches a sibling replica within the 1s Redis read interval (#42422)
* test(e2e): assert a cooldown reaches a sibling replica within the 1s Redis read interval

* test(e2e): skip the sibling replica cooldown cell when one gateway URL is named

* test(e2e): collapse repeated gateway addresses so the sibling cooldown cell skips instead of erroring

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-09-22 12:42:41 -07:00
devin-ai-integration[bot]
b6d4133e41
fix(websearch_interception): keep intercepted searches under the parent request's session and trace (#41711)
* fix(websearch_interception): propagate parent session/trace ids into intercepted searches

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

* fix(websearch_interception): let parent correlation win over configured search params and type test params

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

* test(e2e): bill an intercepted web search under the parent request session

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

* test(e2e): drop unrelated reformatting from the websearch session harness change

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

* ci(e2e): keep the websearch interception session suite out of the stage-mirror gate

The stage-mirror stack runs no websearch_interception callback or search tool, so the suite is deselected there and the changed-tests gate fails on a file that executed nothing

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

* ci(e2e): run the websearch interception session suite on the stage-mirror stack

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

* docs(e2e): leave CONTRIBUTING.md untouched

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

---------

Co-authored-by: shivam <shivam@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-22 12:35:27 -07:00
devin-ai-integration[bot]
2ef710e3d5
fix(langsmith): json.dumps with default=str so non-serializable metadata does not crash batch flush (#42424)
* fix(langsmith): json.dumps with default=str so non-serializable metadata does not crash batch flush

Serialize the runs/batch payload with json.dumps(default=str, allow_nan=False) and send it as content= with an explicit Content-Type, so datetime, Decimal and similar metadata values no longer raise TypeError and drop the batch. Forward content= on the AsyncHTTPHandler retry path so a retried batch re-sends the identical body

Replaces #39133, which was cut from the retired staging branch and conflicts with main

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

* test(langsmith): drop test docstrings and replace monkeypatch with a client-injecting handler

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

* test(langsmith): add live e2e for non-native metadata reaching LangSmith

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

* test(langsmith): scope the e2e docstring to the values the test injects

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

* test(http_handler): close injected retry clients

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

* test(e2e): deselect the LangSmith live e2e on the stage-mirror stack

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

---------

Co-authored-by: yucheng <yucheng@berri.ai>
Co-authored-by: Damien Smrt <dsmrt@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-22 12:21:11 -07:00
devin-ai-integration[bot]
f3a0bb7249
fix(proxy): write key deleted audit logs for cascade and alias key deletions (#42446)
* fix(proxy): write key deleted audit logs for cascade and alias key deletions

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

* test(proxy): assert persisted key deleted audit rows for cascade paths

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

* test(e2e): route /audit and /v2/login to the control plane in split transport

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

---------

Co-authored-by: yucheng <yucheng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-22 11:52:08 -07:00
devin-ai-integration[bot]
071cb49d32
fix(jwt): accept a team alias in x-litellm-team-id (#42445)
* fix(jwt): accept a team alias in x-litellm-team-id

The header only matched canonical team ids, so a JWT caller selecting one of their teams by its alias got a 403 even though they belonged to it. The header value is now resolved through the existing alias lookup before the JWT allowed-team check and the DB membership fallback, while a value that is already a team id never costs an alias lookup and denials keep naming the value the caller sent

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

* fix(jwt): only alias a header team id the database provably lacks

Under fallback_to_db_teams a header value whose team row read fails for any reason other than TeamNotFoundError now keeps the membership denial instead of falling through to the alias lookup, so a degraded read cannot select a different team that carries the value as an alias. Drops the HeaderTeam docstring that only restated its fields

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

---------

Co-authored-by: ryan <ryan@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-22 02:22:00 -07:00
devin-ai-integration[bot]
1a714548a4
fix(guardrails): store the masked output in spend logs when Presidio masks the response (#42441)
* fix(guardrails): store the masked output in spend logs when a post_call guardrail rewrites the response

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

* fix(guardrails): record served output without re-narrowing the logging object

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

* fix(guardrails): overlay the served output before message redaction so turn_off_message_logging still wins

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

* test(logging): type the monkeypatch fixture in the redaction ordering regression

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

* test(e2e): poll through raw card output until the guardrail reaches the serving worker

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

* fix(guardrails): keep blanked, multi-choice and disconnected served output out of raw spend logs

Served text keeps empty strings and tracks unavailable choices as None so a guardrail that blanks
the output still overrides the raw provider text. Stream choices are sized from the highest choice
index, served chunks are recorded before a client disconnect or stream failure propagates, and
message-logging redaction drops the served text from callback kwargs

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

---------

Co-authored-by: yucheng <yucheng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-22 01:19:22 -07:00
devin-ai-integration[bot]
30d8b12512
test(e2e): add conversational matrix across chat, messages and responses (#42359)
* test(e2e): add conversational matrix across chat, messages and responses

Parameterizes one behavioral contract (reply, stream, cost log, tool call,
tool round trip) across /v1/chat/completions, /v1/messages and /v1/responses,
OpenAI and Anthropic models, and env-ref vs stored-credential auth, with
record/replay fixtures.

Adds general_settings.disable_model_info_refresh so the proxy fronting a
replay fixture does not poll every OpenAI-compatible deployment's /v1/models
in the background, which otherwise leaves unconsumed interactions in the
recorded bundle.

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

* test(e2e): force the weather tool on the first turn and rename Provider to Deployment

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

---------

Co-authored-by: mateo <mateo@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-21 22:32:56 -07:00
devin-ai-integration[bot]
55e95c0279
fix(presidio): mask PII in streaming /v1/messages output (#42351)
* fix(presidio): mask PII in streaming /v1/messages output

Raw Anthropic SSE frames were passed through the post_call output masking
callback untouched, and ProxyLogging rerouted the callback to the unified
apply_guardrail path on /v1/messages because mask_response_content was
false. Buffer the raw frames, assemble them with the shared Anthropic SSE
helpers, mask through Presidio, and re-emit the masked frames.

Resolves LIT-8288

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

* fix(presidio): replay raw SSE frames when masking fails mid-stream

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

* fix(presidio): propagate upstream stream errors instead of returning an empty stream

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

* refactor(presidio): extract buffered stream masking to satisfy complexity budget

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

* fix(presidio): let BLOCK on generated PII refuse the streaming /v1/messages response

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

* refactor(presidio): fold the BLOCK re-raise into the existing except to stay within the complexity budget

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

* test(presidio): move the blocked stream consumption into a helper so pytest.raises holds one statement

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

* fix(presidio): fail closed when output masking of a raw SSE stream errors

A Presidio outage on streaming /v1/messages replayed the unscanned frames
to the caller. Propagate the error instead, matching the non streaming
path and the merge base

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

* test(presidio): cover structured chat stream output masking and trailing bytes passthrough

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

---------

Co-authored-by: yucheng <yucheng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-21 21:42:52 -07:00
devin-ai-integration[bot]
2f76aa1b1b
test(integration): move Xiaomi MiMo coverage from live e2e to the providers wire shard (#42395) 2026-09-21 19:51:26 -07:00
devin-ai-integration[bot]
5dc6261ebb
fix(bedrock): sign batch S3 requests with s3_access_key_id and s3_secret_access_key (#42342)
* fix(bedrock): sign batch S3 requests with s3_access_key_id and s3_secret_access_key

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

* test(bedrock): keep S3 signer test additions scoped to new cases

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

* test(bedrock): drop e2e suite changes from the S3 signing fix

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

* fix(bedrock): build S3 credentials directly from the s3_* pair so ambient AWS_* env never mixes in

Restores the split-identity e2e coverage

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

---------

Co-authored-by: yucheng <yucheng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-21 19:01:18 -07:00
Mateo Wang
9fad216030
Merge pull request #42295 from BerriAI/litellm_fix_azure_cancellederror_cooldown
fix(azure): propagate asyncio.CancelledError instead of raising a 500
2026-09-21 17:24:22 -07:00
yucheng-berri
12f79308cf
Merge pull request #42354 from BerriAI/litellm_fix_video_key_guardrails_lit_6685
* fix(guardrails): scan video prompts for key-attached guardrails on /v1/videos

/v1/videos dispatches call_type avideo_generation, which CallTypes did not
know and no guardrail translation handler covered, so the unified guardrail
hook returned the request unscanned. Add the video call types and an OpenAI
video guardrail translation package that scans the prompt for create, remix,
edit and extension requests

Resolves LIT-6685

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

* chore(ui): regenerate api types for video call types

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

* test: skip avideo_generation in azure sdk client exhaustive check

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

* test(e2e): retry a leaked video job until the guardrail sync deadline

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

* refactor(guardrails): satisfy the type-discipline gate in the video handler

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

* test(guardrails): gate the video e2e on a chat probe so a miss starts at most one paid job

Addresses Greptile review: typed RewritingGuardrail override, dropped routine docstrings, and the e2e waits for the key guardrail to sync via /chat/completions before its single /v1/videos call

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

---------

Co-authored-by: yucheng <yucheng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-21 17:23:56 -07:00
kerry
47a1053065 feat(xiaomi_mimo): add mimo-v2.6-pro and mimo-v2.6-flash cost map rows with live e2e coverage
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-21 23:12:57 +00:00
yucheng
9ae2fe2ea4 fix(guardrails): scan video prompts for key-attached guardrails on /v1/videos
/v1/videos dispatches call_type avideo_generation, which CallTypes did not
know and no guardrail translation handler covered, so the unified guardrail
hook returned the request unscanned. Add the video call types and an OpenAI
video guardrail translation package that scans the prompt for create, remix,
edit and extension requests

Resolves LIT-6685

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-21 22:47:14 +00:00
yucheng
b41edb57c1 fix(bedrock): keep batch S3 credentials out of chat requests and debug logs
Register s3_access_key_id, s3_secret_access_key and s3_encryption_key_id as
LiteLLM-owned batch params so they are no longer forwarded to Bedrock as
additionalModelRequestFields (which 400s ordinary chat on a batch-configured
deployment), keep them on CredentialLiteLLMParams so the batch/file paths
still receive them, and redact the S3 credential key names in debug logs.

Resolves LIT-8290

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-21 20:42:04 +00:00
mateo-berri
fda7a078d3 test(e2e): client hang-up under cancel_on_disconnect never benches the Azure deployment 2026-09-21 11:59:55 -07:00
Joshua Valluru
64452f76c2 test(e2e): restore LIT-3467 implementation for rework 2026-09-19 16:21:53 -07:00
Mateo Wang
af6a1798e2
Revert "test(e2e): cover MCP OAuth SSO and cold restart persistence" 2026-09-19 16:15:43 -07:00
Joshua Valluru
31b4405435 chore: merge main with shared E2E lockout fix 2026-09-19 15:10:55 -07:00
Yuneng Jiang
bf9c717d77
test(e2e): stop the config suite locking itself out of the shared proxy
Two tests in the config/misc management suite were failing every run against
the Buildkite e2e stack, and one of them took the rest of the build with it.

test_add_allowed_ip_does_not_store_unrelated_config_value posted 127.0.0.1 to
/add/allowed_ip. That route sets the live general_settings["allowed_ips"] that
auth_utils._check_valid_ip reads before it persists anything, and the check is
exact string membership with no CIDR support, so from the moment the POST
returns only 127.0.0.1 can reach the proxy. The runner 403s on its very next
call, and the deferred /delete/allowed_ip sits behind the same auth dependency,
so the cleanup is locked out too and every later test in the build 403s. Build
254's first attempt lost 459 of its 465 failures to that one cascade.

There is no safe way to exercise the route against a shared proxy: nothing
reports the caller's address as the proxy sees it, so a test cannot allowlist
itself first. Move the claim to the route's own TestClient suite, where the
auth dependency is overridden and general_settings is per-test, and record the
route in the module docstring beside /cache/settings and the Vault override so
it is not re-added. save_config's end of the contract was already covered by
test_ProxyConfig_save_config_merges_changed_keys_without_copying_file_settings;
the new test covers the route's end, that what it hands save_config differs
from the loaded config in allowed_ips and nothing else.

The unrelated-key probe also only ever worked on one lane: max_parallel_requests
was added to tests/e2e/gateway/stage_mirror_ci_config.yml and never to the
Buildkite stack's config, where resolve() reports it as "unset" rather than
"config". That key is now unused, so drop it again.

test_config_update_persists_router_setting_to_get wrote router_settings.
num_retries, which both lanes declare in their config file, so the config-
ownership work correctly refuses it with a 400. Switch to retry_after, which is
declared by neither lane, is accepted by /config/update, and is reported back by
GET /router/settings. Verified against a live proxy: max_fallbacks also takes
the write but never reads back, so the read-back poll is what picks the key.
2026-09-19 12:51:54 -07:00
Joshua Valluru
7f4dd4eabc test(e2e): cover MCP OAuth SSO and cold restart acceptance 2026-09-18 22:02:09 -07:00
Devin AI
62051ad9bc merge: bring main into litellm_mcp_oauth_happy_path_e2e
Co-Authored-By: bot_apk <apk@cognition.ai>
2026-09-19 03:42:37 +00:00
Yujong Lee
bf7d1c0733 chore: consolidate CLAUDE.md into AGENTS.md
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-19 02:30:35 +00:00
Devin AI
6c8f1c22e0 test(e2e): cover MCP OAuth happy path through gateway
Co-Authored-By: bot_apk <apk@cognition.ai>
2026-09-19 00:35:56 +00:00
Yuneng Jiang
af17691389
fix(proxy): persist only the keys a caller changed in save_config 2026-09-17 20:32:04 -07:00
kerry
435ab2ccc0 Merge remote-tracking branch 'origin/main' into litellm_aws_govcloud_partition_gate 2026-09-17 20:16:28 +00:00
kerry
5a5b18550c test(e2e): cover bedrock batch files in govcloud
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 20:16:09 +00:00
ryan-crabbe-berri
e3a82f2f66 fix(proxy): stop team admins raising an org team's max_budget under the org cap
The keep-or-lower budget rule only ran for standalone teams, so once max_budget is enabled a team admin on an org team could grow its own budget up to the organization's. It now applies to team admins on every team; org admins keep editing within the org cap.
2026-09-16 17:35:18 -07:00
ryan-crabbe-berri
37c56df054 feat(proxy): let team admins edit rpm_limit and max_budget when enabled
Adds both fields to the team admin editable allow-list and the dashboard's team admin form. The existing budget authority check still stops a team admin from raising or removing a standalone team's budget.
2026-09-16 17:25:36 -07:00
ryan-crabbe-berri
ab92a6637d test(e2e): cover team admin editable fields on /team/update
Team admins are refused until a proxy admin enables a field, then limited to the enabled fields, and resending unchanged budget settings keeps the team's budget reset times
2026-09-16 16:26:52 -07:00
Mateo Wang
2e06d195b2
Merge pull request #39857 from BerriAI/litellm_e2e_reliability_module_cells
test(e2e): cover the reliability retry, cooldown, fallback, and routing-strategy cells
2026-09-15 11:38:51 -07:00
yuneng-jiang
15bd8b0e4a
Merge pull request #40892 from BerriAI/litellm_jwt_management_callers
test: bind management E2E callers and isolate JWT actors
2026-09-14 20:30:52 -07:00
Joshua Valluru
94e4dd725f fix(mcp): require admission for delegated OAuth 2026-09-12 16:41:23 -07:00
Yuneng Jiang
c8bb54993e
test: enforce isolated actors and stop OIDC process groups 2026-09-12 13:49:49 -07:00
Yuneng Jiang
88de192dcf
test: bind management E2E callers and isolate JWT actors 2026-09-12 13:29:04 -07:00