Commit graph

54813 commits

Author SHA1 Message Date
lewis
edea3989f4 fix(ui): integrate customer access and budget checks 2026-10-17 21:30:29 -04:00
lewis
af2676b744 Merge commit 'dc78d8bb120f0a737c7b033daea14e4d1f3d052c' into HEAD 2026-10-17 21:24:06 -04:00
lewis
dc78d8bb12 refactor(proxy): simplify customer MCP server intersection 2026-10-17 21:20:53 -04:00
lewis
f22cc8e12d fix(proxy): retain block cache keys and response types 2026-10-17 21:16:51 -04:00
lewis
5e82636898 fix(proxy): retain block cache behavior in customer stack merge 2026-10-17 21:16:32 -04:00
lewis
8f2104521a Merge commit '08afd2f7a36a82ceee6f48d3735511a350119b3a' into litellm_customers_page
# Conflicts:
#	enterprise/enterprise_hooks/blocked_user_list.py
#	litellm/proxy/management_endpoints/customer_endpoints.py
#	tests/unit/proxy/management_endpoints/test_customer_endpoints.py
2026-10-17 21:16:22 -04:00
lewis
6516a51eb1 fix(ui): retain customer tool limits through MCP groups 2026-10-17 21:15:48 -04:00
lewis
08afd2f7a3 Merge commit '0f52da9232aa0604bb0114782d676b319f021ff2' into HEAD 2026-10-17 21:13:19 -04:00
lewis
1f849c210a Merge commit 'd7a4a2746ac979197a8ae187d20175ac782b88bc' into HEAD
# Conflicts:
#	litellm/proxy/management_endpoints/customer_endpoints.py
#	tests/unit/proxy/management_endpoints/test_customer_endpoints.py
2026-10-17 21:13:19 -04:00
lewis
e664274d6a perf(proxy): index customer budget ownership lookups 2026-10-17 21:12:01 -04:00
lewis
0f52da9232 fix(proxy): enforce customer MCP denial before open grants 2026-10-17 21:11:21 -04:00
lewis
4dda35f967 fix(ui): preserve customer edit baselines and shared labels 2026-10-17 21:08:42 -04:00
lewis
e261898271 fix(ui): avoid restoring denied MCP tool grants 2026-10-17 20:19:28 -04:00
lewis
5eb6357127 fix(ui): preserve MCP grants on unrelated key and user edits 2026-10-17 20:13:57 -04:00
lewis
c849cbc0bd fix(ui): keep colliding MCP names distinct and show unrestricted and no-tool grants
The MCP selector typed only toolset values, so a stored server whose ID or alias matched an access group became that group on the next edit and widened the grant. Servers and access groups now carry their own prefixes

The customer MCP filter called unrestricted customers 'No MCP access'. It now separates limited, denied and unrestricted customers, matching the table column

Server details showed an empty tool allowlist like an unrestricted server. It now says no tools are allowed
2026-10-17 18:27:10 -04:00
lewis
d15a0151de fix(ui): export customer MCP tool permissions in the CSV 2026-10-17 14:45:32 -04:00
lewis
85698939d8 fix(ui): let customers deny all MCP and keep tool limits and alias grants on save 2026-10-17 11:15:10 -04:00
lewis
be809af754 fix(ui): block unsafe customer MCP saves and drop Map.groupBy
Refuse a save that adds a direct MCP server before it has a tool allowlist,
so a pending or failed tool discovery can no longer grant every tool,
including delete tools.

Require the access-group catalog when the customer has or selects access
groups, so a failed catalog can't turn a stored group into a server grant.

Count customer budgets without Map.groupBy, which older browsers lack.
2026-10-17 09:06:44 -04:00
lewis
7266452d14 Merge branch 'litellm_fix_customer_toolset_server_ceiling' into litellm_fix_object_permission_partial_upsert 2026-10-17 08:05:36 -04:00
lewis
8408cd94b5 fix(proxy): apply an explicitly sent zero customer budget instead of ignoring it 2026-10-16 22:44:03 -04:00
lewis
d7a4a2746a test(integration): run customer block cases through a proxy with the blocked user check 2026-10-16 22:26:29 -04:00
lewis
133bd508ca test(ui): expect the change source in customer MCP tool permission tests 2026-10-16 22:23:16 -04:00
lewis
8d4adfcadf Merge remote-tracking branch 'origin/main' into litellm_fix_customer_toolset_server_ceiling
# Conflicts:
#	tests/unit/proxy/_experimental/mcp_server/auth/test_user_api_key_auth_mcp.py
2026-10-16 22:16:56 -04:00
lewis
6f9d717227 fix(proxy): read the blocked user hook list through a read-only protocol 2026-10-16 22:10:42 -04:00
lewis
363ca735ec test(proxy): run customer budget update tests inside the locked transaction 2026-10-16 22:00:29 -04:00
lewis
0ab63669c8 Merge branch 'litellm_fix_object_permission_partial_upsert' into litellm_customers_page 2026-10-16 21:51:51 -04:00
lewis
fc09bb3964 Merge remote-tracking branch 'origin/main' into litellm_customers_page
# Conflicts:
#	tests/unit/proxy/_experimental/mcp_server/auth/test_user_api_key_auth_mcp.py
2026-10-16 21:51:51 -04:00
yuneng-jiang
81a3802a92
test(e2e): assert the Vertex cache-creation billing contract in the prompt caching test (#47027)
The cache-creating call now reports cache_creation_input_tokens on top of
Vertex's own prompt count and bills the write at the input rate, as Google
does. The old assertions assumed neither, so the test went red on every run.
Check the cache write, the discounted read, and that a second identical call
reuses the cache instead.
2026-10-16 18:51:38 -07:00
lewis
e95c08bf3a Merge remote-tracking branch 'origin/main' into litellm_fix_object_permission_partial_upsert 2026-10-16 21:51:13 -04:00
lewis
b121d07c6a Merge remote-tracking branch 'origin/main' into litellm_fix_customer_block_state 2026-10-16 21:51:13 -04:00
lewis
4bac01c84e Merge branch 'litellm_fix_object_permission_partial_upsert' into litellm_customer_bulk_update 2026-10-16 21:51:13 -04:00
lewis
546f2b868e chore(terraform): allowlist the customer bulk update endpoint 2026-10-16 21:50:16 -04:00
lewis
689b7169bf fix(proxy): type the blocked user hook without an enterprise import 2026-10-16 21:49:43 -04:00
Yassin Kortam
fc484be77a
fix(proxy): keep the OIDC login refresh token as the ID-JAG subject when the SSO client is the ID-JAG client (#47004)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-16 18:28:20 -07:00
yuneng-jiang
59fa785de0
test(e2e): repair fallback management test for config-owned fallbacks (#47023)
* test(e2e): repair fallback round trip for config-owned fallbacks

The e2e stack config sets router_settings.fallbacks, and since #45860 a
/fallback write to a key the config file owns is refused with a 400. The
round trip now runs on context_window fallbacks, which the stack config
leaves to the database, and also deletes the rule and checks it is gone.

A second test locks the new contract: a /fallback write to the
config-owned fallbacks key returns 400 naming router_settings.fallbacks,
and the config rule keeps being served unchanged.

* test(e2e): assert the refused fallback rule is never served
2026-10-17 01:27:39 +00:00
yuneng-jiang
856a3f62f3
test(integration): expect a pre-token invalid prompt on a bridged stream to fall back (#47020)
#46493 reads the first stream chunk inside the router's retry and fallback
chain, so an invalid prompt rejected before any token now falls back to the
configured fallback deployment instead of answering 400. The author
confirmed this is intended

The fallback test now asserts the spare serves the stream. The old 400
without a fallback attempt still holds when fallback_on_unsupported_params
is false, so that contract moves to a second proxy with that setting
2026-10-16 18:26:56 -07:00
joshua-berri
6d0d0bb68b
feat: add Nace Drex decisions provider (#46523)
* feat: add Nace Drex decisions provider

* fix: expose Nace in dashboard provider metadata

* ci: include Nace tests in the provider shard

* fix: complete Nace provider catalog and preserve OpenRouter tests

* test: mark Nace test directories as packages

* fix: share Drex score parsing across decision providers

---------

Co-authored-by: Joshua Valluru <326636767+joshua-berri@users.noreply.github.com>
2026-10-16 18:07:50 -07:00
Kerry Lu
e8550c1454
fix(openai): send chat completions through client.post so the SDK's per-message param transform stops holding the event loop (#46967)
* fix(openai): send chat completions through client.post so the SDK's per-message param transform stops holding the event loop

create() walks every message, content part and tool of the request through
openai._utils._transform before posting, 91 ms at 300k tokens and ~300 ms at
1M tokens on the event loop, and returns the same dict for the plain dicts
litellm builds. client.post is what create() calls after that walk, same
request, headers, retries, raw response and stream objects, so the chat
helpers now call it directly through the helper embeddings already used

* fix(openai): type the chat client.post result as LegacyAPIResponse so the type gate sees .headers and .parse

* fix(openai): assert the chat client.post result is a LegacyAPIResponse instead of casting it

* test(openai): stub client.post with a LegacyAPIResponse spec in the empty-response and no-leak tests

* test(core-utils): stub client.post with a LegacyAPIResponse spec in the agentic loop leak test

* test: every client.post stub on the OpenAI chat path returns a LegacyAPIResponse-spec'd mock

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-16 18:06:01 -07:00
Kerry Lu
6b3a5dc3ce
docs(cost): rewrite stale completion_cost docstring (#47022)
The old docstring called completion_response required, said total_time was in
seconds (it is milliseconds), and only covered a handful of the parameters.
Replace it with a short accurate summary and fix the "calculat" typo in the
unmapped-model error.

Co-authored-by: Kerry Lu <kerry@berri.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 01:04:41 +00:00
lewis
cec8c31ead Merge branch 'litellm_fix_object_permission_partial_upsert' into litellm_customers_page 2026-10-16 20:38:38 -04:00
lewis
1d97d70c37 Merge branch 'litellm_fix_object_permission_partial_upsert' into litellm_customer_bulk_update 2026-10-16 20:38:38 -04:00
lewis
4ff5c941db Merge commit 'f81e657903' into litellm_fix_object_permission_partial_upsert 2026-10-16 20:38:37 -04:00
lewis
aa1488c49a Merge commit 'f81e657903' into litellm_fix_customer_block_state 2026-10-16 20:38:37 -04:00
devin-ai-integration[bot]
aa32e4cc88
fix(logging): stop the spend metadata merge from walking a dict the logging thread writes to (#46044) 2026-10-16 17:37:18 -07:00
Mateo Wang
3ef0b35255
fix(bedrock)!: block realtime replay after an input-first start and reject malformed Nova Sonic session settings (#46936)
* fix(bedrock): block realtime replay after input starts the session and reject non-string instructions and voice

* test(bedrock): name the malformed realtime setting test for its string cases

* fix(bedrock): replay the opening realtime message until Bedrock accepts the stream and refuse any fallback after commit

Also rejects a malformed modalities, input_sample_rate_hertz or tools setting with invalid_type, dropped under drop_params.

* fix(bedrock): replay a refused opening message on any fallback and reject OpenAI voices and non-string audio formats

* fix(realtime): read the client scope only when the websocket has one and reject any-case OpenAI and empty Bedrock voices

* test(bedrock): cover realtime replay, refusal and committed failure through a real proxy

Fix the scripted Bedrock peer's port import and the nested tool call cell's stale expected events, so the Nova Sonic integration file collects again
2026-10-17 00:29:50 +00:00
jesus-berri
7f603da9f1
fix(openai_like)!: surface provider response headers on non-streaming chat completions (#46928)
* fix(openai_like): surface provider response headers on non-streaming chat completions

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

* test(openai_like): mark the header test locals Final

* fix(proxy): leave out response header values HTTP cannot carry

Non-streaming OpenAI-like chat now forwards every provider header, so on the
httpx transport a provider header value outside latin-1 made the proxy answer
500 after the provider had served the call. get_custom_headers now leaves such
a value out and keeps the other headers, which also ends the same 500 on
streaming

Adds integration cells for the provider request id on non-streaming chat and
the Responses bridge, LiteLLM headers an upstream cannot overwrite, spend
parity under an upstream cost header, the latin-1 case, and the logging
callback payload

* fix(cost): ignore an upstream response cost header that is negative or not a number

* test(integration): cover invalid upstream cost headers and non-latin-1 headers on every endpoint

* fix(prometheus): keep deployment metrics when a provider rate-limit header is not a number

* test(prometheus): type the rate-limit header test and drop its clock reads

* test(integration): cover deployment metrics under an unparseable provider rate-limit header

---------

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-10-16 17:25:50 -07:00
yuneng-jiang
37b3a697c7
fix(ui): preserve key settings drafts during background refreshes (#41350)
* fix(ui): preserve key settings drafts during background refreshes

* fix(ui): refresh untouched key settings while preserving edits

* fix(ui): keep team-change MCP clears across key refreshes

clearMcpGrants now marks the MCP fields dirty, so a background refresh with
keepDirtyValues no longer restores the grants a team change just cleared.
Also adapts the draft tests to the onSubmit(values, dirtyFields) signature
on main and asserts a refresh does not mark untouched fields dirty, since the
team admin member-key path rejects saves with non-budget dirty fields.
KeyEditView becomes a one-line arrow to stay under the max-lines limit.

* fix(ui): keep MCP tool defaults out of a key's edited fields

MCPToolPermissions writes non-delete tool defaults for unrestricted servers
on mount. Marking that write dirty made a team admin's budget-only save on a
member key get rejected for editing mcp_tool_permissions. onChange now says
whether a write is a default or a user edit, and the key editor dirties only
edits.

The edit session is now keyed by token or token_id, matching the form's own
fallback, so switching between keys that only carry token_id starts fresh.

* fix(ui): keep a new MCP server's default tool limits across refreshes

Default tool writes for a server the user just added in this edit session
are part of that edit, so they are marked dirty and survive a background
refresh. Defaults written for an already saved server stay clean, so a team
admin's budget-only save on a member key is still accepted.
2026-10-16 17:20:46 -07:00
ryan-crabbe-berri
9aab6ec47b
fix(azure_ai): count Grok reasoning tokens in completion_tokens (#46969)
* fix(azure_ai): count Grok reasoning tokens in completion_tokens

Grok on Azure AI Foundry reports reasoning_tokens outside completion_tokens,
like xAI does, so azure_ai/grok-* calls billed only max(visible, reasoning)
output tokens. Fold them with XAIChatConfig.fold_reasoning_tokens_into_completion
in transform_response and in a new streaming iterator, matching the xai provider.

Split out of #46377.

* fix(azure_ai): fold reasoning tokens for every deployment name, streaming or not

Non-streaming checked the deployment name for grok while streaming checked the
chunk model field, so a Grok deployment named without grok was only folded when
streaming. The fold already only fires when total_tokens equals prompt plus
completion plus reasoning, so it now runs on every azure_ai response.
2026-10-16 16:54:38 -07:00
jesus-berri
8efb99b4ac
feat(ui): shareable URL state for usage views and users-to-usage deeplinks (#43425)
* feat(ui): shareable URL state for usage views and users-to-usage deeplinks

The Usage page now keeps the view, global view tab, date range and entity filters in the
URL, so links can be bookmarked and shared. Relative presets are stored as range=7d and
keep rolling, custom ranges as UTC from/to. Invalid params are dropped, and a link to a
view or user the viewer cannot access shows a toast and falls back to the default view.

Internal Users rows get a "View usage" action that opens usage filtered to that user, and
the per-user usage views link back to the user's record. A "Copy Share Link" button sits
next to "Ask AI"

* fix(ui): clean usage links for sessions without the organizations query

The organizations query only runs for premium sessions, and a disabled query stays
pending forever, so the URL cleanup never ran for other sessions. Gate on isLoading
instead, and give the access denied toast an id so a re-run effect shows it once

* fix(ui): type-check usage entity filters and address review findings

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

* fix(ui): keep team usage available to internal users

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

* style(ui): format users table usage action

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

* fix(ui): sync date picker preset with the current usage range

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

* fix(ui): restore dashboard AGENTS.md from main and drop unused usage state

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

* fix(ui): share custom usage date ranges as calendar days

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

* fix(ui): keep per-user agent usage page across rerenders

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-10-16 16:42:29 -07:00
Kerry Lu
40efc6e07e
feat(cost-map): support s3:// URIs for LITELLM_MODEL_COST_MAP_URL (#46972)
* feat(cost-map): support s3:// URIs for LITELLM_MODEL_COST_MAP_URL

* fix(cost-map): handle incomplete S3 ClientError responses

* refactor(cost-map): drop S3 retry classification and s3_client argument

* test(cost-map): cover non-object remote map fallback

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-16 16:00:06 -07:00