Commit graph

3203 commits

Author SHA1 Message Date
devin-ai-integration[bot]
cc45d18e9c
feat(complexity-router): add return_raw_model_name toggle for response model field (#33875)
* feat(complexity-router): optionally return raw model name

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

* test(proxy): restore asyncio import

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

* chore(tests): preserve staging asyncio import

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

* test(proxy): drop unused local asyncio import

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

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

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

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

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

* style(proxy-tests): preserve module spacing

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

---------

Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-18 19:24:56 -07:00
tin-berri
3f3295b33f
feat(spend): track prompt compression saved tokens in daily spend aggregates (#33810)
* feat(spend): track prompt compression saved tokens in daily spend aggregates

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

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

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

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

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

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

* feat(ui): add Cost Optimization dashboard page

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

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

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

---------

Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-18 17:47:54 -07:00
devin-ai-integration[bot]
d495da4ce4
feat(chat-ui): add personal Logs view scoped to the current user (#33829)
* feat(chat-ui): add personal Logs view scoped to the current user

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

* fix(chat-ui): show request payload from proxy_server_request in logs detail

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

* fix(chat-ui): address logs panel review feedback (stable detail key, error state)

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

---------

Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-18 14:58:12 -07:00
ryan-crabbe-berri
6f4f4f69df
refactor(ui): consolidate Add/Edit credential modals into one CredentialModal (#32572)
* refactor(ui): consolidate Add/Edit credential modals into one CredentialModal

AddCredentialModal and EditCredentialModal were ~90% identical: the same
provider select, ProviderSpecificFields, and submit/filter logic, differing
only in title, button text, edit-mode prefill, and the disabled credential
name. Replace both with a single CredentialModal driven by a mode: 'add' |
'edit' prop, and point the two call sites in credentials.tsx at it.

Removes ~120 lines of duplication and drops the no-explicit-any and
no-restricted-imports baselines. The two per-file tests merge into one
CredentialModal.test.tsx covering both modes (add: editable empty name;
edit: prefilled, disabled name; provider fields render).

* refactor(ui): derive credential name disabled state from mode, not data

The disabled flag on the credential name field was tied to whether
existingCredential?.credential_name is truthy, an artifact of the old
EditCredentialModal. Drive it from the isEdit flag like the rest of the
component so mode='add' with a stray existingCredential can't disable the
field and mode='edit' with an empty name can't leave it editable. Behavior
is unchanged for real call sites; adds a regression test for the edit-with-
empty-name case.

* refactor(ui): prefill credential form declaratively instead of via useEffect

The edit-mode form was seeded with an imperative form.setFieldsValue inside
a useEffect that also set React state (setSelectedProvider), an antd anti-
pattern carried over from the old EditCredentialModal. Both call sites mount
the modal fresh with existingCredential already present (conditional && plus
destroyOnHidden), so there is no 'prop arrives after mount' case to handle.

Replace it with antd's declarative initialValues on the Form and a lazy
useState initializer for the provider. Removes the effect, its
react-hooks/set-state-in-effect suppression and exhaustive-deps warning, and
one any cast; behavior is unchanged (edit now shows the real provider on
first paint instead of flashing the default). Existing tests cover prefill
and the disabled name field.
2026-07-18 18:24:03 +00:00
ryan-crabbe-berri
577dd3b707
fix(ui): stop credential edit from persisting the masked api key (#33797)
Editing an existing LLM credential and changing only the api_base also
overwrote the stored api_key with its masked display value (e.g. sk****IA).
The edit form pre-fills fields from the credential the backend returns, whose
secrets come back masked, and the update handler sent every field straight
back; the endpoint then encrypted and stored the asterisks over the real key.

Run credential_values through stripMaskedSecrets before the PATCH so masked
placeholders are never sent, mirroring the guard the model edit form already
uses. The isMaskedSecret / stripMaskedSecrets helpers move out of
model_info_view into a shared utils module so both call sites share one
implementation.

Add a Playwright e2e that seeds a credential, edits only the api base in the
LLM Credentials tab, and asserts the outgoing PATCH no longer carries the
masked api_key while the new base persists.
2026-07-17 18:25:24 -07:00
devin-ai-integration[bot]
b0a0f11b09
feat(complexity-router): user-triggered escalation keywords (#33656)
* feat(complexity-router): user-triggered escalation keywords

Add an escalation_keywords config option to the complexity router so a user
can force a bump to the next-higher complexity tier by including a phrase in
their message (a stronger model, but not one they get to choose). Defaults to
['LITELLM ESCALATE'] when unset, case-sensitive so it only fires on the
deliberate shouted form; admins can override the list or set [] to disable.

Escalation applies across every routing path: heuristic/LLM classification,
literal and semantic keyword_tier_rules overrides, adaptive routing, and
session affinity (where it bumps relative to the pinned model and persists the
higher tier for the rest of the session). Capped at the highest configured
tier and skips unconfigured intermediate tiers.

Expose it in the Auto-Router v2 UI as an Escalation Keywords field wired into
the complexity_router_config payload.

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

* fix(complexity-router): validate escalation keywords and pin at tier ceiling

Strip blank/whitespace escalation keywords so an empty phrase can't match every message and escalate all traffic. Keep the exact pinned model when a session escalates at the highest configured tier instead of randomly hopping to a peer in a multi-model pool.

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

---------

Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-17 10:24:59 -07:00
devin-ai-integration[bot]
4d33964898
fix(ui): remove Chat item from dashboard leftnav (#33647)
Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-16 21:47:18 -07:00
yuneng-jiang
5daed34749
refactor(ui): migrate AI Hub, public hub, and MCP Toolsets tables onto shared DataTable (#33629)
* refactor(ui): migrate AI Hub, public hub, and MCP Toolsets tables onto shared DataTable

* test(ui): stub skillHubPublicCall in the public model hub networking mock
2026-07-16 19:32:20 -07:00
yuneng-jiang
3459956fd2
refactor(ui): migrate 5 simple tables onto shared DataTable (#33548)
Migrate Projects, Project Keys, Vector-store Documents, Logging
Callbacks, and Pass-through Endpoints onto the shared DataTable and
cell library, each split into a thin container plus a columns file.
Row actions move into the unified overflow menu, empty states get the
rich icon+title+body treatment, and parents that never tracked loading
gain an initial-load-only isLoading flag so tables render skeletons
instead of flashing the empty state.

Project Keys switches from a hand-rolled antd Pagination header to
server pagination through the shared footer, and its section is
rewired into ProjectDetailsPage, which had regressed to a hardcoded
"No keys to display" empty card while the section sat orphaned.
pass_through_settings.tsx moves to PascalCase under
components/PassThroughSettings/ and drops its dead modelData prop and
tremor imports; its stale eslint bulk-suppressions are pruned.
2026-07-16 16:00:59 -07:00
devin-ai-integration[bot]
5961c173e1
feat(ui): require embedding model for semantic auto router (#33313)
Co-authored-by: shivam <shivam@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-16 15:13:38 -07:00
ryan-crabbe-berri
74ff8d0ff9
fix(ui): navigate to /ui/login/ with trailing slash via hard navigation (#33561)
* fix(ui): navigate to /ui/login/ with trailing slash via hard navigation

Logged-out redirects targeted /ui/login without the trailing slash, so
Starlette's StaticFiles(html=True) mount answered with a 307 whose
absolute Location is built from the scheme the container sees. Behind a
TLS-terminating reverse proxy uvicorn does not trust X-Forwarded-Proto
by default, so the redirect downgraded https to http and stranded users
on an unreachable URL (#33454). The auth guard also used the Next client
router for this navigation, which first requests an RSC payload that the
static export cannot serve, producing 404s before falling back to a full
page load.

Centralize the login URL in getLoginUrl(), which always emits the
trailing slash so no server redirect fires, and use
window.location.replace for the login redirects so no RSC fetch is
attempted.

* test(ui): expect trailing slash in expired-token login redirect
2026-07-16 12:18:55 -07:00
tin-berri
db800152c0
Merge pull request #33450 from BerriAI/litellm_mcp_issuer_anchored_discovery
feat(mcp): issuer-anchored OAuth discovery (RFC 8414 §3.3) to close the authorization-server mix-up
2026-07-16 11:55:35 -07:00
yuneng-jiang
dce1beadca
Merge pull request #33357 from BerriAI/litellm_/gallant-carson-880e37
refactor(ui): migrate policies, deleted keys, deleted teams, budgets, and search tools tables onto shared DataTable
2026-07-16 07:11:15 -07:00
Krrish Dholakia
f6516e7be5
fix(ui): stop sending the complexity-router pseudo-model to /health/test_connection (#33498)
* fix(ui): stop sending the complexity-router pseudo-model to /health/test_connection

Test Connection on a saved auto-router model sent the raw
"auto_router/complexity_router" model string to the generic
health-check endpoint, which always failed with "Unmapped LLM
provider" since it's a routing-strategy config, not a real
completion endpoint.

Reuse the per-tier connection test already built for the Add Auto
Router wizard: for complexity-router models, test each configured
tier's underlying model group instead of the router pseudo-model.
Semantic-type auto routers (auto_router_config) have no equivalent
tier-based test yet, so the button is hidden for them instead of
guaranteed to fail.

* fix(ui): address review feedback on auto-router test connection fix

Type the complexity-router config parsing instead of using `any`, use
NotificationsManager.warning instead of fromBackend for the
client-generated "no tiers configured" message, remove comments added
in the previous commit, and also test the deployment's configured
complexity_router_default_model as a fallback target when it isn't
already covered by a configured tier (matches the fallback Router
itself uses for unconfigured tiers).
2026-07-15 21:41:45 -07:00
Yuneng Jiang
63e0be6200
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/laughing-herschel-d4f735
# Conflicts:
#	ui/litellm-dashboard/src/components/UsageIndicator.test.tsx
#	ui/litellm-dashboard/src/components/UsageIndicator.tsx
2026-07-15 17:49:43 -07:00
yuneng-jiang
614dd8756e
Merge pull request #33446 from BerriAI/litellm_/chat-ui-first-message-bug-4ca43c
fix(ui/chat): resolve chat routes at render time so navigation works under server_root_path
2026-07-15 17:48:14 -07:00
Yuneng Jiang
585f21aaf7
chore(ui): remove Hide Usage Indicator flag and hook 2026-07-15 17:45:40 -07:00
Yuneng Jiang
daa15ba074
chore(ui): remove unmounted UsageIndicator component 2026-07-15 17:09:24 -07:00
Tin Chi Lo
031922eec6 feat(mcp): discover-by-default issuer (trust-on-first-use) + stale-clear on url/auth_type change + UI
Discover the issuer from the upstream and persist it trust-on-first-use (fill-empty-only,
frozen thereafter), so admins do not have to type it; an admin-configured issuer always wins
and is never overwritten. Re-pointing the server url now clears the discovered issuer and
endpoints (matching the existing auth_type-change clearing) so a new upstream re-discovers
instead of anchoring on the previous upstream's issuer. Adds the issuer to the per-user OAuth
token identity so re-pointing it purges stale tokens. Surfaces the issuer as an optional,
auto-discovered, overridable field in the create and edit MCP server forms.

C901 gate shows +1 vs staging; that is inherited from the #33317 stack base (delta 0 against
2026-07-15 16:59:01 -07:00
Yuneng Jiang
056f85a71e
feat(ui): show exact license expiration date in usage cards 2026-07-15 16:52:21 -07:00
devin-ai-integration[bot]
ff4a40f017
feat(ui): add reusable BetaBadge and use it for Projects sidebar item (#33449)
* fix(ui): remove New badge from Projects sidebar item

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

* feat(ui): add reusable BetaBadge and use it for Projects sidebar item

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

* feat(ui): rename disableShowNewBadge flag to disableShowBadges and make BetaBadge respect it

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

* style(ui): use blue for BetaBadge to match existing New badge

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

* revert(ui): keep disableShowNewBadge localStorage key to preserve existing user opt-outs

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

* chore: remove non-functional md artifacts from PR

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-07-15 16:04:29 -07:00
devin-ai-integration[bot]
b907378f02
feat(guardrails): forward optional metadata on POST /guardrails/apply_guardrail (#33067)
Clients calling the standalone apply_guardrail endpoint had no way to pass
per-request configuration to custom guardrail implementations. This adds an
optional metadata field to ApplyGuardrailRequest and forwards it to
CustomGuardrail.apply_guardrail via request_data, only when the client sends
it. The messages guard is aligned to the same is-not-None semantics so an
explicitly-sent empty list is forwarded instead of silently dropped.

The Admin UI's Guardrail Test Playground gains an optional Metadata JSON
input (validated client-side) wired through applyGuardrail in networking.tsx,
so parameterized guardrails can be exercised from the dashboard.

Tests cover metadata alone, metadata with messages, explicit empty values,
the omitted-field passthrough, and the UI panel's parse/error behavior

Co-authored-by: Yassin Kortam <yassin@berri.ai>
2026-07-16 01:36:17 +03:00
Yuneng Jiang
719e1bfec0
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/chat-ui-first-message-bug-4ca43c 2026-07-15 13:48:38 -07:00
Yuneng Jiang
2b9b681e12
fix(ui/chat): resolve chat routes at render time so navigation works under server_root_path
CHAT_ROUTES was built once at module load via migratedHref, capturing an
empty server root path before the UI-config bootstrap sets it. Under
SERVER_ROOT_PATH every chat route came out unprefixed, so router.push
hard-navigated to a 404; during a send that page unload also aborted the
streaming request, so the first message of a new conversation never
rendered and only a second send appeared to work. Compute the routes at
render time via getChatRoutes(), update the send URL with a shallow
history.pushState instead of a router navigation, and source the active
conversation id from the hook's local state so it propagates without a
router round-trip
2026-07-15 13:48:27 -07:00
devin-ai-integration[bot]
9c59e2ae55
feat(ui): move Caching from Experimental into Developer Tools (#33432)
Co-authored-by: ryan <ryan@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-15 12:47:11 -07:00
yuneng-jiang
f8c49f51cc
refactor(ui): migrate guardrails table onto shared DataTable (#33303)
* feat(ui): migrate guardrails table onto shared DataTable

Move the guardrails list onto the shared DataTable + cell library as the
proof-of-concept for the simple-tables design migration, following the Teams
reference pattern.

Split the table into a thin container (guardrail_table.tsx) and column defs
(guardrailTableColumns.tsx): client-side sort defaulting to created_at desc, a
search + refresh toolbar, IdCell / DateCell / StatusBadge cells, real provider
logos, a rich empty state, and skeleton loading rows. Row actions move into a
per-row overflow menu; deletion stays disabled for config-file guardrails, now
surfaced as a disabled menu item instead of a greyed trash icon. Detail view
and the delete modal remain owned by GuardrailsPanel.

Restyle the "Add New Guardrail" control to the shared Button + dropdown menu.

Update the regression tests for the menu-based actions and drop the now-stale
eslint suppression entry that the rewrite eliminated.

* fix(ui): match guardrails table to the design

Address design-review feedback on the guardrails migration:

- Drop the search + refresh toolbar. The original table had neither and the
  SimpleTable design has no toolbar; the container now just renders the sorted
  table and its empty state.
- Give the Guardrail ID cell the design's hover affordance by rendering it with
  the shared IdentityCell (monospace, chevron on hover) instead of the blue
  IdCell pill.
- Stop pinning the actions column. Pinning added a sticky divider that the
  design and the Teams table don't have; it is now a plain right-aligned menu
  column, matching Teams.

* fix(ui): match loading skeleton row height to loaded rows

The compact skeleton row did not carry the h-8 height that real compact
rows get, so loading rows rendered shorter than loaded ones and the table
height jumped when data arrived. Mirror the same size-based height on the
skeleton row in the shared DataTable so every compact table loads at a
stable height

* test(ui): drop stale onGuardrailUpdated from guardrails table baseProps

The prop was removed from GuardrailTableProps when the toolbar went away;
the test baseProps still listed it. Harmless at the call site since it is
spread rather than an object literal, but dead and worth removing

* fix(ui): remove dead edit_guardrail_form after guardrails migration

The guardrails table migration dropped the last import of EditGuardrailForm,
which knip flags as an unused file. The form was already unreachable before
the migration: the table wired a delete button only, and nothing ever called
handleEditClick to open the modal, so the import was the sole thing keeping
the file referenced. Delete it and prune its now-stale eslint suppression
entry. Guardrail editing is unchanged and lives in the detail view
(GuardrailInfoView)
2026-07-14 17:35:27 -07:00
yuneng-jiang
2166608eb8
feat(ui): left-anchor the Create Key and Create Team CTAs (#33248)
Move the Create New Key and Create Team buttons out of the page header's
right-side action slot. On Teams the button now sits in the tab bar's left
slot, separated from the three tabs by a vertical rule, so the CTA and tabs
read as one left-anchored cluster. On Keys, which has no tabs, the button
anchors left on its own row beneath the title.
2026-07-14 11:39:36 -07:00
Krrish Dholakia
b200d664ee
feat(ui): add adaptive routing settings to Auto-Router v2 (#33146) 2026-07-13 21:29:58 -07:00
devin-ai-integration[bot]
001457af8b
fix(keys): persist key_type so the UI shows correct key scope instead of "All Proxy Models" (#33115)
* fix(ui): derive key model scope so SCIM/management/read-only keys stop showing 'All Proxy Models'

key_type is not persisted on a key (the proxy maps it to allowed_routes and
drops it), so the keys tables only inspected the models list and rendered
'All Proxy Models' for any key with an empty models array, including SCIM,
Management and Read-only keys that cannot call a single model.

Add deriveKeyModelScope(allowed_routes) and render 'No model access' with a
scope tooltip for those recognized scopes; unrestricted, AI-API and custom
keys keep the existing model-list rendering.

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

* refactor(ui): move key_scope helper to components root

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

* feat(keys): persist key_type on virtual keys so the UI reads scope directly

Add a nullable key_type column to LiteLLM_VerificationToken (root, proxy,
and proxy-extras schemas plus an additive migration) and stop dropping the
value in handle_key_type, so management/read_only/llm_api/default keys store
their type alongside the derived allowed_routes. Surface it on the key read
and create response models. The dashboard now prefers the persisted key_type
for the no-inference buckets and keeps the allowed_routes derivation as the
fallback for keys created before the column existed (key_type null), so no
backfill is required.

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

* style(keys): use PEP604 X | None for new key_type annotations to satisfy ruff UP045 budget

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

* fix(keys): add key_type column to LiteLLM_DeletedVerificationToken

The deleted-token archive model inherits key_type from the verification
token, so regenerate/delete flows write key_type into
LiteLLM_DeletedVerificationToken. Add the column (all schemas + migration)
so the archive insert does not fail with FieldNotFoundError.

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

* fix(migrations): regenerate key_type migration via runbook (canonical ADD COLUMN)

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-07-13 18:08:43 -07:00
tin-berri
384bbf7fc2
Merge pull request #33113 from BerriAI/litellm_mcp_oauth_error_relay
fix(mcp): relay upstream OAuth token and DCR rejections instead of a generic 500
2026-07-13 18:06:47 -07:00
yuneng-jiang
e35f5d2b73
feat(ui): rebuild the Teams table on the shared DataTable (#33128)
* feat(ui): rebuild the Teams table on the shared DataTable

The Your Teams tab moves off the Ant Design table onto the shared DataTable that the Virtual Keys page uses, following the new dashboard design. It gains server-side sort, pagination and filtering, a toolbar with a filter drawer and a columns menu, and a per-row actions menu

Sorting is wired only to the columns /v2/team/list can actually order by (team_alias, created_at); Spend / Budget and Updated stay unsorted because the endpoint silently ignores those fields. The design's "Created by" column is dropped since the team object has no such field, and the drawer's "Has keys" filter is dropped for the same reason. The Resources cell shows members, models and keys as colored pills, and the actions menu keeps the existing Edit, Copy team ID and Delete behaviors, with Edit and Delete gated to Admin

The teams grid gets its own unit tests in TeamsPage/TeamsTable.test.tsx. Teams.tsx keeps the create-team modal, delete modal, detail view and tabs, now refreshing the list through React Query invalidation instead of a manual refetch

* fix(ui): match Teams loading skeletons to the rendered row height

The default twoLine and chips skeleton shapes rendered the Team and Resources cells shorter than the loaded row (a real row measures 55px, the old skeleton ~49px), so the loading state looked visibly squat. Give the Team column a custom renderSkeleton that mirrors the two-line IdentityCell (measured 54px) and the Resources column one that mirrors the pills, and mark the hidden Rate Limits column as two-line so it matches when shown

* fix(ui): keep team admins' Members tab by deriving is_team_admin from the selected team

The redesign computed is_team_admin from useTeam(selectedTeamId), but that hook returns teamInfoCall's nested { team_info: { members_with_roles } } shape, so the top-level members_with_roles read was always undefined and is_team_admin was always false. For a non-proxy-admin team admin that hid the Members, Member Permissions and Settings tabs in the team detail view, which broke the team-admin add/remove member e2e tests. Pass the Team object up from the table instead (/v2/team/list returns it with a top-level members_with_roles), matching the pre-redesign behavior; proxy admins were unaffected because is_proxy_admin already granted access

Also point the Delete-a-team e2e at the new kebab: open the row actions menu, then click Delete team, rather than clicking the old inline delete icon
2026-07-13 17:47:14 -07:00
Tin Chi Lo
b70de76df3 Merge origin/litellm_internal_staging (#32980 bridge refresh envelope) into litellm_mcp_oauth_error_relay
The refresh-envelope helpers merge cleanly alongside the faults package. The upstream invalid_grant
special case for bridge refreshes moves inside the post-call except branch (its old location after a
second raise_for_status would be unreachable under the call-time-raise structure this branch
introduced) and now keys off the classified fault; _upstream_oauth_error is dropped since the
classifier already parses the RFC 6749 error field with total accessors
2026-07-13 17:31:17 -07:00
ryan-crabbe-berri
539bc30e04
refactor(ui): migrate callback debounce sites to react-pacer with regression tests (#33043)
* refactor(ui): standardize debounce waits behind shared DEBOUNCE_WAIT_MS constant

* build(ui): bump @tanstack/react-pacer from 0.2.0 to 0.22.1

* refactor(ui): migrate straightforward value debounces to react-pacer

* refactor(ui): migrate callback debounce sites to react-pacer with regression tests

* chore(ui): restore trailing newline in eslint-suppressions.json

* test(ui): mock all pacer debounce hooks in VirtualKeysTable test

* fix(ui): update merged debounce tests for OldTeams to Teams rename
2026-07-13 15:38:34 -07:00
yuneng-jiang
07d2a03dbf
fix(ui): render the sidebar scrollbar with shadcn ScrollArea (#33124)
* fix(ui): render the sidebar scrollbar with shadcn ScrollArea

The sidebar navigation scrolled through a native overflow-y-auto container, so the browser drew its default scrollbar. It now scrolls through the shadcn ScrollArea primitive so the thumb matches the rest of the dashboard

Switching to ScrollArea surfaced a latent styling gap. The Base UI scroll-area, tabs, and separator primitives rely on data-horizontal and data-vertical Tailwind variants that resolve to [data-orientation="horizontal"] and [data-orientation="vertical"], and those variants ship in shadcn's shared stylesheet. The project never imported it, so the classes matched nothing and the scrollbar collapsed to zero width. This adds shadcn as a devDependency and imports shadcn/tailwind.css, which also repairs the vertical tabs and separator styling. See shadcn-ui/ui#9196 for the upstream tracking issue

* refactor(ui): inline the Base UI data-* variants, drop the shadcn dep

The earlier fix imported shadcn/tailwind.css through the shadcn devDependency, which pulled 219 packages and tied the CSS build to shadcn's package exports (an open Turbopack-breaking bug, shadcn-ui/ui#10931). shadcn's model is that we own the components, so the custom variants those components depend on belong in our own stylesheet rather than a runtime dependency. This inlines the nine data-* custom variants and the no-scrollbar utility that the Base UI primitives reference into globals.css, and removes the shadcn package.
2026-07-13 15:35:08 -07:00
yuneng-jiang
da5ed97ff2
fix(ui): drop w-full from page-content wrappers to remove 32px horizontal overflow (#33118)
Several dashboard pages wrap their content in a div styled w-full mx-4, so the
element's width is 100% of the scrollable main while mx-4 adds 16px of margin on
each side. That makes the margin-box 100% + 32px wide, which overflows main by
exactly 32px. Because main uses overflow-y-auto its overflow-x computes to auto,
so the overflow surfaces as a horizontal scrollbar along the bottom of the whole
content area under the pagination

The wrapped block is already full width without w-full, so removing that one
token keeps the layout and drops the overflow to 0. This is the same fix already
applied to the Virtual Keys page in #33112, extended to the remaining pages that
share the wrapper: Models + Endpoints, Tag Management, Organizations, Vector
Stores, AI Hub, and Logging & Alerts
2026-07-13 14:33:31 -07:00
yuneng-jiang
f448ea5762
fix(ui): address Virtual Keys redesign review nits (#33112)
* fix(ui): address Virtual Keys redesign review nits

Restore sorting by budget on the merged Spend / Budget column. The column now
uses a new DataTableMultiSortHeader whose chevron opens a menu offering Spend
and Budget in both directions plus Reset, so the progress-bar cell stays merged
while the sort field becomes an explicit choice. Sorting is server-side, so the
chosen field id (spend or max_budget, both accepted by /key/list) flows straight
through as sort_by

Fill the DataTable to its container width when column resizing is on. The table
width was pinned to the sum of column widths, so hiding columns left an empty
gutter on the right. It now keeps that width as a minimum and stretches to 100%
on underflow while still scrolling on overflow, which also covers the same gap
in TeamVirtualKeysTable since both share the component

Drop the dark background box behind the page-header icon so the Virtual Keys
header reads like the Teams header, and pull the 4-line inline filter lambda in
SearchSelect out into a named matchesQuery helper

Extends the DataTable and VirtualKeysTable tests to cover the new multi-field
sort menu (field id maps to sort_by, active indicator, reset) and the
fill-to-container width

* fix(ui): emphasize the active field in the Spend / Budget sort header

The merged Spend / Budget header always read "Spend / Budget" regardless of
which field drove the sort, so after picking Budget descending there was no way
to tell what was sorted without reopening the menu. The header now builds its
label from the sort fields and emphasizes whichever one is active (bold,
full-strength text) while muting the other, so the sorted column reads at a
glance alongside the direction chevron. Drops the now-redundant title prop since
the label is derived from the fields

* fix(ui): remove w-full so the keys page content stops overflowing by 32px

The virtual keys content wrapper used "w-full mx-4", which sets the width to
100% of the parent and then adds 16px of horizontal margin on each side, so its
margin-box came to 100% + 32px and overflowed the scrollable main region by
exactly 32px. That surfaced as a horizontal scrollbar along the bottom of the
whole content area, under the pagination. A block div is already full-width, so
dropping w-full lets mx-4 inset it correctly with no overflow

* fix(ui): darken the clickable Key cell on hover so it reads as clickable

The Key cell was the click target that opens the key detail, but hovering only
faded the chevron in with no change to the cell itself, so there was no cue that
the area was clickable. Give the cell a subtle muted background and a pointer
cursor on hover. The button spans the full cell (a negative inline margin plus a
matching width offset so the hover fill reaches both cell edges while the title
stays aligned with the other columns)
2026-07-13 14:02:27 -07:00
Tin Chi Lo
f90382584b fix(mcp): relay upstream OAuth token and DCR rejections instead of a generic 500
An upstream token endpoint rejection (e.g. Google requiring client_secret even for PKCE web clients) escaped exchange_token_with_server as a raw httpx.HTTPStatusError, which the global exception handler turned into an opaque 500 Internal server error in the create-flow UI. The RFC 6749 section 5.2 error body the IdP sent (error, error_description, error_uri) is now relayed with the upstream's own 400/401 status; rejections outside the section 5.2 contract map to 502 so a broken upstream is not misattributed to the caller. The same relay covers the non-bridge DCR registration arm, and a 200 token response without a usable access_token now answers 502 instead of a KeyError 500. The catch wraps the post call itself because litellm's AsyncHTTPHandler raises MaskedHTTPStatusError at call time, which also made the pre-existing bridge-relay status check unreachable in production. The dashboard's token exchange error message now composes error and error_description so the form shows the IdP's reason
2026-07-13 13:16:45 -07:00
yuneng-jiang
fa09cde3c0
feat(ui): rebuild the Virtual Keys table on the shared DataTable (#32991)
* feat(ui): rebuild the Virtual Keys table on the shared DataTable

Replaces the hand-rolled Tremor table and bespoke toolbar/pagination on the admin
Virtual Keys page with the shared DataTable: server-side sort, paginate, and
filter, a sticky scrolling body, a search plus column-visibility plus filters
toolbar, a right-side filter drawer, and a rows-per-page footer. A page header
with the existing key icon carries the Create New Key action.

Adds reusable, shadcn-default building blocks for the tables migrating onto the
DataTable next: shared IdentityCell, ModelsCell, and SpendBudgetCell in
shared/table_cells, plus a shared PageHeader. The models cell reveals overflow in
a hover tooltip and the spend/budget cell uses the Meter primitive.

All data and domain logic is preserved, including the useKeys query, team and org
alias resolution, the user popover, and the KeyInfoView detail swap. The rich
async Team/Org/Alias filters move into the drawer, and the toolbar search maps to
the key-alias substring search. Status now also reflects key expiry alongside
blocked and SCIM-blocked.

The VirtualKeysTable tests are updated to the new markup and extended with focused
coverage for each new shared cell

* fix(ui): address Virtual Keys redesign review feedback

Fold the status badge into the clickable Key cell and drop the separate Status
column so a key's alias, secret, and status read as one unit. The Key cell is
now the single click target that opens the key detail; the whole-row click is
removed

Migrate the filter drawer off AntD to shadcn. A new Combobox composed from
Popover and Input backs the Team, Organization, and Key Alias filters, keeping
search and the alias infinite-scroll

Show $0.00 for zero spend instead of a hyphen, and extend the shared DataTable
with badge, chips, and meter skeleton shapes so the loading state matches the
loaded cells (status pill, model chips, spend meter) rather than uniform bars

Fix key sorting: the Key column sent its column id "key" as sort_by, which
/key/list rejects with 400. It now sorts by the backend field key_alias

* fix(ui): use the shadcn base combobox and refine the keys filters and skeletons

Replace the hand-rolled filter combobox with the supported shadcn Base UI combobox
(ui/combobox, added via the CLI and reused through a small SearchSelect wrapper).
Its vended input-group and textarea deps are written for React 19 (plain functions
with ref-as-prop); this app is on React 18, where those subcomponents drop the refs
Base UI passes for focus and anchoring, so InputGroupInput, InputGroupButton, and
ComboboxTrigger are adapted to forwardRef. Those ui/ files now diverge from the
registry, and a future shadcn add would overwrite the adaptation until the app moves
to React 19. Adds class-variance-authority, which input-group needs

Give loading skeletons a per-column renderSkeleton escape hatch on the shared
DataTable and mirror the Key cell exactly (alias line, secret, status pill), so
skeleton rows match the real rows instead of being shorter and simpler

Resolve the automated review: the toolbar search and the drawer Key Alias filter
both mapped to the key-alias query, so the search silently overrode the drawer value
while its chip stayed visible. Consolidate to a single alias search in the toolbar
(placeholder now "Search by key alias…") and drop the redundant drawer field. Re-add
coverage for the Created By column's alias-over-email display

Refine the Team and Organization filters: they match on name and id, so the labels
read "Team" and "Organization" rather than "... ID", each option shows the name with
the id on a muted second line instead of "name (id)", and the active-filter chip
shows the friendly name

* chore(ui): drop duplicate class-variance-authority, use the repo cva package in input-group
2026-07-13 12:49:01 -07:00
ryan-crabbe-berri
aa9dcb43cf
refactor(ui): standardize debounce waits behind shared DEBOUNCE_WAIT_MS constant (#33040) 2026-07-13 12:19:57 -07:00
ryan-crabbe-berri
7fce761cde
fix(ui): respect litellm_key_header_name in BYOK credential save and workflow runs fetches (#33103) 2026-07-13 12:19:38 -07:00
ryan-crabbe-berri
e6d916b82e
fix: show and allow editing team model aliases after team creation (#33047)
* refactor(ui): rename OldTeams component file to Teams

* fix: show and allow editing team model aliases after team creation

* fix(ui): mark team model_aliases as nullable to match the prisma schema
2026-07-13 09:31:10 -07:00
Krrish Dholakia
26ab730bfa
feat(router): soft-floor adaptive mode for complexity router (#32947)
* feat(router): soft-floor adaptive mode for complexity router

Let complexity_router_config.adaptive=true Thompson-sample across the
union of tier pools with a tier-distance penalty, and wire the existing
adaptive post-call bandit so mis-tiered requests can still recover.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(router): reattach adaptive hooks for hybrid complexity

Finalize was wiping every AdaptiveRouterPostCallHook and only
re-registering standalone auto_router/adaptive_router deployments,
so complexity adaptive=true never received bandit updates.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(router): drop unnecessary hybrid docstrings

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(router): attribute adaptive feedback

Credit user reactions to the model that produced the previous response while keeping current-response signals on the serving model

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(router): tune hybrid cold defaults

Use the cost-weighted policy that beat equal-pool complexity in the full bakeoff, and make the committed harness compare identical tier pools

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(router): preserve hybrid cold quality floor

Sample only unobserved models in the classified tier until feedback exists, then apply adaptive scoring without mis-penalizing models shared across tiers

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(router): bound feedback context cache

Cap retained session feedback so unique session IDs cannot exhaust router memory

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(router): preserve exhaustion signals

Include tool-result exhaustion in adaptive feedback and clear strict lint regressions blocking CI

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(router): remove stale owner cache

Remove obsolete attribution state, tighten the embedded router type, and keep the test diff focused on adaptive behavior

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(router): centralize hook cleanup

Use the callback manager to discover and remove adaptive hooks across every registered callback list

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-11 21:56:33 -07:00
Abhimanyu Kapur
8d358574ea
Merge pull request #32978 from BerriAI/litellm_auto_router_qol
fix(auto_router): filter embedding models in complexity tab dropdowns, require all tiers, inline validation
2026-07-11 19:49:30 -07:00
Abhimanyu Kapur
0e90f61e48 fix(auto_router): inline error for missing LLM classifier model
Selecting the LLM classifier without picking a model only surfaced a
toast on submit; the classifier model select now gets the same red
outline and helper text as the tier and embedding selects once a submit
attempt has failed.
2026-07-11 19:21:51 -07:00
Abhimanyu Kapur
d6883d15b0 fix(auto_router): flag name field and tier fields together on empty submit
Clicking Add Auto Router with the name empty returned early with only a
toast, so blank tier selects never got their inline error state. The
empty-name branch now sets showValidationErrors and triggers antd
validation on the name field, so every unfilled mandatory field is
flagged at once. Adds a regression test for the tab component.
2026-07-11 19:08:07 -07:00
Abhimanyu Kapur
3464b5e7df fix(auto_router): reset inline validation errors when switching router type 2026-07-11 19:03:48 -07:00
Abhimanyu Kapur
0de308a5b8 fix(auto_router): filter embedding models out of tier selects, require all tiers, add inline validation
The Add Auto Router complexity tab let chat models fill the embedding-model
slot (and vice versa) since neither dropdown filtered on ModelGroup.mode, and
submit only required at least one of the four tiers instead of all four. Adds
getMissingTiersError alongside the existing getSemanticConfigError, and
highlights unfilled tier/embedding selects inline once a submit attempt fails.
2026-07-11 18:18:37 -07:00
ryan-crabbe-berri
5fc1a3c671
refactor(ui): convert endpoint usage charts to shadcn/recharts (#32723)
* refactor(ui): convert endpoint usage charts to shadcn/recharts

Adds a LineChart wrapper to the shared charts kit, mirroring the
BarChart/AreaChart composition with connectNulls and curveType props,
and converts EndpointUsageBarChart and EndpointUsageLineChart from
tremor to the shared wrappers. Both endpoint chart tests now assert on
real recharts SVG output instead of tremor mocks.

* refactor(ui): drop unused endpointData prop from EndpointUsageLineChart

* fix(ui): point endpoint chart test type imports at the UsagePage types alias after colocation move
2026-07-11 18:16:18 -07:00
ryan-crabbe-berri
b6dbda48f9
refactor(ui): colocate the mcp-servers view, keeping the shared mcp_tools surface (#32968)
* refactor(ui): colocate the usage view, keeping the shared usage components

Split for the usage (UsagePage) segment. Most of the folder is the usage page's
own view, but four pieces are reused elsewhere and stay in @/components/UsagePage:
TopKeyView (old-usage), KeyModelUsageView and value_formatters (activity_metrics),
and the shared types (activity_metrics, chartUtils). The other 21 files move into
usage/_components, preserving the folder structure.

The external consumers import only the retained files, so they are untouched. The
moved files' imports of the retained files become @/components/UsagePage paths,
other escaping relative imports are absolutized, and lint suppressions are re-keyed
for moved files only. No behavior change.

* refactor(ui): colocate the mcp-servers view, keeping the shared mcp_tools surface
2026-07-11 18:01:41 -07:00
Abhimanyu Kapur
81db114c40
Merge pull request #32950 from BerriAI/litellm_auto_router_test_connection
feat(ui): working Test Connection for the complexity auto router
2026-07-11 17:45:46 -07:00