Commit graph

40462 commits

Author SHA1 Message Date
devin-ai-integration[bot]
190ea0802d
fix(spend): sum multi-round session cost in logs UI (#32796)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
2026-07-10 10:44:33 -07:00
yucheng-berri
b8bb95be8d
fix(spend-logs): honor store_prompts_in_spend_logs for guardrail_information (LIT-4314) (#32688)
* fix(spend-logs): honor store_prompts_in_spend_logs for guardrail_information (LIT-4314)

_get_spend_logs_metadata passed guardrail_information entries through
verbatim, so guardrail hooks that echo the LLM request into
guardrail_response leaked the raw prompt into LiteLLM_SpendLogs.metadata
regardless of store_prompts_in_spend_logs. This mirrored the pre-existing
gap for the other prompt-carrying fields (vector_store_request_metadata,
error_information, etc.), which already sanitize via
_should_store_prompts_and_responses_in_spend_logs.

Add _sanitize_guardrail_information_for_spend_logs alongside the other
per-field sanitizers and wire it into _get_spend_logs_metadata. When the
flag is False the sanitizer replaces guardrail_request and
guardrail_response with REDACTED_BY_LITELM_STRING while preserving every
other typed field on the entry (name, provider, mode, status, timings,
action, violation_categories, risk_score, masked_entity_count, ...) so
guardrail dashboards keep working. When the flag is True (or the field
is None) the entries pass through unchanged.

Widen StandardLoggingGuardrailInformation.guardrail_request from
Optional[dict] to Optional[Union[dict, str]] so the redacted sentinel
satisfies the TypedDict without needing a cast; guardrail_response
already accepted str.

Regression tests cover the three cases (flag=False redacts,
flag=True passes through, None passes through) plus an end-to-end
get_logging_payload path that fails if the wire-in at line 139 is
reverted.

* chore(spend-logs): review nits (one-shot dict build, scrub identifier in tests)

- _redact_prompt_fields_in_guardrail_entry now returns the redacted
  dict in one expression instead of seed-then-mutate (TYPE-3)
- swap the illustrative guardrail_name in the new test fixtures for
  a generic 'demo-echo-guard' identifier

* chore(spend-logs): only redact guardrail prompt fields when caller supplied them

Greptile P2: the sanitizer was unconditionally writing REDACTED_BY_LITELM
into both guardrail_request and guardrail_response on the copy, so entries
that never carried one of those fields (e.g. a guardrail that only emits
a guardrail_response) came out with a phantom guardrail_request key added.
Guard both assignments with an in-check so the output shape is stable.
Add a mutation-checked regression test that fails if either guard is
removed.

* fix(spend-logs): also redact match_details and classification in guardrail_information

The initial LIT-4314 fix redacted guardrail_request and guardrail_response,
but two other typed fields on StandardLoggingGuardrailInformation also
carry raw prompt content when a first-party guardrail populates them:

- litellm_content_filter/content_filter.py:1676 sets classification =
  dict(CompetitorIntentDetection), whose evidence[*].match is a substring
  taken directly from the user's normalized prompt (see
  litellm_content_filter/competitor_intent/base.py:184-194).
- block_code_execution/block_code_execution.py:571 sets match_details =
  guardrail_response = [dict(d) for d in detections], where detections
  carry the fenced-code-block content extracted from the user's message.

Reproduced live against localhost:4000 with store_prompts_in_spend_logs
false and a custom guardrail passing tracing_detail with both fields:
before this commit the raw prompt shows up in metadata.guardrail_information[0]
under match_details and classification; after, both are the sentinel.

Widen the two TypedDict fields to Optional[Union[..., str]] so the
sentinel string satisfies the schema without a cast, and consolidate
the redaction set into a tuple so future prompt-carrying additions are
one-line changes.

* fix(spend-logs): normalize non-list guardrail_information shapes in sanitizer

xecguard's logging hook (xecguard.py:246) assigns a bare dict to
standard_logging_object['guardrail_information'] instead of a list,
violating the typed contract Optional[List[StandardLoggingGuardrailInformation]].
Without defensive normalization, _sanitize_guardrail_information_for_spend_logs
iterates the dict's string keys and _redact_prompt_fields_in_guardrail_entry
raises TypeError on {**'guardrail_name'}, which get_logging_payload's
downstream update_database catches with a broad except and silently drops
the entire spend-log write for that request.

Normalize a bare-dict input to a single-item list at the sanitizer's
entry point, and skip any non-dict entries defensively (matching OTEL's
existing isinstance filter at opentelemetry.py:1751-1753 for the same
field). Downstream readers already model this defensively; make the
spend-log write path match.

The root cause is xecguard's writer, not the sanitizer. That is being
tracked as a separate ticket; this PR keeps xecguard-enabled deploys
from silently losing spend logs when store_prompts_in_spend_logs=false.

* fix(types): declare guardrail Union members str-first to avoid poisoning typing cache

CPython's typing module caches Union[...] order-insensitively (first-
construction wins), and litellm/types/utils.py has no 'from __future__
import annotations', so its unions are constructed eagerly at import
time -- before any proxy model. Declaring guardrail_request,
classification, and match_details with dict-first ordering seeds the
typing cache with a dict-first tuple, and later proxy models that
declare custom_llm_provider / model_aliases / vertex_credentials as
Optional[Union[str, dict]] pick up the same dict-first object.

Downstream, Pydantic's get_args() then reports anyOf in dict-first
order, FastAPI emits the OpenAPI accordingly, and 'npm run gen:api'
produces a schema.d.ts diff on unrelated fields, tripping the schema-
sync CI check.

Behaviorally identical in Python and at the wire; the flip only reorders
the union members so the first construction matches how the codebase
had always declared these unions, and 'npm run gen:api' now produces a
zero diff against the committed schema.d.ts.
2026-07-10 10:44:10 -07:00
tin-berri
bf02a4a47f
test: add /v1/messages to supported_endpoints schema enum (#32739) 2026-07-10 00:23:16 -07:00
ryan-crabbe-berri
2a12707372
refactor(ui): colocate the policies view, keeping PolicySelector shared (#32720)
* refactor(ui): colocate the policies view, keeping PolicySelector shared

Group-1 colocation split. The policies folder lived in the shared src/components
dump but is only partly shared: PolicySelector (used by the Playground,
ComplianceUI, and key edit view) and its types stay in @/components/policies,
while the policy-management view (27 files: index, tables, forms, modals,
pipeline builder, and their tests) moves to policies/_components.

Moved files' imports of the retained shared files become @/components/policies
paths; other escaping relative imports are absolutized against the @/ alias
(including @/data/... and the repo test-utils via @/../tests/...), and moved
test files have both their `from` imports and `vi.mock` paths rewritten so the
mocks keep matching the source. Grandfathered lint suppressions for moved files
are re-keyed. PolicySelector did not move, so its external consumers are
untouched. No behavior change.

* fix(ui): keep PolicySelector.tsx eslint suppression at its original path

PolicySelector.tsx stays in src/components/policies (only the management view
moved to _components), but the suppression re-key wrongly moved its
no-nested-ternary entry to the phantom _components path, orphaning the real
file's suppression. Revert that one key. (Greptile P1 on #32720.)
2026-07-09 22:44:32 -07:00
ryan-crabbe-berri
3d5d5e1295
refactor(ui): colocate tag-management and vector-stores views, keeping the shared selectors (#32719)
Two of the group-1 colocation splits. Each of these folders lived in the shared
src/components dump but is only partly shared: the page's management view is
segment-owned, while a selector widget is reused by other features. So this
splits them rather than moving wholesale.

tag-management: TagSelector (used by playground) and its types stay in
@/components/tag_management; the management view (index, tag_info, TagTable,
CreateTagModal) moves to tag-management/_components.

vector-stores: VectorStoreSelector (used by organizations and playground) and
its types stay in @/components/vector_store_management; the rest of the
management UI moves to vector-stores/_components.

The moved files' imports of the retained shared files are rewritten to absolute
@/components paths, escaping relative imports are absolutized, and moved test
files have both their `from` imports and `vi.mock` paths rewritten to match.
Grandfathered lint suppressions for moved files are re-keyed. The external
consumers of the selectors are untouched (the selectors did not move). No
behavior change.
2026-07-09 22:19:11 -07:00
ryan-crabbe-berri
27cf064556
refactor(ui): colocate cost-tracking and prompts components into _components/ (#32716)
Renames each segment's local components/ folder to _components/ (private to the
route, matching Next's _ route-exclusion). Both folders are imported only by
their own page.tsx via the folder index (verified zero external importers
across src, tests, and e2e_tests), so each is a straight rename plus repointing
that one index import; a folder rename keeps every file at the same depth, so
all internal and relative imports are unaffected.

Grandfathered lint suppressions under the two folders (31 entries: cost-tracking
15, prompts 16) are re-keyed to the new paths with counts unchanged. No behavior
change.
2026-07-09 21:34:04 -07:00
devin-ai-integration[bot]
45f9beed2a
ci: skip backend unit tests on ui-only PRs without stranding required checks (#32532) 2026-07-09 20:50:25 -07:00
devin-ai-integration[bot]
f90b3efb2e
feat(models): add Azure GPT-5.6 (sol/terra/luna) pricing and metadata (#32678) 2026-07-09 20:46:21 -07:00
devin-ai-integration[bot]
d82645d163
feat: add Meta Model API provider and muse-spark-1.1 (day-0) (#32701) 2026-07-09 20:45:27 -07:00
yucheng-berri
74623b12b1
fix(guardrails): mask credentials embedded in guardrail_response before persist (LIT-4314) (#32687)
Team-level callback_vars (e.g. langsmith_api_key) get spread into
data["metadata"] as four aliases (user_api_key_metadata,
user_api_key_team_metadata, user_api_key_auth_metadata,
user_api_key_auth). When a guardrail hook echoes that metadata into
its guardrail_response, the plaintext credential landed five times
inside LiteLLM_SpendLogs.metadata.standard_logging_guardrail_information[i].guardrail_response
and every downstream sink that reads it (OTel via emit_guardrail_span,
Langfuse, custom loggers).

Add a purpose-built payload walker (mask_credentials_in_payload) that
only masks strings under sensitive-named keys and preserves every
other value (None, ints, floats, bools, tuples, typed objects) verbatim.
The walker reuses SensitiveDataMasker.is_sensitive_key so the pattern
list stays in one place, and unwraps Pydantic models via model_dump()
so nested UserAPIKeyAuth values reached by the walk get scanned as
plain dicts (they are JSON-serialized downstream anyway).

Apply the walker at add_standard_logging_guardrail_information_to_request_data
after the existing secret_fields pop and match/regex redaction, so
every downstream sink sees masked values from a single seam.
2026-07-09 20:26:22 -07:00
mubashir1osmani
913314a0e9
test(e2e): lower realtime server-VAD threshold to 0.5 (#32710)
At threshold 0.8 azure gpt-realtime fires speech-stop and creates a response, but the committed audio is clipped enough that the response comes back empty (0 transcript, 0 audio), failing the audio-input assertions deterministically. Dropping to 0.5 captures the full utterance so the model produces real content. Verified against a live proxy: 0.8 yields empty responses, 0.5 yields transcript and audio. openai tolerated 0.8; azure did not
2026-07-09 19:24:51 -07:00
mubashir1osmani
560253b163
test(e2e): replace deprecated batch/realtime models (#32698)
azure batch used azure/gpt-4.1-mini-batch; gpt-4.1-mini is deprecating (2026-11-04)
and can no longer be deployed, so point it at gpt-5.4-mini (Global Batch) and bump
the api_version to 2025-04-01-preview. Requires an Azure Global Batch deployment
named gpt-5.4-mini-batch plus AZURE_API_BASE/AZURE_API_KEY on the proxy.

xai/grok-4-1-fast-non-reasoning is deprecated (2026-05-15); update the commented
xai realtime provider and the coverage-matrix doc to xai/grok-4-1-fast.
2026-07-09 18:40:05 -07:00
yuneng-jiang
54df4f5fab
Merge pull request #32560 from BerriAI/litellm_/org-admins-team-budgets-1d4b26
fix(proxy): resolve team org from team_id so org admins can update team budgets
2026-07-09 18:37:22 -07:00
ryan-crabbe-berri
592510ec18
feat(ui): shadcn charts foundation with tremor-compatible wrappers (#32668) 2026-07-09 18:18:52 -07:00
tin-berri
eec948dcb8
Merge pull request #32652 from BerriAI/litellm_mcp_stale_token_invalidation
fix(mcp): invalidate a browser-authorized upstream token when a mint-relevant field changes
2026-07-09 18:13:19 -07:00
ryan-crabbe-berri
65d90fd5cf
refactor(ui): colocate 11 route segments' components into _components/ (#32704)
Colocation follow-up to the App Router migration: move each page's owned
components out of the shared src/components dump and into its route segment's
_components/ folder, draining the shared bucket. Convention: a component used
by exactly one segment goes in that segment's _components/ (private, matching
Next's _ route-exclusion); a component shared by 2+ segments stays in
@/components. No new _shared/ folder.

Rename-in-place (segment already had a local components/ folder):
- api-reference (also relocates the shared CodeBlock, used by playground and
  cost-tracking, to @/components/CodeBlock)
- memory, budgets, access-groups
- caching, projects, guardrails-monitor

Extract from src/components (page view lived in the shared dump):
- AdminPanel -> admin-panel, organizations -> organizations,
  general_settings -> router-settings, usage -> old-usage

Each folder/view was verified to have no importer other than its own page
(cross-checked across src, tests, and e2e_tests). Relative imports inside moved
single files are rewritten to absolute @/components/*; colocated tests move with
their subject and have their vi.mock paths rewritten to match. Grandfathered
lint suppressions (tremor, react-hooks, and similar, all pre-existing) are
re-keyed to the new paths with counts unchanged. No behavior change.
2026-07-09 17:43:33 -07:00
Tin
db8c872c7d fix(ui): staged edit preview sends explicit oauth2_flow and spec_path; invalidation clears the tool list
The preview endpoint infers client_credentials when the inherited client_id, client_secret, and
token_url are all present (common once DCR or discovery filled them) and then strips the forwarded
bearer to preview as M2M, so the staged interactive token was silently unused; sending
oauth2_flow=authorization_code bypasses the inference. spec_path now rides along so OpenAPI servers
take the spec-based preview path the create form gets. clearHeldOAuthToken also empties the tool
list, mirroring the create form's clearTools, so a preview fetched with the discarded token never
lingers while the refetch is in flight
2026-07-09 16:29:17 -07:00
Tin
9dcc21cd48 refactor(mcp): batch the purge row deletion into one query
The per-row delete_many loop becomes a single delete filtered to the enumerated OAuth users'
(user_id IN, server_id) pairs; same rows deleted, same BYOK-sparing precision, same count-mismatch
detection, one round-trip instead of N
2026-07-09 16:29:17 -07:00
Tin
c46c9d4652 docs(mcp): mcp_server_resource docstring matches the origin-only redaction
The field doc still said scheme + host + path while the redactor now strips the path along with
userinfo, query, and fragment, since hosted MCP servers routinely embed the credential in the path
2026-07-09 16:29:17 -07:00
Tin
4786e599b0 test(ui): pin the client-forwarded token contract on create and edit
The create and edit submit paths for true_passthrough and oauth_delegate persist only the tool
configuration: the parametrized create test authorizes, disables the allowlist, and asserts nothing
is persisted before submit, then that the create payload carries allowed_tools but no credentials
and no occurrence of the token anywhere in the serialized payload, no per-user DB credential is
written, and the token is committed to sessionStorage only, keyed to the created server. The edit
save test gains the same serialized-payload assertion
2026-07-09 16:29:17 -07:00
Tin
71e0491d37 fix(ui): preview tools with a staged interactive OAuth token in the edit form
For authorization_code the edit preview listed tools by server_id only, relying on the stored
per-user DB credential, so a token authorized in the edit session gave an empty preview until the
admin saved; the create form previews the identical state through the config-based preview
endpoint, which takes the token explicitly. The edit fetch now routes through that same endpoint
when a staged interactive token is held, built from the form values with the saved record as
fallback, and keeps the by-server_id listing for every other case
2026-07-09 16:29:17 -07:00
Tin
b304620311 fix(ui): compare url and spec_path independently in the OAuth authorization identity
The identity used to pick the audience from spec_path only when
values.transport was OPENAPI, but the create form keeps transport in component
state rather than form values, so spec_path edits on OpenAPI servers never
invalidated a held token. Comparing url and spec_path independently mirrors
the backend's mcp_oauth_token_identity and fires regardless of whether
transport is present. Invalidation now also wipes only credentials; the
admin-typed endpoint fields are kept
2026-07-09 16:29:17 -07:00
Tin
aa351311c0 fix(mcp): spare BYOK rows when purging stale OAuth tokens and invalidate caches on server delete
LiteLLM_MCPUserCredentials stores BYOK API keys in the same column as per-user
OAuth tokens, so the purge on a mint-relevant config change now deletes only
rows whose payload decodes as an OAuth2 credential, each by its
(user_id, server_id) pair, instead of every row for the server. An api_key
server whose url changes purges nothing. delete_mcp_server now also
invalidates each enumerated user's cached token so a re-created server reusing
the id cannot serve tokens minted for the deleted one, and both cache drops
are best-effort
2026-07-09 16:29:17 -07:00
Tin
e720b5e25a test(ui): drop the vacuous access_token assertion from the preview invalidation test
The staged access token never reaches formValues (it is not a registered form field), so the
assertion could not fail; the DCR client pair is the leak the test actually pins, proven by the
mutation run
2026-07-09 16:29:16 -07:00
Tin
c75184bec9 fix(mcp): make the pre-update identity snapshot advisory so a read failure cannot fail the edit
The snapshot read only feeds the stale-token purge decision; leaving it unguarded meant a failed
read would 500 an edit whose update would have succeeded, and it broke
test_edit_mcp_server_redacts_credentials, whose mocked prisma is not awaitable on the un-patched
get_mcp_server path. A failure now logs and skips the purge, consistent with the purge half already
being best-effort. Adds the first endpoint-level coverage of the edit purge wiring: purge on a
mint-relevant change, no purge when the identity is unchanged, and edit success with purge skipped
when the snapshot read raises
2026-07-09 16:29:16 -07:00
Tin
42388c3d68 refactor(mcp): align the invalidation code with the v2 DI and typing discipline
The purge takes an injectable invalidate_token_cache callable defaulting to the manager's shared
invalidation, and MCPServerManager takes an injectable per_user_token_cache alongside the existing
per_user_oauth_token_store, so tests inject fakes instead of monkeypatching the global manager and
the module-level cache. The new identity helpers drop Any for object throughout
2026-07-09 16:29:16 -07:00
Tin
48124734a0 fix(mcp): compare the token identity decrypted and invalidate every per-user token store
Review follow-ups on the stale-token invalidation. The backend identity now decrypts client_id and
client_secret before comparing: the stored values are NaCl-encrypted with a fresh nonce on every
write, so comparing ciphertext flagged every routine save as a mint-relevant change and purged
per-user tokens that were still valid. The identity also gains spec_path, the audience for OpenAPI
servers, and parses credentials stored as a JSON string

The purge now routes each (user, server) through the manager's invalidate_user_oauth_token_cache,
which becomes the single invalidation point covering both the legacy per-user token cache and the
v2 per-user OAuth token store; previously the purge evicted only the legacy cache while the revoke
path evicted only the v2 store, so each path left the other cache serving a replaced token until
its TTL. A credential row racing in between the find and the delete is now detected via the
delete_many count and logged; its cache entry expires by TTL

On the dashboard, CLEARED_ON_INVALIDATION and the staleness check move to types.tsx as the single
shared implementation for both forms. The edit form's transport handler now rechecks the identity
after its programmatic setFieldsValue calls, which antd does not report through onValuesChange, so
a token no longer survives a transport switch that clears the mint target. The create form rebuilds
formValues from the post-reset form state after an invalidation instead of publishing the pre-reset
snapshot, so the tool preview can no longer refetch with the discarded DCR client. Both transport
handlers now share the recheck, which also stops the create form from over-invalidating on an
http to sse swap that keeps the same url and therefore the same audience
2026-07-09 16:29:16 -07:00
Tin
05f39bf942 fix(mcp): invalidate a browser-authorized upstream token when a mint-relevant field changes
An admin who ran Authorize & Fetch and then changed a field that determines which upstream OAuth
token gets minted kept using the stale token for tool preview, sessionStorage, and (on the backend)
the stored per-user credential and its cache. Grounded in RFC 8707/8693 and the MCP auth spec, a token
is bound to one tuple: resource/audience (url), OAuth mode/grant (auth_type, oauth_flow_type), the
authorization-server endpoints, and the OAuth client + scopes. A shared getOAuthAuthorizationIdentity
captures exactly those fields; transport (http/sse on the same url is the same audience) and
delegate_auth_to_upstream (a downstream-usage toggle never sent to the authorize request) are excluded.

UI: both the create and edit forms now discard the held token (React state / sessionStorage / hook,
plus the fetched token + DCR client in form.credentials) whenever the identity diverges from the one it
was authorized against, re-applying the admin's in-flight edit so it is never wiped. The check lives in
one shared helper so the two forms cannot drift.

Backend: editing an MCP server now compares the pre/post identity and, on a mint-relevant change, purges
every stored per-user OAuth credential for the server (DB row + per-user token cache) so no user
forwards a token minted for a resource/AS/client that no longer matches. Best-effort; a purge failure
never fails the update.
2026-07-09 16:29:16 -07:00
tin-berri
68a4ca7247
Merge pull request #32414 from BerriAI/litellm_mcp_passthrough_ui_enum
feat(mcp/ui): expose true_passthrough and oauth_delegate auth types with a no-auth warning
2026-07-09 16:12:33 -07:00
Tin
d4e02ac047 refactor(mcp): share _UPSTREAM_OAUTH_DISCOVERY_AUTH_TYPES in the relay gate and tools preview
The gateway authorize/token/register gate and the preview header extraction each carried their own
inline copy of the oauth2 + client-forwarded mode set, which could drift from the discovery
constant the registry builders use; all three surfaces mean the same thing (modes that run the
upstream OAuth browser flow), so they now read the one constant
2026-07-09 15:42:35 -07:00
Tin
d0f1c38d6a fix(mcp): log only the origin of the upstream MCP url in tool-call metadata
The redacted resource kept the path, but hosted MCP servers routinely embed the credential in the
path (for example /mcp/s/<token>/mcp), and mcp_tool_call_metadata is readable by a caller who can
invoke the tool, so the path leaked the upstream credential into spend logs. Only scheme, host, and
port are logged now
2026-07-09 15:35:28 -07:00
Tin
65d0dcfb82 fix(mcp): never forward an Authorization header that satisfied admission on the tools preview
Authorization doubles as the admission fallback when x-litellm-api-key is absent, so a caller who
authenticated the preview request that way had their LiteLLM key forwarded to the upstream as the
oauth2/client-forwarded token. The preview now forwards Authorization only when the primary
admission header is present, which is how the dashboard has always sent it; with no primary header
there is no upstream token on the request at all. Applies to oauth2 and both client-forwarded
modes; parametrized regression test plus the admission header added to the existing extraction
tests to mirror the real UI request shape
2026-07-09 15:02:05 -07:00
Mateo Wang
1fa200123f
fix(tests): stop DATABASE_URL env pollution from read-replica tests breaking DB e2e tests (#32653) 2026-07-09 14:37:49 -07:00
Mateo Wang
41e9cc491e
fix(bedrock-invoke): retain clear_tool_uses_20250919 context_management edits and emit context-management-2025-06-27 beta (LIT-3393) (#32658)
* fix(bedrock-invoke): retain clear_tool_uses_20250919 context_management edits and emit context-management-2025-06-27 beta (LIT-3393)

Copy of #29206 by oss-agent-shin, rebased onto litellm_internal_staging so CircleCI can run.

Bedrock InvokeModel supports automatic tool-call clearing (clear_tool_uses_20250919) under the context-management-2025-06-27 beta, but LiteLLM stripped the edit and dropped the beta header, causing a Bedrock 400. This maps bedrock.context-management-2025-06-27 to itself in anthropic_beta_headers_config.json (bedrock_converse stays null) and rewrites _filter_context_management_for_bedrock_invoke around an allowlist of supported edit types that keeps each supported edit and adds its matching beta.

* test(bedrock-invoke): restore beta-headers config cache with a shared fixture in LIT-3393 tests

Greptile flagged that three of the four new tests reloaded the module-level
beta-headers config into local mode without restoring it on teardown, leaking
state into later tests in the same process. Move setup/teardown into a
local_beta_headers_config fixture used by all four tests.

---------

Co-authored-by: oss-agent-shin <ext-agent-shin@berri.ai>
2026-07-09 14:31:27 -07:00
mubashir1osmani
8519d7fc24
test: litellm fix failing tests (#32577)
Some checks are pending
CodSpeed Benchmarks / benchmarks (push) Waiting to run
GitHub Actions Security Analysis / zizmor (push) Waiting to run
* fix: rust ocr tests finally pass

* fix: move realtime dir

* fix(realtime): normalize azure realtime api_base to host for Foundry endpoints

The azure realtime handler appended the realtime path to api_base verbatim, so a
Foundry base carrying a project path (.../api/projects/<name>) produced an invalid
realtime URL and the websocket handshake hung. Normalize api_base to scheme and host
before building the realtime path so both Azure OpenAI and Foundry bases connect

Point the e2e realtime azure deployment at the GA gpt-realtime model and stop passing
the os.environ refs the realtime path never unwraps, resolving them from the gateway
env by name instead. Drop the local docker-compose scaffolding from the tree

* test(e2e): add Gateway.list_files and list_fine_tuning_jobs for the discovery suite

The discovery endpoints suite calls client.gateway.list_files and
list_fine_tuning_jobs, which did not exist on Gateway, so both tests errored with
AttributeError before reaching the proxy. Add the two GET wrappers using the
existing FileListResponse / FineTuningJobsResponse models

* revert(realtime): drop azure realtime api_base host-normalization

The azure realtime handshake failure was a config issue, not a litellm bug: the
realtime base was set to the Azure AI Foundry project endpoint (.../api/projects/<p>),
but the OpenAI-compatible realtime route lives at the resource root. litellm correctly
appends the realtime path to whatever base it is given, so pointing the realtime
deployment at the resource root is the fix and no core change is needed

* fix(ocr): route azure_ai doc-intelligence to its own endpoint at the source

get_llm_provider inherits AZURE_AI_API_BASE into api_base for every azure_ai/* OCR
model, but Azure Document Intelligence is a separate resource reached via
AZURE_DOCUMENT_INTELLIGENCE_ENDPOINT, so doc-intelligence requests went to the wrong
host. Stop inheriting the azure_ai base for doc-intelligence models so api_base stays
unset and both the rust bridge and the python get_complete_url fall back to the
document-intelligence endpoint. This drops the earlier _rust_bridge_api_base reorder,
which only covered the rust path and let the env silently override an explicit api_base

* refactor(ocr): consolidate azure doc-intelligence detection; keep explicit api_base

Extract is_azure_document_intelligence_model as the single source of truth for the azure_ai doc-intelligence sub-route so the check is no longer duplicated across _prepare_ocr_request and _rust_bridge_api_base, and gate the dynamic_api_base suppression on the caller not supplying an api_base so an explicit endpoint is always honoured. Restore xai to the realtime PROVIDERS as a documented disabled entry instead of dropping it silently, and add a regression test pinning doc-intelligence api_base resolution.

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

---------

Co-authored-by: Mubashir Osmani <mubashir@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-09 13:54:45 -07:00
yuneng-jiang
56ab5e0a38
Merge pull request #32514 from BerriAI/litellm_oss_daily_branch_workflow
ci: add OSS daily branch workflow
2026-07-09 13:51:16 -07:00
Tin
43726f2d0b refactor(ui): useTestMCPConnection uses the shared isClientForwardedTokenMode helper
The helper extraction missed this call site, leaving an inline duplicate of the two-mode check that
could drift from the shared definition
2026-07-09 13:51:03 -07:00
yucheng-berri
5cf269088c
fix(proxy): capture logging_obj before post_call_failure_hook pops it in ModifyResponseException streaming path (#32665)
* fix(guardrails/bedrock): honor disable_exception_on_block by raising ModifyResponseException

The Bedrock-specific GuardrailInterventionNormalStringError predates the
unified guardrails refactor and no proxy code path handles it, so a block
with the flag set surfaced as an uncaught Exception -> HTTP 500 in pre_call
mode and was silently discarded in during_call mode (model call proceeded
in the parallel asyncio.gather; the block hook's data["mock_response"]
mutation happened after route_request had already unpacked kwargs).

Convert the block to ModifyResponseException at the raise site inside
make_bedrock_api_request. That exception is the industry-standard proxy
contract already caught in proxy_server, anthropic_endpoints, response_api
_endpoints, and pass_through_endpoints; it turns into a 200 response with
finish_reason=content_filter and the block message as content, which is
exactly what the flag was documented to yield. Post-call blocks attach
the LLM response to original_response so the synthetic reply reports the
upstream call's real token usage instead of zero.

Deletes the now-orphaned GuardrailInterventionNormalStringError class and
the dead create_guardrail_blocked_response / mock_response plumbing in the
Bedrock hooks; updates the existing tests that had locked in the buggy
contract.

Resolves LIT-4186

* chore(guardrails/bedrock): drop dead str branch in _update_messages_with_updated_bedrock_guardrail_response

Follow-up to the disable_exception_on_block fix. That method used to
receive either a BedrockGuardrailResponse or a plain string (the block
message, when the flag was set). Now that a block always raises
ModifyResponseException before this method runs, the string branch is
unreachable; tighten the type to BedrockGuardrailResponse and delete
the guard.

* fix(guardrails/bedrock): streaming post_call block yields synthetic stream instead of surfacing as SSE 500

Regression from the LIT-4186 refactor: pre-refactor, the streaming
post_call iterator caught GuardrailInterventionNormalStringError locally
and replaced the assembled response with a synthetic content-filter
message, then re-emitted it as chunks via MockResponseIterator. After
the refactor the exception was re-raised as ModifyResponseException,
which async_streaming_data_generator serializes as a proxy 500 error
frame because the SSE response headers are already flushed by the time
the block fires.

Non-streaming paths still let ModifyResponseException propagate to the
endpoint handler (which converts it into a 200). Streaming can't do
that, so keep the local synthesis: on the exception, rebind the
assembled response to a ModelResponse whose single choice carries the
block message as content and finish_reason=content_filter, and let the
downstream MockResponseIterator emit it as chunks. Same shape a
non-streaming block produces.

Adds a mapped-file regression test that mutation-kills the raise
behavior and locks in the synthetic-stream contract.

* fix(guardrails/bedrock): preserve upstream usage on streaming post_call block

Non-streaming post_call blocks report the upstream LLM call's real
token usage via ModifyResponseException.original_response, which the
endpoint handler unwraps through _blocked_response_usage. Streaming
post_call synthesizes its own ModelResponse locally (the exception
can't escape the SSE generator), and previously left .usage unset,
so the client saw accurate billing on non-streaming blocks and zero
on streaming blocks -- silent revenue leak.

Copy the assembled response's .usage onto the synthetic block
response before yielding. Pre-refactor code had the same gap
(create_guardrail_blocked_response never set usage); this is a net
improvement, not a regression fix.

* fix(proxy): capture logging_obj before post_call_failure_hook pops it in ModifyResponseException streaming path

post_call_failure_hook removes litellm_logging_obj from request_data before
iterating callbacks (it's not serialisable). The streaming branch of the
ModifyResponseException handler read it from _data after that call, so it
always received None and CustomStreamWrapper.__init__ crashed with
AttributeError: NoneType has no attribute model_call_details.

Capture it before the hook runs so the streaming path gets a valid object.

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

* test(proxy): add regression for streaming ModifyResponseException logging_obj capture

Covers the bug where logging_obj was read from request_data after
post_call_failure_hook had already popped it, causing CustomStreamWrapper
to crash with AttributeError.

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

* test(proxy): drive real chat_completion in ModifyResponseException streaming logging_obj regression

The original test inlined the fix pattern (capture before pop) in its
own body rather than calling the actual chat_completion handler in
proxy_server.py, so a revert of the fix left the test passing.
Confirmed via mutation check: reverting the two-line source fix and
re-running left the test green.

Rewrite the test to drive chat_completion directly:
- patch _read_request_body so chat_completion sees the seeded dict
- patch ProxyBaseLLMRequestProcessing.base_process_llm_request to
  raise ModifyResponseException with the same request_data
- patch proxy_logging_obj so post_call_failure_hook mutates the dict
  the way production does (pops litellm_logging_obj)
- intercept CustomStreamWrapper.__init__ and assert logging_obj is
  the non-None object seeded in request_data

Mutation-verified: reverting the source fix now surfaces the exact
production crash inside CustomStreamWrapper's __init__
(AttributeError: NoneType has no attribute model_call_details) rather
than a silently-passing test.

Addresses Greptile P1 on PR #32665.

---------

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-07-09 13:48:47 -07:00
ryan-crabbe-berri
d1a79f7971
fix(ui): rename Virtual Keys 'Key Hash' filter label to 'Key ID' (#32672) 2026-07-09 13:48:20 -07:00
mubashir1osmani
97d0951296
test(e2e): make dynamic model provisioning robust on split deployments (#32670)
create_model now waits until the new deployment is servable on the data plane
(polls /v1/models) before returning, instead of assuming /model/new makes it
instantly callable. On a split control/data-plane proxy the gateway only sees a
model after its next DB reload, so an immediate call raced the reload and 400'd
with "Invalid model name passed" (embeddings, responses, messages, ocr, ...).

It also stops pinning model_info.id to the model_name, letting the proxy assign a
unique model_id. Re-registering a fixed-name deployment (the batch suite's
openai-batch et al.) after a failed teardown no longer collides on the model_id
unique constraint (prisma UniqueViolationError surfaced as the generic 500
"Failed to add model to db", erroring every batch_lifecycle case at setup)
2026-07-09 13:39:47 -07:00
Tin
bff2c952e0 fix(ui): key the edit form's browser-held token handling off the effective auth type
The edit form decided auth mode from the saved mcpServer.auth_type in fetchTools while the
authorize flow used the current form value, so a token authorized after switching the form to a
client-forwarded mode was never forwarded as the x-mcp header until the server was saved. A shared
getEffectiveAuthType (form value falling back to the saved record) is now the single decision point
for token receipt and tool loading

The save path classified the staged token with getMcpOAuthMode, which returns null for
true_passthrough and oauth_delegate, so the staged token was dropped on save instead of being
committed to sessionStorage the way the create form's submit path does. The passthrough branch now
also covers the client-forwarded modes; the token still never enters the server row
2026-07-09 13:19:56 -07:00
yucheng-berri
6eed38bcfb
fix(guardrails/bedrock): honor disable_exception_on_block by raising ModifyResponseException (#32289)
* fix(guardrails/bedrock): honor disable_exception_on_block by raising ModifyResponseException

The Bedrock-specific GuardrailInterventionNormalStringError predates the
unified guardrails refactor and no proxy code path handles it, so a block
with the flag set surfaced as an uncaught Exception -> HTTP 500 in pre_call
mode and was silently discarded in during_call mode (model call proceeded
in the parallel asyncio.gather; the block hook's data["mock_response"]
mutation happened after route_request had already unpacked kwargs).

Convert the block to ModifyResponseException at the raise site inside
make_bedrock_api_request. That exception is the industry-standard proxy
contract already caught in proxy_server, anthropic_endpoints, response_api
_endpoints, and pass_through_endpoints; it turns into a 200 response with
finish_reason=content_filter and the block message as content, which is
exactly what the flag was documented to yield. Post-call blocks attach
the LLM response to original_response so the synthetic reply reports the
upstream call's real token usage instead of zero.

Deletes the now-orphaned GuardrailInterventionNormalStringError class and
the dead create_guardrail_blocked_response / mock_response plumbing in the
Bedrock hooks; updates the existing tests that had locked in the buggy
contract.

Resolves LIT-4186

* chore(guardrails/bedrock): drop dead str branch in _update_messages_with_updated_bedrock_guardrail_response

Follow-up to the disable_exception_on_block fix. That method used to
receive either a BedrockGuardrailResponse or a plain string (the block
message, when the flag was set). Now that a block always raises
ModifyResponseException before this method runs, the string branch is
unreachable; tighten the type to BedrockGuardrailResponse and delete
the guard.

* fix(guardrails/bedrock): streaming post_call block yields synthetic stream instead of surfacing as SSE 500

Regression from the LIT-4186 refactor: pre-refactor, the streaming
post_call iterator caught GuardrailInterventionNormalStringError locally
and replaced the assembled response with a synthetic content-filter
message, then re-emitted it as chunks via MockResponseIterator. After
the refactor the exception was re-raised as ModifyResponseException,
which async_streaming_data_generator serializes as a proxy 500 error
frame because the SSE response headers are already flushed by the time
the block fires.

Non-streaming paths still let ModifyResponseException propagate to the
endpoint handler (which converts it into a 200). Streaming can't do
that, so keep the local synthesis: on the exception, rebind the
assembled response to a ModelResponse whose single choice carries the
block message as content and finish_reason=content_filter, and let the
downstream MockResponseIterator emit it as chunks. Same shape a
non-streaming block produces.

Adds a mapped-file regression test that mutation-kills the raise
behavior and locks in the synthetic-stream contract.

* fix(guardrails/bedrock): preserve upstream usage on streaming post_call block

Non-streaming post_call blocks report the upstream LLM call's real
token usage via ModifyResponseException.original_response, which the
endpoint handler unwraps through _blocked_response_usage. Streaming
post_call synthesizes its own ModelResponse locally (the exception
can't escape the SSE generator), and previously left .usage unset,
so the client saw accurate billing on non-streaming blocks and zero
on streaming blocks -- silent revenue leak.

Copy the assembled response's .usage onto the synthetic block
response before yielding. Pre-refactor code had the same gap
(create_guardrail_blocked_response never set usage); this is a net
improvement, not a regression fix.
2026-07-09 13:18:51 -07:00
ryan-crabbe-berri
1d9a86eac4
refactor(ui): consolidate invitation flow into the dashboard layout (#32576)
The App Router migration is complete: every page is a path route and the
legacy `?page=` switch is gone from the index. This closes it out.

The `/ui/` index (page.tsx) kept its own duplicate copy of teams state, a
teams fetch, and keys/addKey plumbing solely to feed a second `UserDashboard`
render for the `invitation_id` case. That was redundant: `ApiKeysDashboard`
already renders `UserDashboard` sourcing its own data, so the index is thinned
to just render `<ApiKeysDashboard />`. The login redirect, the legacy `?page=`
deep-link redirect for old bookmarks, and the post-login return-URL handling
stay on the index.

The invitation entry point now resolves in one place. Modern invitation links
already point at the dedicated `/onboarding` route; the dashboard layout now
redirects legacy `/ui/?invitation_id=` links there too (via `migratedHref`,
the same base-aware redirect the index uses for `?page=`), instead of
re-rendering that route's page component inline. This removes an import of one
route's `page.tsx` into another module, and lets the now-unreachable
`if (invitation_id) return <Onboarding/>` branch in the shared
`user_dashboard.tsx` be deleted along with its dead `Onboarding` import and
`searchParams` read. A layout test asserts the redirect and fails if it
regresses.

`legacyPageHref` and the sidebar's migrated-vs-legacy href fallback are left
in place; they are still live for the parent-category nav nodes (agentic,
tools, experimental, settings) that are not page routes.

eslint-metrics.json is resynced: -2 no-explicit-any from the removed `any`
casts, plus pre-existing drift the gate requires the snapshot to match.
2026-07-09 11:59:22 -07:00
ryan-crabbe-berri
7d63b86e00
fix(ui): forward refs through ui primitives and fail tests on swallowed refs (#32401)
* fix(ui): forward refs through ui primitives and fail tests on swallowed refs

Under React 18 a ref passed to a plain function component is dropped
with only a dev console warning, so Base UI render-prop triggers
composed over our shadcn-style primitives silently stop working (the
tooltip just never opens; ui/badge.tsx hit exactly this on the shared
DataTable branch). Label, Separator, Skeleton, UiLoadingSpinner and the
Table family now use React.forwardRef like Button and Input already
did, a contract test pins ref delivery for each, and setupTests turns
React's ref warning into a test failure so the next primitive that
swallows a ref fails CI instead of shipping a dead tooltip

* fix(ui): include captured ref warnings in the tripwire error

The afterEach tripwire threw a fixed message and discarded the collected
React warnings, so a failure never said which component swallowed the ref.
Append the captured warnings (component name + stack) to the thrown error.
2026-07-09 11:59:16 -07:00
devin-ai-integration[bot]
a874de6ac6
feat(models): add GPT-5.6 (sol/terra/luna) pricing and metadata (#32659)
* feat(models): add GPT-5.6 (sol/terra/luna) pricing and metadata

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

* test: allow gpt-5.6 service-tier cache-write keys in model prices schema

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

* fix: floating point entry errors

---------

Co-authored-by: mateo <mateo@berri.ai>
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>
2026-07-09 11:51:12 -07:00
T K Chandra Hasan
4e63c0c9e6
Fix enterprise doc link (#31815)
Signed-off-by: T K Chandra Hasan <t.k.chandra.hasan@ibm.com>
2026-07-09 11:48:11 -07:00
devin-ai-integration[bot]
0a40bd7ae5
fix(ui): prevent reasoning block from expanding chat playground layout (#32485)
The expanded reasoning block did not constrain its width or break long unbreakable tokens, so its inline-block bubble grew past its max width and pushed the whole page wider (#32481). Mirror the message body handling by capping the container width and breaking long words/code.

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-09 11:47:51 -07:00
Tin
a3f1873a87 fix(ui): extract inline object args in the MCP forms
The create/edit forms passed several large object literals inline as arguments (persist-state
JSON.stringify, storeMCPOAuthUserCredential, setToken, transport-clear setFieldsValue), tripping
local/no-large-inline-object-arg. Assigned each to a named variable at the call site - a pure,
behavior-preserving refactor verified by the create/edit suites - which lowers the whole-tree count so
the eslint baseline is 512 rather than being raised to accommodate them.
2026-07-09 11:39:44 -07:00
Tin
e29e24e628 fix(ui): keep the browser-authorized token out of form.credentials for the pass-through modes
The create form wrote the upstream token obtained by Authorize & Fetch into
form.credentials for every mode, so for true_passthrough / oauth_delegate the
browser-held token leaked into the OAuth flow's getCredentials (preview requests)
and the redirect-persist cache, and was a step away from server-level credential
persistence. onTokenReceived now early-returns for the client-forwarded modes,
holding the token only in local state for preview (mirroring the edit form),
instead of writing it into form.credentials.
2026-07-09 11:39:44 -07:00
Tin
a199bf975d refactor(mcp): share one constant for the upstream-OAuth discovery auth types
The config-YAML loader and the DB loader each defined their own local tuple
(oauth2, true_passthrough, oauth_delegate) to decide which auth types trigger
upstream OAuth endpoint discovery, under two different names. Hoisted them to a
single module constant _UPSTREAM_OAUTH_DISCOVERY_AUTH_TYPES so the two load paths
cannot drift on which modes get discovery.
2026-07-09 11:39:44 -07:00