Commit graph

6820 commits

Author SHA1 Message Date
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]
c976c16a82
feat(mcp): allow ["*"] wildcard in mcp_tool_permissions to grant all current and future tools (#43108)
* feat(mcp): allow ["*"] wildcard in mcp_tool_permissions to grant all current and future tools

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

* style(ui): run prettier on MCPToolPermissions files

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

* fix(mcp): keep ["*"] wildcard through toolset union and move constant to litellm.constants

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

* style(mcp): format user_api_key_auth_mcp with ruff format

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

* test(mcp): restore wildcard ceiling and deny-all regression tests

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

* fix(mcp): treat an empty team tool list as deny-all regardless of key grants

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

* revert(mcp): keep legacy [] merge semantics, the truthiness check predates this PR

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

* test(mcp): drop banner comment that repeats the wildcard test docstring

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

---------

Co-authored-by: joshua <joshua@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-24 21:34:57 -07:00
devin-ai-integration[bot]
f61b3c3f38
refactor(types): declare litellm-owned kwargs as typed objects and derive the lists from their fields (#42843)
* refactor(types): declare litellm-owned params in one registry

* refactor(types): re-export registry constants without redundant aliases

* refactor(types): satisfy type-discipline rules in registry projections and tests

* refactor(types): classify every registry entry and check groups against typed config models

* test(types): pin load-bearing names and exact projections in registry tests

* style(types): keep agentic projection comment within ruff format

* refactor(types): declare litellm-owned params as typed objects and derive the lists from their fields

* refactor(types): fields of the typed objects become the registry; tests use a hand-written inventory

* refactor(types): split traversal into wire_names and owned_wire_names, move rust to kwarg artifacts

rust is a module-level switch (litellm.rust) that nothing reads from a call's kwargs, so it
joins self, use_client and model_config as a registered artifact instead of a DispatchOptions
field. The field constants now import from litellm.types.litellm_params directly instead of
through a re-export in litellm.types.utils. metadata and litellm_metadata are MutableMapping
because their readers mutate them in place, and client accepts raw httpx clients

* refactor(types): own max_agentic_loops as an option and walk only nested leaves

Move max_agentic_loops from AgenticLoopState to a new AgenticLoopOptions leaf under
LiteLLMOptions, since the interception handlers read it as a deployment ceiling rather
than stamping it. Drop the owned_wire_names fallback that treated an unresolved annotation
as a direct field, which under postponed annotations silently shrank the registry. Re-export
TRUSTED_CALLBACK_VARS_FIELD and ADDRESSED_RESPONSE_ID_FIELD from types.utils so that import
path keeps working. Tests use hand-written inventories for the callback and pricing names

* refactor(types): move data_residency to call state and drop aliased re-exports

data_residency is stamped by get_litellm_params and responses.main during the
call, so it lives on CallState, not CostOptions. mock_response also accepts a
float sequence, which main.py reads for mock embeddings. The types/utils.py
re-exports become one plain import with an exact F401 suppression instead of
two X as X aliases that pushed PLC0414 over its strict-gate ceiling. Redundant
leaf docstrings and the structural artifact test are gone; the re-exported
FIELD constants are checked by identity instead

* refactor(types): project owned kwarg names once and keep pass-through extraction in request order

* refactor(types): type caching_groups from its cache reader and hoist the pass-through ownership set

caching_groups is a sequence of flat model-group sequences, which is what
Cache._get_caching_group iterates. A regression test drives the public
cache key path so two groups in one caching group share a key and a third
does not. The pass-through endpoint builds its frozenset of owned names
once at import instead of per request, reads the two metadata carriers
from the extracted mapping instead of popping them, and its extraction
mappings are read-only. Concatenation tests assert the whole derived list
and tuple, docstrings drop reader claims that nothing in the module backs

* refactor(types): read owned names live in pass-through and pin tests to literal inventories

The pass-through endpoint checks body keys against the public all_litellm_params
list at request time again, as the base does, instead of a frozenset taken at
import, so a name registered after import is still extracted. A test drives
that path with a name added after import, and another sends both metadata
carriers interleaved with provider keys and asserts the whole merged result.

retry_policy accepts the mapping form its router reader builds a RetryPolicy
from. The pricing inventory in the typed tests is a literal tuple checked
against the model's fields, the agentic compatibility test asserts type, length
and set instead of declaration order, and the typed-model overlap tests assert
the exact intersection.

* refactor(types): move model_alias_map to CallState and read the owned registry in registry order in pass-through

* refactor(types): drop restating docstrings, keep FIELD importers on types.utils, pin pass-through registry order

* fix(types): satisfy strict lint for public FIELD re-exports

* fix(types): restore clean parameter re-exports

* fix(tests): compare pass-through extraction order to registry body keys

* refactor(types): type owned request parameter leaves

* refactor(types): share routing strategy literal and tighten leaf tests

* fix(proxy): drop client-supplied proxy-stamped names from pass-through litellm_params

* refactor(proxy): name pass-through litellm key split for what it holds

* refactor(types): drop TODO markers on the kept readerless fields

* fix(types): keep deployment tag_regex and max_file_size_mb out of provider requests

* fix(types): include every routing strategy the router accepts

---------

Co-authored-by: shrey kharbanda <shreshth@berri.ai>
2026-09-24 20:18:41 -07:00
devin-ai-integration[bot]
1d51a8dfc3
fix(proxy): authorize key model aliases the same way as team aliases (#43049) 2026-09-24 19:51:02 -07:00
devin-ai-integration[bot]
5c0de806fb
feat(proxy): let team admins update member key budgets when enabled (#42555)
* feat(proxy): let team admins update member key budgets when enabled

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

* fix(proxy): drop casts flagged by LIT006 in member key budgets change

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

* fix(ui): send budget-only key updates when a team admin edits a member key

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

* fix(proxy): block spend echo in team admin member key updates

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

* fix(ui): only send dirty budget fields in team admin member key updates

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

* test(proxy): replace class method monkeypatch with module symbol patch

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-25 01:54:52 +00:00
Mateo Wang
25fb7810c2
fix(spend): attribute CLI session spend to the per-user cli-session alias instead of the hashed session token (#40541)
* fix(spend): attribute CLI session spend to the per-user cli-session alias instead of the hashed session token

A CLI session token is a fresh random secret on every login, so since v1.99 each
login's spend rows carried a different sha256 hash as api_key and the usage APIs
could resolve neither key_alias nor user_email for them. Spend rows and logging
callbacks now attribute a session request to its stable alias,
cli-session-<user_id>, and the usage endpoints derive that alias and owner from
the key itself instead of scanning for a matching digest

* fix(spend): resolve the CLI session team from the user's first team in usage metadata

A cli-session key carries no team of its own in the DB, so the usage
breakdown showed team_id None for it and the export grouped it as
Unassigned. The login attaches the user's first team to the session, so
the recovery mirrors that rule for cli-session keys only.

* fix(spend): claim the session team only for a single-team user

The CLI login attaches a team on its own only when the user has exactly
one; a user in several teams picks one per login, so usage metadata for
the alias would otherwise name a team the login may not have used.

* test(pass_through): mark the mocked auth object as a plain key

The logged key follows the alias only for a session token; a bare
MagicMock reads as one, so the test names the field it relies on.

* fix(spend): attribute CLI session pass-through, queue, and managed batch spend to the cli-session alias

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

* fix(spend): only treat the exact cli-session-<created_by> value as a batch key alias

A managed object row written by an older build can still carry the raw per-login
session token, which shares the cli-session- prefix. Matching on the prefix alone
would have surfaced that token as a trusted alias and persisted it verbatim in the
batch cost spend log, so the alias check now requires the exact per-user value and
every other prefixed value keeps going through redaction

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

* fix(spend): log proxy executed batch rows under the cli-session alias instead of the session token

_row_metadata set user_api_key from the raw bearer token while user_api_key_hash carried the alias, so the spend log redaction rejected the alias as untrusted and hashed the random session token instead

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

* fix(spend): attribute semantic search embedding spend to the cli-session alias

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

* fix(spend): scope /key/spend/report for a CLI session to the cli-session alias

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

* fix(spend): use the cli-session alias for websearch spend, prometheus failure labels and the parallel limiter

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

* refactor(spend): drop explanatory docstrings on get_logged_api_key and attach_user_details

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

* fix(spend): only recover cli-session usage keys whose suffix is a known user

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 18:21:47 -07:00
devin-ai-integration[bot]
e2302be068
refactor(ocr): remove the Python OCR execution path and require the Rust route (#43081)
* refactor(ocr): remove the Python OCR execution path and require the Rust route

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

* fmt

* refactor(ocr): tidy the native OCR passthrough binding

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

* style(ocr): ruff format the azure passthrough transformation

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

* refactor(ocr): resolve passthrough OCR costing in one Rust call

Replace passthrough_url/passthrough_transform with passthrough_response,
which matches the relayed endpoint against each Azure config's path
segments instead of building a fake request to call get_complete_url.
The binding drops the unused headers, status and api_base arguments.

Catch the ValueError/RuntimeError the binding raises so a relayed body
that is not OCR-shaped falls back to the passthrough object instead of
failing logging, and cover the relay against the real binding.

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

* fix(ocr): drop the unused LlmProviders import from health check helpers

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

* ci: drop the ocr_testing job now that tests/ocr_tests is gone

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

* test(ocr): restore the live OCR matrix and the ocr_testing job

The public litellm.ocr / aocr / Router interface is unchanged by the Rust
migration, so the live provider matrix still applies. Drops the stale VCR skip
list for the deleted test_rust_bridge.py.

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

* test(ocr): import Final in the health check helper tests

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

---------

Co-authored-by: Yujong Lee <yujong@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 18:18:50 -07:00
devin-ai-integration[bot]
7bdccd7371
feat(proxy): enforce tpm_limit and rpm_limit set on tag objects (#41807)
* feat(proxy): enforce tpm_limit and rpm_limit set on tag objects

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

* refactor(proxy): keep tag rate limit helpers within type discipline budget

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

* refactor(proxy): drop descriptive docstrings from tag rate limit helpers

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

* test(integration): cover tag object rpm and tpm limits

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

* test(proxy): type the fake tag batch helper parameters

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

* fix(proxy): name the over-limit tag in 429 errors

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

* test(integration): cover tag rpm limit shared across teams, orgs and users

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

* style(proxy): format the tag descriptor match in the v3 limiter

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

* fix(proxy): drop Final annotation inside loop for pyright

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: kerry <kerry@berri.ai>
2026-09-24 18:11:57 -07:00
devin-ai-integration[bot]
1987133b4e
fix(router): match provider-prefixed fallback keys for bare model groups served by wildcard deployments (#43062)
* fix(router): match provider-prefixed fallback keys for bare model groups

* fix(router): infer the fallback key's provider the way routing does for bare model groups

A bare model group served by a wildcard deployment (claude-sonnet-4-6 routed to anthropic/*) now finds a fallback keyed <provider>/<group>. The provider is inferred through one shared helper, inferred_provider, which the pattern router already used inline, so the fallback lookup and routing agree on the prefix. The lookup only infers a provider when some fallback key ends in /<group>, so alias-style groups never hit the resolver

* fix(router): resolve context window and content policy fallback keys through the shared lookup

---------

Co-authored-by: Jason Dougherty <jasondoc3@gmail.com>
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-09-24 18:08:56 -07:00
devin-ai-integration[bot]
c183d810f3
feat(spend): capture-rate check of LiteLLM spend against the OpenAI bill (#43044)
* feat(spend): capture-rate check of LiteLLM spend against the OpenAI bill

* fix(spend): claim the alert lock after the check, NaN gauge on no rate, 180-day range cap, live settings, OpenAI adapter under llms

* fix(spend): chart the capture-rate gauge in the all-metrics dashboard and clear it when the check is removed

* fix(prometheus): record the capture-rate gauge when api_provider is an excluded label

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-09-24 17:09:28 -07:00
Yassin Kortam
27d1974e2f
fix(mcp): cap an agent key's tools at what the invoking user and team may call (#42478)
* fix(mcp): cap an agent key's tools at what the invoking user and team may call

The invoking user's and team's x-litellm-user-id / x-litellm-team-id, echoed back by the
agent, already narrowed which MCP servers the agent key could reach, but not which tools on
those servers. An agent granted every tool on a server kept them all when acting for a user
who may only call a subset. The caller's team and user tool grants now intersect the agent's
tool list on each server, mirroring the servers axis, so the headers only ever narrow.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(mcp): pick the caller principal explicitly instead of getattr in the tool grant stub

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

* fix(mcp): return immutable tool sequences from the agent caller tool ceiling

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-24 18:47:44 -05:00
devin-ai-integration[bot]
5afb80742d
fix(proxy): stop /utils/transform_request from calling the provider and blocking the event loop (#33954)
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-24 18:34:00 -05:00
devin-ai-integration[bot]
4584958574
feat(agents): add optional per-agent kill switch webhook (#42841) 2026-09-24 18:26:50 -05:00
devin-ai-integration[bot]
248f0eb159
ci: move tests/proxy_unit_tests to tests/unit/proxy and run the proxy-db shards from litellm-tests (#42903)
* ci: fix the litellm-tests unit job with sysmon coverage, an env allowlist and coverage upload on failure

* test: replace key-dependent proxy, enterprise and mcp unit tests with synthetic values and integration and e2e coverage

* test: drop key reads at the legacy proxy, enterprise and mcp paths and wire the gemini pass-through split

* ci: move caching, proxy-extras, gateway and enterprise tests into tests/unit and run them from litellm-tests under their legacy flags

* ci: move caching, proxy-extras, gateway and enterprise tests into tests/unit and run them from litellm-tests under their legacy flags

* ci: move tests/proxy_unit_tests to tests/unit/proxy and run the proxy-db shards from litellm-tests

* ci: fail the unit shard when circleci tests split errors

* test: drop restating comments from the gemini pass-through split

* build: point the local proxy unit targets at the nested tests/unit/proxy tree

* ci: exit the unit shard cleanly when circleci tests split assigns it no files

---------

Co-authored-by: yuneng <yuneng@berri.ai>
2026-09-24 22:59:11 +00:00
devin-ai-integration[bot]
7b25a151bd
feat(proxy): let callbacks filter the model listing routes per caller (#43027)
* feat(proxy): let callbacks filter the model listing routes per caller

* fix(proxy): offer every listed name to the listing callback, agent groups and deployment lookups included

* fix(proxy): hide aliases of a team model by its public name and offer /model/info lookups the listed name

* fix(proxy): map a malformed model listing filter return to the proxy error contract and document legacy team names

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-09-24 15:52:44 -07:00
devin-ai-integration[bot]
de8aeff6c6
feat(proxy_cli): add --validate_config dry-run flag (#41705)
* feat(proxy_cli): add --validate_config dry-run flag

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

* style(tests): format --validate_config CliRunner calls

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

* fix(proxy_cli): run --validate_config before the ollama auto-start

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

* test(proxy_cli): restore file and add ollama validate_config regression test

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: yassin <yassin@berri.ai>
2026-09-24 17:07:10 -05:00
ryan-crabbe-berri
5e4b1b9df0
fix(proxy): pass team member spend rows as jsonb so a $0 flush cannot poison the pool connection (#43029)
Prisma types a raw array parameter from the first batch a connection sees. After a flush in
which every member cost was a whole number (a free model), the connection's cached statement
expected int8[] and every later fractional batch on it failed with "improper binary format in
array element", so member spend silently stopped landing while team spend kept rising.

The rows now travel as one JSON document unpacked by jsonb_to_recordset with the column types
declared in SQL, so Postgres types the numbers and the batch shape no longer matters.
2026-09-24 20:44:56 +00:00
devin-ai-integration[bot]
77eccaca78
feat(proxy): server-side Team Usage export beyond the top-N key cap (#42996)
* feat(proxy): add uncapped server-side team usage export route

GET /team/daily/activity/export answers the same scoping as
/team/daily/activity/aggregated with one unbounded rollup query, so keys
past USAGE_TOP_API_KEYS_LIMIT are included. Supports daily,
daily_with_keys, daily_with_users and daily_with_models export types as
CSV (default) or JSON. The PTU flat-cost sentinel stays in the plain
daily rollup and is excluded from the keyed and per-model exports

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

* feat(ui): export team usage server-side when the key list was truncated

When the aggregated spend response reports api_key truncation, EntityUsage
passes a serverExport into the export modal that downloads CSV or JSON
from GET /team/daily/activity/export instead of building the file from
the truncated on-screen data. apiClient gains a responseType option so
the download can arrive as a Blob, and truncation no longer blocks the
export button

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

* test(proxy): cover team usage export types, sentinel handling and scope

Unit tests pin the uncapped key rollup past USAGE_TOP_API_KEYS_LIMIT,
PTU sentinel inclusion in the daily rollup and exclusion elsewhere, the
per-user fold, and the CSV column layout. Integration tests exercise the
route against a live proxy, including member scope denial

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

* refactor(proxy): tidy team usage export route

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

* fix(proxy): use membership test for export type branch (PLR1714)

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

* style(ui): format exportBlockedReason test with prettier

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

* fix(proxy): satisfy type-discipline gate in team usage export

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

* fix(proxy): pass export rows as a sequence to the response model

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

* test(proxy-behavior): cover team usage export in the daily activity scope matrix

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

* feat(proxy): carry PTU flat cost and escape formulas in team usage export

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

* fix(ui): keep the truncation export block on surfaces without a server export

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

* chore(ui): drop redundant comments in team export call and modal test

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

* test(integration): audit cells for team usage export

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

* test(integration): tighten team usage export audit cells

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

* fix(proxy): type the export params tuple and fold user keys in one pass

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

* style(ui): bring entity usage export helpers under eslint budgets

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

* style(ui): prettier-format UsagePageView after merge

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

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-24 15:40:36 -05:00
devin-ai-integration[bot]
c2eb549ee6
feat(usage): search team keys beyond the top-N in the Team usage view (#42857) 2026-09-24 15:04:12 -05:00
devin-ai-integration[bot]
5a8ec13786
feat(usage): search keys beyond the top-N usage subset (#42827) 2026-09-24 15:04:03 -05: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
joshua-berri
57eb3ff6b6
fix(mcp): reject origins outside the configured allowlist (#42649)
Co-authored-by: Joshua Valluru <326636767+joshua-berri@users.noreply.github.com>
2026-09-24 10:23:30 -07:00
devin-ai-integration[bot]
e135a199ad
fix(proxy): fail parked DB lookups at a deadline and flip readiness while they stall (#42654)
Some checks failed
LiteLLM Rust / rust-lint (push) Has been cancelled
LiteLLM Rust / rust-test (push) Has been cancelled
LiteLLM Rust / rust-wheel (push) Has been cancelled
* fix(proxy): fail parked DB lookups at a deadline and flip readiness while they stall

Under a load burst with a slow authentication database every request parked inside
the pod with no deadline while /health/readiness kept answering 200 (its own ping
gets a fresh connection), so the load balancer kept sending traffic until the pod hit
its memory limit, and the parked requests completed against the provider minutes
after every client had hung up

Every pre-request read (key, team, user, end user, budget, membership, organization,
object permission, jwt mapping, project, proxy budget, spend counter reseed) now runs
under one deadline, PROXY_DB_LOOKUP_DEADLINE_SECONDS (default 10 s). A lookup that
hits it fails the request with the existing 503 "authentication database is
temporarily unreachable" answer, honours allow_requests_on_db_unavailable, and never
triggers the transport reconnect (the transport is fine, the query is slow), which is
what turned the repro's stall into "too many clients". Writes stay unbounded

A deadline hit marks the pod stalled for PROXY_DB_LOOKUP_STALL_WINDOW_SECONDS
(default 30 s, 0 disables), during which /health/readiness answers 503 with
"db": "stalled" behind the same fail-open gate, so the pod leaves rotation before it
fills its memory. The existing litellm_in_flight_requests gauge already exposes the
parked set on /metrics

The deadline is enforced on the wall clock: bounded_db_lookup waits on the lookup
task with asyncio.wait and raises DBLookupDeadlineExceeded when the deadline passes
even if the lookup absorbs its cancellation, where asyncio.wait_for on 3.12+ would
sit on the cancelled task for as long as it takes

The failure spend-log row no longer re-runs the key and team lookups when the
failure itself is a database connection or deadline error, so a request that hit
the deadline is answered after one deadline instead of two

* fix(proxy): bound the spend counter gate wait and narrow the stalled lookup shortcut

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

* fix(proxy): keep the global spend lookup on the prisma client handle

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

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-24 10:09:49 -05: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
devin-ai-integration[bot]
6dbd65b230
fix(passthrough): log upstream 4xx/5xx error bodies and carry them into the failure hook (#42695)
* test(integration): reproduce passthrough upstream error body missing from logs and spend row

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

* fix(passthrough): log upstream 4xx/5xx error bodies and carry them into the failure hook

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

* fix(error_normalization): let the passthrough prefix win over upstream body text

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

* fix(passthrough): honor message redaction for upstream error bodies

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

* fix(passthrough): bound the upstream error body read and sanitize it before logging

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

* refactor(passthrough): use the Sequence import directly in the allowed-routes cast

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

* fix(passthrough): rechunk the upstream error stream so the preview read stays bounded

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

* test(integration): audit matrix for passthrough upstream error visibility

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

* fix(passthrough): drop the restating docstring on the upstream failure logger

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

* test(integration): drop the retired covers markers from the passthrough error tests

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

* fix(passthrough): keep the upstream status when the error body peek fails

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

* test(passthrough): cover the relay aclose in the mid-read failure test

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

* fix(passthrough): relay decoded partial body on mid-read failure

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-23 23:42:24 -07:00
devin-ai-integration[bot]
093ceb576d
fix(proxy): do not requeue a daily spend batch whose commit already left for postgres (#42786)
* fix(proxy): do not requeue a daily spend batch whose commit already left for postgres

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

* fix(proxy): settle an interrupted daily spend commit from the shutdown flush instead of blocking the cancelled tick

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

* test(integration): burst two workers and SIGTERM during daily spend COMMIT, expect exactly once

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-23 22:54:19 -07:00
devin-ai-integration[bot]
1519032d90
fix(proxy): keep deployment labels on cache-hit post_call guardrail rejections (#42780)
* fix(proxy): keep deployment labels on cache-hit post_call rejections

A post-call failure on a response served from the litellm cache set no first_api_call_start_time, so the failure hook flagged it as rejected before routing and dropped the model_id and provider labels

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

* fix(proxy): read the cache hit from caching_details in the failure hook

model_call_details[cache_hit] is stamped inside the enqueued success handler, so a post-call failure can observe it too early; logging_obj.caching_details is set synchronously before the cached response returns

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

* test(integration): cover cache-hit guardrail reject deployment labels across endpoints, modes and chaos

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

* test(integration): bound the worker-kill reject count by in-flight losses

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

* test(proxy): assert provider and model labels on the cache-hit regression test

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-23 22:48:04 -07:00
devin-ai-integration[bot]
b8154bcbc0
fix(presidio): stream non-Anthropic raw SSE through the post_call hook unbuffered (#42777)
* fix(presidio): stream non-Anthropic raw SSE through the post_call hook unbuffered

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

* test(presidio): keep the pytest.raises block to a single await

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

* refactor(presidio): move raw SSE format check into a helper to keep hook complexity flat

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

* fix(presidio): fold the raw SSE format check into the existing bytes branch to stay within the complexity budget

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

* fix(presidio): decide raw SSE stream shape on a complete first frame, not a transport fragment

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

* test(integration): cover presidio post_call streaming for native gemini passthrough and anthropic messages

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

* fix(presidio): cap first SSE frame coalescing at 64 KiB so an unterminated first event cannot buffer unbounded

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

* fix(presidio): name raw SSE passthrough in the skipped output masking warning

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-23 22:08:23 -07:00
devin-ai-integration[bot]
8e74bb0d23
fix(proxy): document request body and response schemas for the Responses API in OpenAPI (#42802)
* fix(proxy): document responses API request and response schemas in openapi

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

* fix(proxy): namespace colliding openapi defs instead of overwriting existing components

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

* chore(proxy): regenerate lazy openapi snapshot and dashboard schema types

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

* fix(proxy): require model and input in responses schema, document event stream, fix def collision refs

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

* fix(proxy): mark responses request fields readonly required

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

* fix(proxy): reuse existing OpenAPI components when a $defs entry has the same shape

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

---------

Co-authored-by: kerry <kerry@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-24 01:28:34 +00:00
devin-ai-integration[bot]
cad49ee171
fix(proxy): gate disable_global_guardrails on keys and teams to proxy admins (#42699)
* fix(proxy): gate disable_global_guardrails on keys and teams to proxy admins

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

* test: cover metadata smuggle with explicit false and UI toggle gating

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

* test(proxy): satisfy PT017 in resend-stored guardrail flag test

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

* refactor(proxy): keep regenerate_key_fn under the C901 ceiling via a guardrail opt-out helper

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

* chore(ui): regenerate schema.d.ts for guardrail opt-out docstrings

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

* fix(proxy): gate disable_global_guardrails on caller-sent metadata, not server defaults

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

* test(integration): audit cells for disable_global_guardrails admin gate

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

* test(integration): restore contracts.json formatting

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

* test(integration): share guardrail opt-out helpers

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

* fix(ui): hide the team disable_global_guardrails switch from non proxy admins

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

* test(integration): drop covers markers and bound the slow sink check to the sink delay

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-23 18:03:02 -07:00
devin-ai-integration[bot]
1e5403288c
feat(proxy): honor model_info.discoverable on the model listing endpoints (#42825)
Some checks failed
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 / proxy-utils (push) Blocked by required conditions
Unit Tests / caching-local (push) Waiting to run
Unit Tests / core-utils (push) Waiting to run
Unit Tests / enterprise-package (push) Waiting to run
Unit Tests / enterprise-routing (push) Waiting to run
Unit Tests / integrations (push) Waiting to run
Unit Tests / All Other Providers (push) Waiting to run
Unit Tests / Vertex AI (push) Waiting to run
Unit Tests / mcp-integration (push) Waiting to run
Unit Tests / misc (push) Waiting to run
Unit Tests / proxy-auth (push) Waiting to run
Unit Tests / proxy-endpoints (push) Waiting to run
Unit Tests / proxy-extras (push) Waiting to run
Unit Tests / proxy-infra (push) Waiting to run
Unit Tests / proxy-server (push) Waiting to run
Unit Tests / responses-caching-types (push) Waiting to run
GitHub Actions Security Analysis / zizmor (push) Waiting to run
Terraform Modules / fmt, validate, test (gcp) (push) Has been cancelled
Terraform Modules / fmt, validate, test (aws) (push) Has been cancelled
VS Code Extension / vscode-extension (push) Has been cancelled
* feat(proxy): honor model_info.discoverable on the model listing endpoints

A model_list entry marked model_info: {discoverable: false} is left out of GET /v1/models (OpenAI and Anthropic shapes, scope=expand and wildcard routes included), the list path of GET /v1/model/info and GET /model_group/info for every caller without the admin view, while direct requests naming the model keep routing to it. The field defaults to None so an absent flag reads as discoverable and nothing is persisted or echoed for configs that never set it.

* fix(proxy): hide flagged team models under their public name and cover the scope=expand filter

The discoverability lookup now resolves a listed name with the caller's team context, so a team-scoped deployment marked discoverable: false drops out for that team's keys under its public name instead of failing open. The scope=expand branch is now exercised by a team admin caller, and the OCI secrets test builds a real UserAPIKeyAuth instead of a spec mock that has no pydantic fields.

* perf(proxy): resolve only candidate names in the discoverable filter

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-09-23 17:51:16 -07:00
devin-ai-integration[bot]
ce582affaa
fix(mcp): reject duplicate MCP server names and aliases (#42791)
* fix(mcp): reject duplicate MCP server names and aliases

MCP server_name and alias were unchecked at write time, so two servers
could share one tool prefix and tool routing resolved to an arbitrary
winner. Writes now run inside an advisory-locked transaction that
rejects a collision on either column case-insensitively with a 400
naming the colliding identifier, covering create, edit, connector
import and restricted-admin submission. Server reload logs one warning
per identifier already shared in the database.

Co-Authored-By: bot_apk <apk@cognition.ai>

* fix(ui): block duplicate MCP server names and aliases before submit

The create and edit forms now check the normalized name/alias against
the loaded server list (case-insensitive, spaces to underscores, own
row excluded on edit) and show a field error instead of submitting.
Structured proxy error bodies are unwrapped so a 400 no longer renders
as 'Error: [object Object]'.

Co-Authored-By: bot_apk <apk@cognition.ai>

* fix(mcp): check identifier conflicts when an alias is cleared

Clearing an alias drops the tool prefix to the stored server_name, so
that name must go through the conflict check too; an explicit alias:null
is now treated as an identifier write. Also narrows the new db tests to
behavioral assertions instead of pinning prisma where shapes.

Co-Authored-By: bot_apk <apk@cognition.ai>

* fix(mcp): treat an empty alias as a clear in conflict checks

An empty-string alias was written unchecked even though the prefix falls
back to server_name; the update path now treats any falsy alias like a
clear. The edit form likewise compares a cleared alias as empty instead
of re-checking the alias being removed.

Co-Authored-By: bot_apk <apk@cognition.ai>

* test(mcp): cover clearing an alias to an empty string

Co-Authored-By: bot_apk <apk@cognition.ai>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: bot_apk <apk@cognition.ai>
2026-09-23 17:50:27 -07:00
devin-ai-integration[bot]
1175559c39
feat(lint): add LIT013 flagging *-ok suppressions that suppress nothing and remove the 240 stale ones (#42793) 2026-09-23 17:50:09 -07:00
tin-berri
8b001d4024
fix(shadow-eval): replay approved pre-call guardrail snapshots (#42774) 2026-09-23 17:43:54 -07:00
devin-ai-integration[bot]
6593566cdb
fix(key_management): invalidate cached object permissions on key update (#36719)
* fix(key_management): invalidate cached object permissions on key update

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

* fix(key_management): broadcast permission cache eviction and cover key regenerate

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

* fix(key_management): evict object permission rows through evict_and_broadcast

Co-Authored-By: bot_apk <apk@cognition.ai>

* test(integration): cover mcp tool permission widen, narrow and clear on both workers

Co-Authored-By: bot_apk <apk@cognition.ai>

* fix(key_management): evict object permission rows before the key object

Co-Authored-By: bot_apk <apk@cognition.ai>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: bot_apk <apk@cognition.ai>
2026-09-23 15:55:09 -07:00
devin-ai-integration[bot]
4b50e8b236
fix(mcp): keep tool attribution on guardrail-blocked REST calls (#42790)
* fix(mcp): keep tool attribution on guardrail-blocked REST calls

Co-Authored-By: bot_apk <apk@cognition.ai>

* fix(proxy): keep content enforcers in the pre-call walk when guardrails are skipped

Co-Authored-By: bot_apk <apk@cognition.ai>

* test(proxy): accept skip_guardrails kwarg in pre_call_hook test doubles

Co-Authored-By: bot_apk <apk@cognition.ai>

* test(proxy): drop section comment flagged by repo comment policy

Co-Authored-By: bot_apk <apk@cognition.ai>

* test(proxy): drop docstrings from skip_guardrails tests

Co-Authored-By: bot_apk <apk@cognition.ai>

* test(proxy): wrap pre_call_hook mocks under the line limit

Co-Authored-By: bot_apk <apk@cognition.ai>

* refactor(proxy): drop skip_guardrails docstring sentence

Co-Authored-By: bot_apk <apk@cognition.ai>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: bot_apk <apk@cognition.ai>
2026-09-23 15:40:56 -07:00
devin-ai-integration[bot]
3cbb6ebc4a
fix(proxy): apply user_api_key_cache_max_size to the key object partition (#42796)
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-23 17:25:01 -05:00
devin-ai-integration[bot]
49d0ece934
fix(mcp): forward caller bearer on REST oauth_delegate tool calls (#42787)
* fix(mcp): forward caller bearer on REST oauth_delegate tool calls

Co-Authored-By: bot_apk <apk@cognition.ai>

* fix(mcp): only forward caller bearer on REST for client-forwarded-token servers

Co-Authored-By: bot_apk <apk@cognition.ai>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: bot_apk <apk@cognition.ai>
2026-09-23 14:27:54 -07:00
devin-ai-integration[bot]
dc14e76147
fix(mcp): return 401 challenge for REST token-exchange tool calls without a subject token (#42782)
* fix(mcp): return 401 challenge for REST token-exchange tool calls without a subject token

Co-Authored-By: bot_apk <apk@cognition.ai>

* fix(mcp): keep tool_server_mismatch when server_id disagrees with the tool prefix

Co-Authored-By: bot_apk <apk@cognition.ai>

* test(mcp): type the token-exchange challenge test helpers

Co-Authored-By: bot_apk <apk@cognition.ai>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: bot_apk <apk@cognition.ai>
2026-09-23 14:13:16 -07:00
PhimmStraiker
e0af9917a1
feat(guardrails): straiker guardrail speaks the v3 platform API (/api/v3/detect) (#41880)
* feat(guardrails): speak the Straiker v3 platform API (/api/v3/detect)

The Straiker guardrail posted a webhook envelope to /api/v1/detect/webhook.
The v3 platform exposes /api/v3/detect instead, and its integration keys
(sk_agt_…) are rejected by the v1 route with an empty 401, so a tenant on
the v3 platform could not run this guardrail at all. Measured on a
customer gateway on 2026-09-17 after they rotated to a v3 key.

v3 parses the gateway's own traffic server-side, the same contract as
Straiker's unified Kong plugin. So on v3 the guardrail relays: the
request phase posts the provider body LiteLLM received (Anthropic
Messages or OpenAI chat), the response phase posts
{straiker_phase, sse, model, request}, the answer beside the request it
answers, and Straiker derives prompt, answer, agent and archetype. Both
phases also carry the flat prompt / app_response pair: a gateway-mode
integration key scores only the flat pair and an api-mode key only the
relayed body, each ignoring the other, so one payload serves whichever
key the console issued and it is one turn either way (measured on tenant
123, both key modes, 2026-09-18).

- api_version: "v1" | "v3", unset follows the key prefix, so a v3 key
  needs no extra configuration. Explicit override still wins.
- The relayed body is an allowlist of provider fields. The hook sees the
  client body merged with proxy state: `deployment` carries the resolved
  provider credential and `proxy_server_request` the client's own
  Authorization header. Neither travels. Identity survives as the
  metadata subset Straiker's LiteLLM adapter reads.
- Identity never sends a proxy placeholder. `default_user_id` and the
  master-key alias were being forwarded as a user and became the
  session's identity on the platform.
- Headers: x-tool: litellm (ingress), x-straiker-phase, x-straiker-user,
  and x-claude-code-session-id forwarded when the client sent it.
- Verdict: hookSpecificOutput.permissionDecision on the gateway envelope,
  `action` on the flat one; block on block/deny, and on a non-empty
  blocked_by as a backstop. A detect-mode control reads NONE.
- An error status from Straiker is now a webhook failure. LiteLLM's HTTP
  client raises on any non-2xx and the retry loop caught only connection
  errors, so a 401 or 503 from Straiker escaped the guardrail as an
  exception and was relayed raw to the client, bypassing fail_open /
  fail_closed. Retryable statuses retry; the rest are final.
- v1 is unchanged: same envelope, same X-Straiker-Webhook-Format header.

Tests: 15 new, fixtures from the request dict a hook sees on 1.98.0 and
the verdict envelopes the v3 platform returned on 2026-09-18. Each fix
was mutation-checked (handling removed, the test fails). Live: the same
eight-case battery (chat, /v1/messages, streaming, tool call; benign,
injection, PII) passes on a gateway-mode and an api-mode key, blocks at
pre_call with the tenant's block message, and lands under the declared
agent with the end user attributed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(guardrails): name the agent per application on v3 (x-s6r-agent)

One integration key can front several applications. Straiker enumerates them
as separate agents when the turn names one, which is what the unified Kong
plugin sends as x-s6r-agent. Without it every application on a gateway
collapses onto a single agent.

- Forwards a client-supplied x-s6r-agent.
- New `agent_ref` config names one agent for a route when the client sends
  nothing. The client wins, matching Kong's precedence.
- Neither set: no header, and the platform derives the agent from the traffic.

Verified live on tenant 123 against an integration whose connector is
`gateway`: three distinct values minted three observed agents, and a turn
with no hint derived one from the traffic shape. An integration whose
connector is `custom-agent` declares its agent, so every turn attributes to
that one agent and the hint is ignored (agent_ref_source: attested).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(guardrails): which v3 shape is scored depends on the connector, not the key mode

The earlier comment said a gateway-mode key scores only the flat pair. Re-measured
on tenant 123 across all three integration types with one injection prompt:

  custom-agent connector (Add Agent)  raw body ignored   flat prompt scored
  gateway connector                   raw body scored    flat prompt scored
  api mode                            raw body scored    flat prompt ignored

Behaviour unchanged: the payload already carries both shapes, which is why it works
on every type. Comment only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(guardrails): send exactly what the unified Kong plugin sends on v3

The v3 platform parses the gateway's traffic itself and derives agent,
archetype and identity from it. The earlier commits added to the relayed
body (a flat prompt / app_response pair, source, user_name) and to the
headers (x-tool, x-straiker-phase, x-straiker-user). None of that is in
the Kong v0.12 contract, and traffic through this guardrail was not
classifying by shape the way the same traffic through Kong does. Match
Kong byte for byte and leave classification to the platform.

Request phase: the provider body, plus session_id and
original.processed.Meta.user. Response phase: {straiker_phase, sse,
model, request} plus the same two. No flat fields, no phase or user
headers, no x-tool.

Session id follows Kong's precedence: the client's x-claude-code-session-id,
then the session LiteLLM resolved, then an md5 of system prompt + first
message so a conversation that states no session still groups across its
replays.

Routing hints complete the Kong set: x-s6r-agent (client header, else
`agent_ref`), and new `client` (x-s6r-client) and `format_hint`
(x-s6r-format) config, both optional.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(guardrails): sort imports in the v3 session test

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(straiker): send a streamed Messages answer back in the Messages shape on v3

On a streamed /v1/messages call the proxy rebuilds the answer as a chat
completion before the post-call hook runs, and that is what the plugin put in
the response envelope's sse field. Straiker's coding-agent reader parses a
Messages answer, so a Claude Code turn relayed this way came back
coding_agent/claude with no session and zero events scored: the model's tool
calls were never screened on the response phase. Captured live on 2026-09-18
against tenant 123, a real Claude Code Bash tool call through the proxy.

The proxy's own Anthropic adapter turns the rebuilt answer back into a Messages
response when the call arrived on the anthropic_messages route, which is what a
transport relay forwards. Chat completions calls keep the chat completion shape
and a buffered Messages answer is relayed untouched.

The regression test's fixture is the chat completion the proxy actually built
for that captured turn. After the fix the same turn scores on the response
phase (session resolved, one event, the Bash tool_use block present).

* style(straiker): ruff format the v3 guardrail and its tests

* refactor(straiker): one attempt per call in the webhook retry loop

The HTTPStatusError branch added for v3 duplicated the non-200 branch and put
_post_webhook over the strict complexity ceiling. One attempt is now its own
method that returns the verdict or a failure marked retryable, and the loop only
decides whether to try again. Behaviour is unchanged: retryable statuses and
transport errors retry, everything else is final.

* fix(straiker): name Claude Code's client and agent on v3 so its session lands under one coding agent

Straiker types a gateway turn as a coding agent from the "You are Claude Code"
preamble, which only the main agent turns carry. Claude Code's title and
topic-detection sidecars have their own system prompts, so they resolved by
shape as autonomous, and because they share the session id with the main turns
the whole session was filed under Autonomous rather than under a coding agent.
Kong does not hit this because its plugin config names the client and agent on
every call.

The User-Agent (claude-cli/...) is on every call including the sidecars, so the
plugin now reads it and sends x-s6r-client: claude plus, when the route names no
agent, x-s6r-agent: "Claude (LiteLLM)". A client-supplied x-s6r-agent or the
agent_ref config still wins. Verified live on tenant 123: a real Claude Code
session now lands as one coding_agent labelled "Claude (LiteLLM)" with its turns
scored, where before it split across Autonomous.

Identity: the key's own user (email then id) now outranks the end user the
request named. LiteLLM resolves Claude Code's hashed metadata.user_id as the end
user when nothing better is set, so a per-user key was being shadowed by a
session token. The key is the authenticated principal, the way a Kong consumer
is, so it wins; the request end user is the fallback.

* refactor(straiker): build the v3 request, envelope and headers as frozen mappings

The v3 builders seeded dicts and grew them, which the type-discipline gate
counts as mutable accumulators. Each is now one expression over a tuple of
pairs, frozen with MappingProxyType, and the JSON encoder unwraps a frozen
mapping through a default. The session seed and the verdict parser no longer
rebind locals. The wire is unchanged: 36 live calls through the proxy on this
commit carry the same fields, shapes, headers and identities as before, with
no mappingproxy text in any body.

* fix(straiker): satisfy basedpyright on the v3 builders

The frozen-mapping refactor left a shadowed headers local, a Mapping handed to
an HTTP client that takes a dict, an unguarded optional response, a turn id
typed object, and a redundant isinstance on already-typed texts. No behaviour
change: 4 live calls (chat, Messages, Bedrock, injection) return 200 with the
expected verdicts on this commit.

* fix(straiker): type the v3 config fields at the initializer and keep the verbose log as JSON

The four v3 routing fields (api_version, agent_ref, client, format_hint)
travelled through the untyped kwargs passthrough, which basedpyright counts
against the budget. They are now validated through a small Pydantic model at
the initializer and passed by name.

The verbose log serialized the frozen payload with default=str, which printed
a Python repr instead of JSON once the builders returned MappingProxyType.
Every serializer now unwraps a frozen mapping first. A test asserts the logged
payload parses as JSON and carries the identity; mutating the log site back to
default=str fails it.

* fix(straiker): address review findings on the v3 relay

Text completions relay their prompt: `prompt`, `suffix`, `echo` and `best_of`
join the provider allowlist, so /v1/completions traffic is screened.

The route's `agent_ref` now outranks the caller's `x-s6r-agent` header. The
header is caller-supplied, and letting it beat a pinned route would let any key
file its traffic under another application's agent and controls. On a route
that names nothing the header still names the application, which is how
several applications enumerate behind one key.

Credentials inside `tools` and `mcp_servers` (an OpenAI `mcp` tool's `headers`,
Anthropic's `authorization_token`) are replaced with `[redacted]` before the
body leaves the proxy, on both phases and in the verbose log. Detection reads
tool names, descriptions and schemas, never these.

A 200 whose body is valid JSON but not an object now reports an invalid
schema and follows the failure policy instead of raising out of the hook.

Comments that restated a constant are gone. Tests cover each change and the
failure paths (unreadable error body, client exceptions, missing response,
unmodellable request, session seeds from Anthropic block shapes); every fix
fails its test when reverted.

* fix(straiker): scrub tool credentials one level deep, without recursion

* fix(straiker): scrub only the fields that carry a credential, never a schema

The credential set is now the three fields that actually hold one on a tools
or mcp_servers entry (headers, authorization, authorization_token), read one
level deep. A function tool whose parameter schema defines a token, headers or
api_key property is relayed exactly as sent; a test pins that, and fails
against the recursive version.

* test(straiker): use example.com identities; drop a comment that restated its branch

* fix(straiker): present a legacy completion as the chat exchange it is

Straiker scores chat on both phases of a gateway turn but has no reader for a
text_completion answer: the request phase of a /v1/completions call was
scored and the response phase was refused with 501, whether or not the call
named an agent. A completion is one user turn and one assistant turn, so both
phases now present that exchange: the prompt becomes the single user message
and the TextCompletionResponse becomes a chat completion. Measured through the
proxy on this commit, both phases return 200 and score, and the derived
session is shared between them.

The derived session seed accepts the tuple the conversion produces; the test
pins the session on both phases and fails against the list-only check. The
unreachable "parsed is None" branch is folded into the failure branch, and a
malformed tools value is shown to relay as sent.

* fix(straiker): screen a completions prompt as the text the model receives

LiteLLM's /v1/completions accepts a string, a list of strings, a list of
token ids or a list of token-id lists, and decodes token ids with the
text-davinci-003 tokenizer before calling the model. The relay now renders
the prompt the same way, one user message per prompt, so a pre-tokenized
prompt is screened as the text it stands for rather than as digit strings.
A prompt in a shape this cannot render (empty, mixed, or with no tokenizer
available) is relayed untouched instead of being replaced with something
else. Tests cover all four accepted shapes and six unrenderable ones.

* fix(straiker): seed the derived session on the preamble and the first user turn

An OpenAI chat body carries its system prompt as messages[0], and the derived
session seeded on the Anthropic `system` field plus messages[0] with no role
check. For that shape the seed was the system prompt twice and the first user
turn never counted, so every unnamed conversation behind one system prompt
collapsed into one Straiker session. The seed now takes the preamble from
wherever the API puts it (`system`, `instructions`, or a leading system or
developer message) and the first message with role `user`, else a Responses
`input` string, else `prompt`. Two conversations sharing a system prompt are
two sessions again; a replayed conversation stays one.

* fix(straiker): seed the derived session on every text block of the first turn

A user turn that opens with an image or a document block and carries its
text later seeded the session on an empty string, so two different
conversations under the same preamble shared one Straiker session. Read
every text block of the turn instead of only the first block. A plain
string or a single text block seeds exactly as before.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(straiker): cover the tokenizer fallback, a textless first turn and Responses instructions

Three branches of the v3 relay had no test: a token-id prompt relayed as
sent when the tokenizer cannot be fetched, a first user turn with no text
seeding the session on the preamble alone, and a Responses API body
seeding on its instructions and first input turn. Each test fails when
its branch is mutated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(straiker): seed the derived session on the principal as well as the conversation

Straiker skips turns it has already scored for a session. The derived
session hashed the system prompt and the first user turn alone, so two
users who opened a conversation with the same words shared one session,
and the second user's copy of an attack came back as a replay: unscored
and allowed. Measured live on 2026-09-20: the first user's SSN turn was
blocked (`social_security_number`, scored=2), the second user's identical
turn was allowed (`controls: []`, replayed=2).

The principal now joins the seed. Explicit session ids, the Claude Code
header and LiteLLM's own session are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(straiker): derive the session id with sha256 and drop comments that restated constants

The derived session now hashes the principal, and CodeQL flags MD5 over an
identity as a weak hash on sensitive data. SHA-256 truncated to the same
32 hex characters keeps the id shape. Comments that only labelled the
allowlist groups or restated a constant are removed; the two that explain
a non-obvious choice stay.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(straiker): keep a blocked conversation blocked when it is replayed

Straiker de-duplicates turns it has already scored per session and
answers a replay `allow`, whatever the first verdict was. A client that
resends a blocked request, or grows the conversation past the blocked
turn, was let through: measured on 2026-09-20, `block` then `allow,
events_replayed=2` for the same session and body, and Claude Code's
automatic retry after the 400 turned a blocked poisoned-file read into
a pass.

The guardrail now remembers, per session, a fingerprint of every
conversation it blocked (a bounded, day-long in-memory cache) and blocks
a request that repeats or extends one without asking again. A different
session with the same words is a new conversation and is scored afresh.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(straiker): scope the block memory by session or principal, never by content alone

A request with no derivable session keyed the replay memory on the
conversation fingerprint alone, so one caller's block could answer
another caller's identical request. The memory is now scoped by the
session, else by the principal, and a request with neither is not
remembered at all.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(straiker): remember only a block that names a control, never one that comes from state

The replay memory kept every block, including one the platform returns
because a kill switch is engaged (`action: block` with `blocked_by: []`).
An administrator lifting the kill switch then left the conversation
refused by the remembered copy: measured on 2026-09-21, traffic stayed
blocked after `POST /inventory/agents/{id}/restore` returned `engaged:
false`.

The same words are the same attack tomorrow, so a control-named block is
still worth remembering; state is not ours to cache. The parsed verdict
now carries `blocked_by` so the two can be told apart.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Phimmasone Phonpaseuth <PhimmStraiker@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-23 12:21:09 -07:00
devin-ai-integration[bot]
b0407ad33e
ci: add merge smoke checks workflow with loopback-only harness and 11 curated cases (#42709)
* ci: add dashboard and core smoke checks across supported Python versions

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

* ci: tighten merge smoke harness and keep mapped test diffs additive

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

* ci: terminate proxy on readiness timeout and use contextlib.suppress in teardown

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

---------

Co-authored-by: yuneng <yuneng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-23 11:01:08 -07:00
devin-ai-integration[bot]
cddc53464e
test: deflake fuzzy picker, breached-password HIBP, and MCP stdio timeout tests (rolling deflake 2026-09-22) (#42125)
* test(autoroute): wait for a valid fuzzy selection index and cancel the prompt on driver failure

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

* test(autoroute): read the fuzzy selection through the public InquirerPy property

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

* test(proxy): inject the HIBP client into change_password so the breached-password test never touches the network

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

* test(mcp): only use the 200ms read timeout in the silent mode of the transport completion test

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

* test(proxy): record HIBP requests so the ordering test asserts no lookup happened

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

* ci(codeql): filter the weak-sensitive-data-hashing false positive on the HIBP k-anonymity lookup

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-23 08:44:59 -07:00
devin-ai-integration[bot]
19556952d9
feat(secrets): route secret resolution through native Rust backends (#42619)
* fix(secrets): verify provider API request and payload contracts

* wip

* fix(secrets): unify backend reads and route secret resolution

* feat(secrets): bind built-in managers to retained Rust backends

* refactor(secrets): centralize catalog dispatch and native binding

* test(secrets): split provider integration tests

* refactor(secrets): enforce cache and rotation contracts

* test(secrets): stub parent packages in failing resolver fixture

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

* refactor(secrets): pass manager settings through the interop boundary

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

* fix(secrets): align cloud KMS auth and harden provider reads

* ci(rust): raise native wheel size gate to 40 MB for secrets backends

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

* fix(proxy): treat unset google kms flag as disabled like the old loader

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

* fix(secrets): preserve certificate credentials and disabled KMS flags

* test(secrets): cover certificate validation and bounded auth retries

* test(secrets): cover Python dispatch without the native extension

* test(proxy): skip legacy secret manager cases when the optional SDK is missing

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

* fix(secrets): port Python parity tests and preserve provider behavior

* fix(secrets): store the captured native config without setattr to satisfy the strict lint budget

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

* fix(secrets): preserve missing Azure manager values

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

* test(secrets): pin typed values and recovery failure precedence

* refactor(secrets): organize provider internals and behavioral test suites

* refactor(secrets): simplify recovery and isolate Python compatibility

* fix(secrets): distinguish Azure callback absence from HTTP not found

* fix(secrets): preserve Python AWS read results at the bridge

* fix(secrets): route public reads through the native catalog bridge

* fix(secrets): keep JSON selection outside the bridge

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

* fix(secrets): preserve provider JSON reads at the bridge

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

* fix(secrets): preserve Python primary JSON semantics

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

* fix(secrets): preserve CyberArk mutation behavior through the native bridge

* docs(secrets): record public API replacement gaps

* refactor(secrets): share Vault write payload preparation

* feat(secrets): route Vault mutations through the native bridge

* fix(secrets): preserve typed Vault rotation failures

* refactor(secrets): move Python dispatch into bridge

* refactor(secrets): move CyberArk Python policy into bridge

* refactor(secrets): move Vault Python policy into bridge

* test(secrets): assert Vault rotation request paths

* fix(secrets): keep bridge JSON interop centralized

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

---------

Co-authored-by: Yujong Lee <yujong@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-23 08:24:57 -07:00
devin-ai-integration[bot]
5028f9ec59
fix(proxy): validate model credential name only when it changes (#42701)
PATCH /model/{id}/update rejected read-modify-write edits that resent an unchanged but dangling litellm_credential_name. Existence validation now runs only when the requested name differs from the stored one; empty string and non-admin detach rejections are unchanged

Co-authored-by: yuneng <yuneng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-23 07:34:32 -07:00
Oliver Jensen
bc3b5b1d5b
fix(proxy): revoke UI session tokens on logout and password change (#42463)
* fix(proxy): revoke UI session tokens on logout and password change

Adds POST /session/logout to revoke the presented UI session key server
side (previously logout was client-side only and the key stayed valid
until expiry). Password changes now revoke the user's other UI sessions:
self-change keeps the caller's session, admin reset and onboarding claim
revoke all. The BYOK OAuth cookie auth now re-resolves the embedded key
against the DB so revoked sessions get a 401.

* fix(proxy): satisfy B008 budget and backend allowlist for /session/logout

* refactor(proxy): satisfy type-discipline budget in session_endpoints
2026-09-23 10:31:38 +02:00
devin-ai-integration[bot]
40ec84caa2
fix(proxy): publish auth cache invalidations in the background so a wedged coordination Redis cannot stall user updates (#42534)
* fix(proxy): bound auth cache invalidation publish so a wedged coordination Redis cannot stall user updates

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

* fix(proxy): resolve publish callable at call time in evict_and_broadcast

The keyword-only default bound publish_auth_cache_invalidation at
function-definition time, so tests patching the module attribute observed
zero calls. Default to None, resolve the real publisher inside the body,
and keep the keyword-shaped cache_key call the existing contract asserts

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

* fix(proxy): publish auth cache invalidations in the background so a wedged coordination Redis costs handlers nothing

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

* fix(proxy): cap in-flight auth cache invalidation publishes so a wedge cannot drain the redis pool

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 23:33:32 -07:00
devin-ai-integration[bot]
320ad73f56
fix(policy_engine): keep inherited parent guardrails when a child policy condition misses (#42548)
* fix(policy_engine): keep inherited parent guardrails when a child policy condition misses

Attachment applicability now walks the policy inheritance chain, so an attached child whose own condition does not match still contributes the guardrails of its unconditional ancestors, and a non-default attachment that applies through an ancestor still suppresses default attachments. The resolver continues to skip only the chain members whose own condition fails

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

* fix(policy_engine): skip a policy's pipeline when its own condition misses

resolve_pipelines_for_context returned the pipeline of a matched policy without evaluating its own condition, so a condition-missing child admitted by the chain-aware matcher still ran its pipeline. It now mirrors resolve_policy_guardrails and drops the pipeline when the policy's own condition does not match.

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

* test(policy_engine): property test that chain matching only widens to applicable ancestors

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

* feat(policy_engine): log policies admitted only through an inherited ancestor

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

* fix(policy_engine): log ancestor admissions once per attachment scan

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 23:27:38 -07:00
devin-ai-integration[bot]
f44052d87b
fix(vector_stores): keep config-defined vector stores listed and read-only (#42574)
* fix(vector_stores): keep config-defined vector stores listed and read-only

Vector stores declared in config.yaml were purged from the in-memory registry by /vector_store/list because the database was treated as the only source of truth. Config-defined stores now carry is_config=True, stay in the list beside database rows, are never overwritten or evicted by database state, and reject /vector_store/new, /vector_store/update and /vector_store/delete with 400. The Admin UI renders them read-only

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

* fix(ui): show vector store source and read-only state for config-defined stores

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

* test(integration): audit config-owned vector stores across list, writes, search, authz, peers and redis outage

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

* fix(ui): show a visible read-only hint in the config vector store actions menu

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-23 04:02:16 +00:00
yuneng-jiang
b395bfefdd
fix: repair seven regressions caught by CircleCI on main (#42640)
* fix: repair seven regressions caught by CircleCI on main

- vertex_ai: stop treating fine-tuned endpoint ids (numeric or
  vertex_ai/gemini/<id>) and gemma models as Gemini 3+, which injected
  temperature=1.0 and Gemini 3 thinking config into their requests (#42465)
- cost: price Azure DALL-E 3 from its azure/<quality>/<size>/dall-e-3 rows;
  it only worked through the OpenAI rows that #42435 removed
- bedrock: stream bedrock/invoke/moonshot through an OpenAI-shaped chunk
  decoder; the generic decoder dropped every chunk, which the
  supports_response_schema flag from #42338 un-skipped in CI
- proxy: keep the public model_group on pre-routing rejections so the Usage
  page groups them under the model name, not the deployment (#41077)
- cost map: mirror the base rows' capability flags onto Bedrock regional and
  cross-region copies (#42254 and later syncs)
- whitelist the new regional Bedrock rows from #42543 and #42588 for the
  converse routing check, following the existing regional-row convention

* fix(model-prices): mirror capability flags onto ap-southeast-3 bedrock rows

* refactor(bedrock): tighten types on the moonshot stream decoder and its tests
2026-09-23 02:26:36 +00:00
devin-ai-integration[bot]
327515a3ba
fix(mcp): preserve credential authority in DCR bridge authentication (#42563)
* fix(mcp): admit dcr_bridge envelope alongside an explicit litellm credential and mint under jwt principals

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

* style(mcp): suppress LIT002 on concrete dict header payloads

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

* fix(mcp): mint bridge envelope for jwt mapped to a key without a user_id

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

* fix(mcp): mint and admit bridge envelopes under the master key

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

* fix(mcp): bind mapped JWT envelopes to stored key tokens

* fix(mcp): preserve master envelope scope enforcement

* fix(mcp): reject bridge minting that loses JWT restrictions

---------

Co-authored-by: joshua <joshua@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joshua Valluru <326636767+joshua-berri@users.noreply.github.com>
2026-09-22 18:09:52 -07:00