Commit graph

5061 commits

Author SHA1 Message Date
yuneng-jiang
2ee15a6efb
refactor(ui): migrate user, policy, and margin forms to shadcn (#37305)
* refactor(ui): migrate user, policy, and margin forms to shadcn

Move four dashboard forms off antd Form and Tremor onto the shadcn field
kit, with react-hook-form where the form owns its own submit. Submit
payloads are unchanged: edit_user still emits exactly six keys with spend
as a number and max_budget as a string, policy_test_panel still omits
empty context keys, and add_attachment_form still builds the same
attachment body.

add_margin_form had no Form of its own and no bound field names, so its
Form.Item rules were inert; it keeps its parent-owned state props and
only swaps the presentation.

Adds characterization tests for edit_user and policy_test_panel, both
proven green against the antd originals before the migration, plus a case
pinning the provider value add_margin_form reports upward. The existing
add_attachment_form and add_margin_form suites pass unedited.

Extracts TokenSelect for the tag and alias inputs shared across the
policy forms, keeping antd's token separators and blur-commit behavior.

* refactor(ui): seed the user edit form by remount instead of an effect

Key the form on the edited user so react-hook-form seeds from its
defaults on each user, replacing the effect that reset the form and the
exhaustive-deps suppression that came with it. Cancel and submit still
reset, so reopening the same user shows stored values.

* test(ui): pin that the user edit form forwards null fields unchanged

The proxy returns null rather than omitting unset fields, and antd
forwarded whatever it received. The only fixture seeded every field, so
nothing proved the migrated form still emits null instead of an empty
string. Proven against the antd original first, and it fails if
toFormValues coerces.
2026-08-18 18:42:16 +00:00
yuneng-jiang
5e2d6addc4
refactor(ui): migrate user, logging and policy forms to react-hook-form and shadcn (#37303)
* fix(ui): stop the policy modal cancelling its own save

The Create Policy and Update Policy buttons are Tremor buttons rendered
inside an antd Form. Tremor does not set a type, so both default to
type="submit" and a click ran two things at once: handleSubmit's own
form.validateFields(), and rc-field-form's onSubmit, which calls
formInstance.submit() and validates a second time.

rc-field-form keeps only the newest validation promise, so the first one
resolved as outOfDate and rejected with an empty errorFields list.
handleSubmit read that as a failure, so it never called createPolicy or
updatePolicy and instead reported "Failed to save policy". Creating and
editing a simple policy from the UI could not succeed.

Marking both footer buttons type="button" leaves the submit path solely
with handleSubmit. The new test file pins the request bodies for create
and update, and fails without this change.

* refactor(ui): migrate user, logging and policy forms to react-hook-form and shadcn

Moves four antd Form units onto react-hook-form plus the shadcn kit and
semantic color tokens, keeping every submit payload byte-identical.

- Settings/AdminSettings/LoggingSettings
- CreateUserButton
- users/_components/user_edit_view
- policies/_components/add_policy_form

* chore(ui): drop the eslint suppressions the migrated forms no longer need

* fix(ui): keep users with null optional fields editable

The proxy returns null rather than omitting user_alias, user_role,
budget_duration and metadata, and the new zod shape only allowed
undefined, so opening any such user and saving failed validation.
2026-08-18 18:42:12 +00:00
ryan-crabbe-berri
9ec0145986
feat(proxy): add /team/daily/activity/aggregated and switch the Usage team tab to it (#36562)
* feat(proxy): add /team/daily/activity/aggregated and use it in the Usage UI

The Team Usage tab drained row-paginated pages client side, which painted
newest days first and drew duplicate bars when a day's rows straddled a
page boundary. Serve the whole range in one SQL GROUPING SETS pass instead:
the aggregated query gains optional per-entity rollup levels (entity as the
most-significant GROUPING bit) so breakdown.entities keeps per-team spend,
aliases, and per-key splits. The endpoint shares the paginated route's
scoping via _resolve_team_daily_activity_scope, accepts the timezone the UI
already sends, and the api_key filter now takes a list so non-admin member
scoping works. The dashboard tries the aggregated endpoint first and falls
back to page draining on failure.

* chore: ratchet B008 budget down by the endpoint converted to Annotated Depends

* chore: keep mutable-ok suppressions on their annotation lines after formatting

* fix(proxy): reject malformed or over-wide ranges on team aggregated activity

The aggregated endpoint has no pagination bounding its work, so validate
start_date and end_date as real dates and cap the span at 400 days. The
dashboard's widest presets fit well inside the cap, and an over-cap range
falls back to the paginated flow. Also trim implementation comments that
restated the grouping-set code.

* fix(proxy): parse aggregated range bounds as UTC to satisfy DTZ007

* refactor(proxy): fetch entity rollups with a companion query instead of extending the main one

The entity-as-extra-GROUPING-bit approach made the bitmask layout
mode-dependent: the same constant meant (date) for normal rows and
(date, entity) for entity rows, disambiguated by masking. Split it out:
the shared WHERE builder feeds both the untouched main query and a small
per-entity rollup query keyed by GROUPING(api_key), run concurrently, and
a fold writes breakdown.entities onto the built response.

* refactor(proxy): share the daily-activity error and entity-metadata shapes

The type-discipline ceiling for LIT002 ratcheted down on staging, so the new
aggregated endpoint had to stop hand-rolling collections the codebase already
builds elsewhere. Funnel the `{"error": ...}` detail through one construction
site, turn the range validator into an error-as-value, reuse a single
entity-metadata lookup for both breakdown paths, and widen
get_api_key_metadata to any set so callers stop copying frozensets.
2026-08-18 11:29:57 -07:00
ryan-crabbe-berri
83ae623733
refactor(ui): codemod every toast call site onto lib/toast and delete the antd-era facades (#37253)
MessageManager and NotificationManager were thin facades over lib/toast since #37207. This
rewrites their ~750 call sites (226 files) to import { toast } from @/lib/toast directly:
success/info/warning/error keep their names, fromBackend becomes fromError, destroy/clear
become dismiss. The one config-object caller (CreateMCPServer's admin-review branch) becomes
an explicit toast.success(message, { description }). Behaviour is unchanged: no production
caller passed a duration, so every toast keeps the same kind, title and default duration.

Tests: the global vitest mock now targets @/lib/toast (toast.test.ts opts back out with
vi.unmock), so the per-file vi.mock boilerplate for the facades is deleted and assertions read
toast.success / toast.fromError. The two facade files, their test and their filename-case
suppressions are removed, along with the commented-out facade calls left in networking.tsx
2026-08-18 18:24:49 +00:00
tin-berri
8159f240c4
feat(complexity_router): plan-mode tier floor for coding-agent clients (#37230)
* feat(complexity_router): plan-mode tier floor for coding-agent clients

Claude Code and Copilot signal plan mode only through client-injected prompt
text, which the ask-extraction path deliberately strips, so the router could
never see it. Detect the sentinels on the raw wire body and route those
requests to at least plan_mode_min_tier.

The floor is raise-only and transient: classifier results above it still win,
it overrides a session-affinity pin only on turns carrying the sentinel
without rewriting the pin, and plan_mode decisions are not pinnable, so the
first turn after plan mode exits routes as if plan mode had never happened.
Classification is skipped when the floor is the top configured tier. On
adaptive routers the floor rides _soft_floor_pick as a hard_floor that
excludes below-floor candidates, closing the adaptive_eligible=all gap where
a request classified at or above the floor could still route below it.
Detection is staleness-aware: only leading system content and the newest-ask
tail count, so sentinels surviving in history after plan mode exits, built-in
or operator-supplied, never fire. Custom tier sets are supported with
severity from the tier_definitions list order, same as keyword_tier_rules.
Off by default; decisions are recorded with the new plan_mode cause and the
matched sentinel in matched_keyword

* fix(complexity_router): gate pin writes and the failure exit on sentinel presence, not the floor binding

A plan-mode turn classified at or above the floor keeps its ordinary cause,
but pinning it would carry a plan-mode-shaped choice past plan mode's exit
(on adaptive routers the hard floor constrained that pick), so no
sentinel-carrying turn writes the session pin. The default_model failure
exit is skipped for sentinel turns for the same reason: default_model's
placeholder tier can equal the floor while default_model itself sits in no
pool the floor can vouch for
2026-08-18 10:24:11 -07:00
yuneng-jiang
e09bbe9a14
refactor(ui): migrate tag and memory forms to react-hook-form and shadcn (#37266)
Moves CreateTagModal, the tag detail editor and MemoryEditModal off antd Form
onto react-hook-form with the shadcn field kit, and onto semantic colour
tokens so the pages follow the dashboard theme.

Submitted payloads are unchanged and pinned by tests. The collapsible budget
sections keep values typed into them when collapsed and reopened, matching
what antd's store did, rather than reverting to the seeded default.
2026-08-18 09:05:53 -07:00
yuneng-jiang
b08032c5f7
refactor(ui): migrate budget and skill forms to react-hook-form and shadcn (#37262)
* refactor(ui): migrate budget and skill forms to react-hook-form and shadcn

Moves three dashboard forms off antd Form onto react-hook-form plus the shared
shadcn field kit, and onto semantic color tokens so they render correctly in dark
mode. The submitted request bodies are unchanged.

Budget create and edit previously relied on antd InputNumber precision={2}, which
rounds the submitted value rather than only the display. That rounding is now an
explicit shared helper so the wire payload stays identical, and the helper carries
unit tests covering key presence, null passthrough, negatives and non-finite input.

Characterization tests for both budget modals were written against the antd
implementation first and pass unchanged against the migrated components, which is
what pins the payload. They are named .integration.test.tsx per the dashboard test
tiers, with the pure rounding logic unit tested separately.

* fix(ui): keep collapsed Optional Settings values so reopening does not lose them

react-hook-form shouldUnregister deletes a field's value when its section
unmounts, so typing a budget, collapsing Optional Settings and reopening it
submitted the seeded default instead of what was typed. antd reported only
mounted fields in onFinish but preserved their values in its store, so the
two behaviours have to be reproduced separately.

Drop shouldUnregister, drive the section from controlled state, and blank the
section's fields at submit while it is closed. Seed the edit form from the
five form fields rather than the whole budget record, which shouldUnregister
had been masking.
2026-08-18 08:51:22 -07:00
yuneng-jiang
0896015927
test(ui): await the playground model combobox before clicking it (#36850)
The ChatUI playground test helper looked up the model combobox with a
synchronous getByPlaceholderText. That control renders its placeholder
from the model-loading flag, so the text is "Loading models..." until
the mocked fetch resolves, and the element the helper wants does not
exist yet. Under a loaded full-suite run the query could land inside
that window and fail with "Unable to find an element with the
placeholder text of: Select a Model", while the same test passed in
isolation every time.

Switch the helper to findByPlaceholderText so it waits for the control
to come back after loading.
2026-08-18 08:43:25 -07:00
Mateo Wang
4d57bf0bdd
Merge pull request #37225 from BerriAI/litellm_lit5650_guardrail_usage_units
feat(guardrails): track bedrock guardrail usage units per invocation
2026-08-17 18:55:22 -07:00
ryan-crabbe-berri
68d4ba5da5
refactor(ui): move dashboard toasts from antd message/notification onto sonner (#37207)
* refactor(ui): move dashboard toasts from antd message/notification onto sonner

Add lib/toast.ts as the single toast surface (success/info/warning/error/
fromError/dismiss) backed by sonner, with a <Toaster /> in the root layout.
fromError titles a toast from the proxy error type or the HTTP status instead
of matching prose phrases, and shows the extracted proxy message as the
description.

MessageManager and NotificationManager become thin facades over lib/toast so
the ~250 existing call sites keep working; the mutable antd instance setters,
setMessageInstance/setNotificationInstance, and the antd App/message/
notification providers in AntdGlobalProvider are gone. Prunes the eslint
suppression baseline accordingly.

* test(ui): mock the MessageManager seam in the Fallbacks tests and drop toast doc comments

AddFallbacks and FallbackSelectionForm asserted on a mocked antd message spy
that MessageManager no longer calls; they now mock the facade the components
import. Also removes the explanatory comments Greptile flagged in lib/toast.ts
and both facades.

* fix(ui): keep NotificationManager's antd config-object contract on the sonner facade

success/info/warning/error accept the { message, description, duration } object
form again (CreateMCPServer's admin-review notice uses it) and fromBackend keeps
its extra.duration seconds argument, both mapped onto lib/toast. Prunes stale
suppressions picked up by the rebase.

* feat(ui): read the proxy error type and code out of JSON envelopes embedded in string errors

Legacy networking helpers throw new Error(responseText) and callers prefix
that text, so the envelope arrives as a substring. fromError now parses the
first embedded JSON object for type/code and shows the unwrapped message in
its place, so those toasts get a status title (Request Error, Not Found) and
a readable description instead of raw JSON.
2026-08-17 18:22:41 -07:00
Mateo Wang
b69068c290
Merge pull request #26900 from BerriAI/litellm_model-deprecation-alerts-55bc
feat(proxy): proactive model deprecation alerts and `/model/deprecations` endpoint
2026-08-17 18:15:20 -07:00
tin-berri
1648273469
feat(ui): configure the auto router's heuristic scorer from the Admin UI (#37216)
* feat(ui): configure the auto router's heuristic scorer from the Admin UI

The complexity router has always read tier_boundaries, token_thresholds and
dimension_weights from its config, and /model/new already persists them, but the
dashboard had no control for any of the three, so tuning the scorer meant editing
config.yaml by hand.

Adds an "Advanced scoring" panel to the classification section, shown whenever the
scorer actually runs: on a heuristic router, and on an LLM classifier that falls back
to the heuristic. An untouched knob is omitted from the payload, so a router keeps
tracking the shipped defaults instead of freezing today's numbers.

The three keys join MANAGED_COMPLEXITY_ROUTER_KEYS, so the edit modal now rebuilds
them from form state rather than carrying the stored copy through. That makes
hydration load-bearing, and it hydrates an absent knob to undefined rather than to
the defaults, so an untouched save cannot pin a router that was tracking them.

The "How Classification Works" card now reads the configured boundaries instead of
hardcoding 0.15 / 0.35 / 0.60, which would otherwise start lying the moment an
operator changed them.

* test(complexity_router): pin the dashboard scorer defaults against config.py

The Admin UI keeps its own copy of the boundary, threshold and weight defaults to
prefill its controls. The copy is display only, since an untouched knob is omitted
from the payload, so drift shows a stale placeholder rather than pinning a router.
Nothing caught that drift before, and a blank or dead control is worse, so the two
copies and the dimension key set are pinned against each other here.

* fix(ui): surface out-of-order scorer thresholds as an error, not a hint

Boundaries that decrease make the tiers between them unreachable, which silently
changes where traffic goes, so amber body text undersold it. Saving stays allowed:
a router configured this way in config.yaml would otherwise become uneditable in
the UI for every unrelated change.

* fix(test): search the default-model picker instead of trusting option order

The pinned model is appended after every model the presets contribute, and that list
has reached 11, so the option fell outside the virtualized dropdown's rendered slice
and the two default-model-pin cases failed on staging. CI only runs them when this
file is touched, which is why they went unnoticed. Searching for the model filters
the list to it, so the cases no longer depend on how long the preset list grows.

* revert(test): drop the dashboard scorer defaults parity test

It parsed TypeScript from Python with a hand-rolled brace matcher and a numeric
literal regex, which is not a mechanism this repo should carry: two review rounds
went into fixing the parser rather than the feature. The UI copy of the defaults is
display only, since an untouched knob is omitted from the payload, so drift shows a
stale placeholder and cannot pin a router.

* fix(ui): clamp the scorer inputs and drive the panel from one group spec

min and max are inert attributes on a text input, so the fields accepted a weight of
999, a boundary of -50, and Infinity, and persisted them into the router config.
Values are now clamped on commit and non-finite input is refused.

The three sections were near copies of each other, so they now render from a single
group spec, which also removes the triplicated warning logic.

Moves the scorer constants and types into heuristic_scoring_knobs, the leaf module.
Reading them back through ComplexityRouterConfig was a cycle, so the top-level
DIMENSION_KEYS.map in the panel ran while the constant was still undefined and every
test importing it failed to collect.

* feat(ui): serve the scorer defaults from the proxy instead of mirroring them

The dashboard kept its own copy of DEFAULT_TIER_BOUNDARIES, DEFAULT_TOKEN_THRESHOLDS
and DEFAULT_DIMENSION_WEIGHTS to prefill the Advanced scoring controls. Two copies of
one fact, and the earlier attempt to police the gap parsed TypeScript from a Python
test, which was worse than the problem.

GET /public/complexity_router/scorer_defaults now returns them, following the
/public/providers/fields pattern: a typed response model, the dashboard fetching it
through a react-query hook next to useProviderFields. The controls and the "How
Classification Works" card both read that, so a recalibration of the defaults can no
longer leave the form stating numbers the router stopped using.

The dimension set now comes from the proxy too, so a dimension added backend-side
renders without a dashboard change, under its raw key until it is given a label.
Hydration keeps a stored dict exactly as stored rather than filling it from a local
copy, since the backend already defaults any key omitted at scoring time.

* fix(types): type the scorer defaults response as Mapping, not dict

LIT001 gates mutable collections in annotations, and the three dict fields tripped it.
Mapping is what the codebase already uses for a read-only map on a response model, and
the endpoint hands the config constants over directly rather than copying them into a
fresh dict, which would have traded the LIT001 hit for a LIT002 one.

* test(ui): stub the scorer defaults request for the auto-router tree

The Advanced scoring panel and the classification card read the shipped defaults over
the network, so every render of that tree in a test paid for a request jsdom cannot
serve. That was enough to push the slowest default-model-pin case past its 30s timeout
on CI, where the suite runs 14 forks in parallel.

One fixture in tests/mocks, pulled in by a single vi.mock line per test file, rather
than the same stub pasted into each of the seven that render the tree.

* fix(ui): tell a failed scorer-defaults load apart from a slow one

The panel read only the query's data, so a permanent failure was indistinguishable
from a request still in flight and it sat on "Loading the shipped defaults..." for
good. It now branches on the query state: pending says loading, an error says so and
offers a retry, and the values the router already overrides stay visible and editable
either way.

Two more places had the same flaw. The classification card silently dropped the tier
ranges it used to always show, and now says they could not be loaded. The weight total
was summed over whatever keys were present, so a failed load made it state a total
built from the overrides alone; a total is only shown when the dimension set is known.
2026-08-18 01:10:13 +00:00
mateo-berri
7017df5732 fix(alerting): back off a day after a deprecation pass raises and label the alert in the UI
A pass that raises (a missing Slack webhook, say) now waits the daily interval instead of logging the
same exception every 30 seconds, and the Admin UI alerting settings list the new alert type so it can be
toggled like the others
2026-08-17 17:57:37 -07:00
tin-berri
e2d8fc919f
feat(complexity_router): operator-defined tier sets for the LLM classifier (#37226) 2026-08-17 17:57:30 -07:00
yuneng-jiang
175b639bc3
Merge pull request #37227 from BerriAI/litellm_/resolve-stacked-pr-conflicts-e95c06
test(ui): settle the in-flight search before the loading tests end
2026-08-17 17:34:41 -07:00
Mateo Wang
9cd7696156
Merge pull request #37229 from BerriAI/litellm_comprehend_medical_passthrough
feat(proxy): add Amazon Comprehend Medical passthrough provider
2026-08-17 17:25:01 -07:00
mateo-berri
5437139b94 chore(guardrails): sync lazy OpenAPI snapshot and dashboard types for usage units
Regenerates the guardrails and policy_engine fragments of the lazy OpenAPI
snapshot for the usage-unit fields, regenerates schema.d.ts from it, and
exports DailyGuardrailUsageUnitsRepository next to its sibling repositories
2026-08-17 16:43:58 -07:00
Mateo Wang
b70df5bdf6
Merge pull request #37194 from BerriAI/litellm_azure_di_native_ocr_format
feat(ocr): return Azure Document Intelligence's native payload from /v1/ocr via req_format=native
2026-08-17 16:30:27 -07:00
mateo-berri
915a1cabcd feat(proxy): add Amazon Comprehend Medical passthrough provider 2026-08-17 15:44:06 -07:00
Yuneng Jiang
3cc16de0d2
test(ui): settle the in-flight search before the loading tests end
Both loading tests mock searchToolQueryCall as a promise that resolves on a
timer, assert the loading affordance, then return with that promise still in
flight. When the worker outlives the file's jsdom environment, the component's
setIsLoading(false) runs against a torn-down window, and React reports
"ReferenceError: window is not defined" as an unhandled rejection. Vitest counts
that as an error, so ui-unit-tests fails the job while reporting every one of
its 7351 tests as passed.

Awaiting the settled state keeps both assertions and leaves nothing pending at
teardown.
2026-08-17 15:41:31 -07:00
yuneng-jiang
6a0bd0dffb
Merge pull request #37019 from BerriAI/litellm_mock_assertion_strength
test(ui): assert what collaborators are called with, not merely that they were
2026-08-17 15:18:21 -07:00
tin-berri
c1aae9d723
feat(shadow-eval): name the shadowed key in job responses and the UI headline (#37221) 2026-08-17 22:12:59 +00:00
Yuneng Jiang
6db82c0d15 test(ui): assert what collaborators are called with, not merely that they were
A bare toHaveBeenCalled() passes no matter what the caller passed, so the CSV
export could serialize the wrong rows, write the wrong content type, and name
the file wrong while its test stayed green.

Strengthens the load-bearing cases in three files onto the arguments that carry
the behaviour: the rows handed to the CSV serializer, the blob content type, the
anchor that gets attached and cleaned up, and the specific message each
validation failure shows the user. Two of the discount and margin tests
previously asserted the same bare call for different validation failures, so
neither could tell the two apart.

Each rewrite was proven by mutating the source it covers and confirming the test
goes red where the bare assertion stayed green.
2026-08-17 15:09:06 -07:00
yuneng-jiang
0d3dd8b4e1
Merge pull request #37018 from BerriAI/litellm_testing_library_lint_rules
test(ui): gate dashboard test assertions with testing-library and jest-dom rules
2026-08-17 15:09:02 -07:00
ryan-crabbe-berri
427ed9e2d1
fix(ui): search every user in the Usage page user filter (#37206)
The User Usage view handed EntityUsage a static entityList holding only
the first /user/list page, so its filter could only find the 50 most
recently created users and anyone beyond that page, including users
with spend in the selected period, was unreachable

Add a self-contained UserDropdown that owns useInfiniteUsers (server-side
search plus load-more, mirroring TeamDropdown) and use it both as the
User Usage filterSlot and for the Global Usage user filter. Resolve a
selected user that is outside the loaded page by id so its label
survives view round-trips. Drop the now dead single-select branch from
UsageExportHeader
2026-08-17 15:00:38 -07:00
Yuneng Jiang
41d6dbb50c build(ui): gate dashboard test assertions with testing-library and jest-dom
Enables 14 rules at error with zero violations, so the vacuous-assertion class
the previous commit fixed cannot come back. No budget file and no suppressions
baseline: a rule is on only if it is already at zero.

prefer-to-have-value stays off. It matches any attribute whose name contains
"value", so it rewrites toHaveAttribute("aria-valuenow", n) into toHaveValue(n),
and jest-dom's toHaveValue supports only form controls, which breaks every
role="meter" element the dashboard renders.

Records the enabled set, the rules left off with their measured counts, and the
seven ways these plugins' autofixers produce broken output.
2026-08-17 14:46:55 -07:00
Yuneng Jiang
6670061a33 test(ui): assert presence and state so dashboard tests fail when behaviour breaks
One test could not fail: it asserted on the Promise returned by an unawaited
findAllByText, and a pending Promise is always truthy, so the organization
detail view had no working coverage at all.

Rewrites 316 assertions across 128 files onto matchers that describe what the
user perceives, and awaits the queries that were being compared as Promises.

Most of this was mechanical, but the fixers behind these rules are not
trustworthy, so every site they damaged was repaired by hand. The quiet one
worth naming: prefer-to-have-text-content wraps strings in new RegExp() without
escaping, turning toContain("100K+ requests") into a pattern meaning "100
followed by one-or-more K". That compiles, lints clean, and keeps passing while
no longer asserting what it claims.
2026-08-17 14:46:55 -07:00
Yuneng Jiang
48d3cb83f6
test(ui): filter the default-model combobox instead of indexing into it
The pin tests picked the pinned model by position in the option list. antd
virtualizes that list and jsdom reports every row as zero height, so only the
first window ever renders, and adding the Lite preset to the bundled JSON pushed
the pin out of it. Both cases have been red on litellm_internal_staging since
that preset landed.

Typing the model name filters the list to the pin, so the click no longer
depends on where the pin sits among the presets.
2026-08-17 14:30:22 -07:00
mateo-berri
127b426991 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_azure_di_native_ocr_format 2026-08-17 14:28:17 -07:00
Yuneng Jiang
b0f00e3a19
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/ui-ux-patterns-audit-1d04bc 2026-08-17 14:19:30 -07:00
Yuneng Jiang
75abe09f47
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/ui-ux-patterns-audit-1d04bc
The auto-router tests moved onto accessible queries here while staging added a
"Lite" preset and a default-model pin, so the option-label expectations take
staging's list read through this branch's visibleOptions helper.

Staging's new pin tests reached for antd's internal classes, which the lint rule
this branch enables rejects. The edit-modal cases now read the rendered
selection through one selectedValueIn helper, and the clear-affordance click in
ComplexityRouterConfig keeps a reasoned suppression since antd marks that icon
aria-hidden.
2026-08-17 14:14:08 -07:00
yuneng-jiang
4503efc8b6
Merge pull request #37061 from BerriAI/litellm_/invite-button-shadcn-decouple-1463e5
refactor(ui): decouple bulk invite from the invite user button
2026-08-17 14:03:54 -07:00
Mateo Wang
f1e1c89600
Merge pull request #36790 from daniel-meismer-zocdoc/litellm_searchable_usage_report_user_filter
fix(ui): make per-user usage filter searchable
2026-08-17 13:39:30 -07:00
Mateo Wang
e11fe1d6cc
Merge pull request #36781 from daniel-meismer-zocdoc/feature/request-logs-user-id-filter
feat(ui): add user ID request log filter
2026-08-17 13:32:26 -07:00
mateo-berri
d857ae3844 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_pr36790_drive
# Conflicts:
#	ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/EntityUsage/EntityUsage.tsx
#	ui/litellm-dashboard/src/components/EntityUsageExport/UsageExportHeader.tsx
2026-08-17 13:13:25 -07:00
ryan-crabbe-berri
d0a815d2e1
fix(ui): stop pairing key spend with the team budget when a key has no budget (#37196)
The key overview card and the Virtual Keys spend meter fell back to the parent team's max_budget as the denominator while the numerator stayed the key's own spend, so a $0.50 key on a $1,200 team read as "$0.50 of $1,200 (Team)" and drew a meter against a limit that governs the whole team's aggregate spend, not this key. Both surfaces now show Unlimited for a budgetless key and, when the parent team or organization does carry a budget, a hover hint listing those inherited caps so the reader knows what still gates the key
2026-08-17 13:03:05 -07:00
mateo-berri
11e2341fc9 Merge branch 'litellm_internal_staging' into feature/request-logs-user-id-filter 2026-08-17 13:01:19 -07:00
Thijmen Stavenuiter
c7abb519a1 Merge branch 'litellm_internal_staging' of https://github.com/BerriAI/litellm into key-budget-window-usage 2026-08-17 21:04:01 +02:00
mateo
57d739b433 feat(ocr): add req_format=native to return Azure Document Intelligence's own analyzeResult payload
Callers can opt into the provider's raw operation response on /v1/ocr with the x-req-format: native header (or req_format in the body) while page-based cost tracking keeps reading usage_info off the normalized response.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-17 18:03:53 +00:00
ryan-crabbe-berri
9d40cd4df7
feat(ui): link key info header to its user, creator, team, and organization (#37187)
* feat(ui): link key info header to its user, creator, team, and organization

The key info page showed the owning user and creator as plain text and never surfaced the team or organization at all, so walking from a key to its parent entities meant copying ids into other pages. The header now renders User and Created By as links to the user detail page, and gains a far-right column with Team and Organization links (alias when known, id otherwise). Client-side navigation logic shared by BadgeLink and IdentityCell moves into a reusable EntityLink so all entity links behave the same

* test(ui): mock next/navigation in VirtualKeysTable test

KeyInfoView now renders EntityLink, which calls useRouter, so the table test that opens the key detail needs the app router mocked
2026-08-17 10:17:28 -07:00
tin-berri
3c3ada9af0
feat(ui): add Lite mixed-provider auto-router preset (#37068)
* feat(ui): add Lite mixed-provider auto-router preset

* feat(ui): disable classifier context window in Lite preset
2026-08-17 10:07:01 -07:00
tin-berri
5ecc6af541
fix(UI): add default model pin to complexity router UI (#36615)
* Add default model pin to complexity router UI

A complexity router's default model was only ever derived from the tiers, so
operators had no way to point the fallback at a model that is not first in the
Simple or Medium tier. Adds a Default Model select that records an explicit pin.

The pin is stored in complexity_router_config.default_model, which the backend
already reads, and mirrored onto complexity_router_default_model on save. Both
paths resolve through one helper that mirrors init_complexity_router_deployment:
a pin wins, otherwise MEDIUM or SIMPLE. Recording the pin in the config keeps it
distinguishable from a derived value, so a pin that happens to match the tiers
survives a round trip instead of being read back as tier tracking.

The edit modal only requires one non-empty tier, so a router with models in
COMPLEX alone can reach save with nothing the backend would pick. That now
blocks with an inline message rather than saving a router that raises at init.

* fix(UI): probe the pinned default model in the auto router connection test

The connection test built its targets from the tiers and the embedding model
only, so a Default Model pin outside every tier was never reached and a green
result could hide an unreachable default. model_info_view had already hand
rolled the dedupe and append locally, so the rule moved into
buildAutoRouterTestTargets and both call sites now share it.

* fix(ui): mirror backend precedence when resolving a complexity router default

The edit modal only recognized a pin stored in complexity_router_config.default_model,
so a router whose default lived solely in litellm_params.complexity_router_default_model
lost it on the next save. That field cannot be trusted outright either: before this PR
every save wrote a tier-derived value into it, so treating any value as a pin would
freeze legacy routers away from their tiers. Hydration now takes the config marker as
authoritative and falls back to litellm_params only when it diverges from what the tiers
alone derive, which is only reachable through an external API or config write.

Test Connection had the mirror-image bug: it fell back to complexity_router_config.default_model,
a UI-only marker init_complexity_router_deployment never reads, so it could probe a model
the router would never call. It now follows router.py exactly: litellm_params, else pure
tier-derivation.

Also reword a tooltip that hardcoded the Default Model select's position on the page, and
document the dual write and the create-vs-edit validation asymmetry.
2026-08-17 10:04:23 -07:00
mateo-berri
81aefe4b3c Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_pr36151_ws_passthrough
# Conflicts:
#	litellm/proxy/pass_through_endpoints/pass_through_endpoints.py
#	tests/test_litellm/proxy/pass_through_endpoints/test_pass_through_endpoints.py
2026-08-16 14:21:57 -07:00
Shivi Jain
8a44c14928 Merge upstream/litellm_internal_staging and fix batch quota review comments
Resolves conflicts from the upstream merge and addresses the Veria-AI
review comment on this PR: batch rows could bypass a project's
per-model ITPM/OTPM quota when the batch's file-bound/routing model
had no quota configured. Charges each row's own model against its own
project quota instead of only the routing model's, and fixes rate
limit error messages to attribute the correct model via a new
descriptor_value field on RateLimitStatus/AtomicCounterMeta. Also
re-syncs the ruff-strict, type-discipline, and basedpyright budgets
against the correct (non-stale) merge base.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-16 15:47:46 +05:30
yuneng-jiang
992a8123ac
Merge pull request #37010 from BerriAI/litellm_shadcn_next_0814
fix(ui): de-duplicate the reset budget option and polish shadcn surfaces
2026-08-15 17:18:25 -07:00
Yuneng Jiang
81ec2540b3
Merge branch 'litellm_internal_staging' of github.com:BerriAI/litellm into litellm_/invite-button-shadcn-decouple-1463e5 2026-08-15 15:39:54 -07:00
Yuneng Jiang
ff3da21aab
refactor(ui): decouple bulk invite from the invite user button
The bulk invite button was rendered from inside CreateUserButton, so the two
actions were locked together and only the bulk one had been migrated, leaving
the users page with an antd primary button sitting next to a shadcn one.

Move BulkCreateUsersButton up to the users page toolbar so each action stands
on its own, and migrate CreateUserButton's buttons to the shared shadcn Button
so both triggers render identically. The teams prop only ever fed the bulk
button, so it goes away from CreateUserButton and its other call site.
2026-08-15 15:39:50 -07:00
tin-berri
540caa6574
feat(ui): direction picker and reverse-mode display for shadow evals (#36994)
* feat(ui): direction picker and reverse-mode display for shadow evals

* fix(ui): include configured model groups in the shadow eval baseline picker
2026-08-15 15:30:28 -07:00
Thijmen Stavenuiter
db67bafd59 fix: drop Final from loop locals to stay within the basedpyright budget 2026-08-15 21:26:55 +02:00
Marty Sullivan
2e33eab2b0 chore(ui): regenerate dashboard api types for the new bedrock batch params 2026-08-15 11:42:07 -07:00