Commit graph

4890 commits

Author SHA1 Message Date
ryan-crabbe-berri
4dd93ae8aa feat(ui): clear user settings from the Admin UI and edit TPM/RPM there
The user edit form now saves through PATCH /management/v1/users/{user_id}
instead of /user/update, so emptying a control actually clears the setting
rather than saving as a no-op. TPM and RPM limits are editable and shown on
the details panel, and /v2/user/info returns them so the form seeds correctly.
2026-08-27 18:14:46 -07:00
tin-berri
40ff01b987
feat(mcp): let a resolved OAuth token target a custom upstream header (#38456)
An MCP server behind an API gateway needs two credentials on one request: the
gateway's own token on a private header, and a separate bearer on Authorization
for the server behind it. Every arm that minted or held a token hardcoded
Authorization, and the conflict rule then dropped the operator's static
Authorization to make room, so the second credential never arrived.

ApiKeyConfig already modelled this as header_name plus value_prefix behind a
header() method. Extend that carrier to the four minted-token configs, have each
resolver arm ask its config which header to use instead of naming one, and drop
only the header the resolved credential is about to occupy.

Operators set it per server via upstream_token_header, plumbed through
config.yaml, the credentials blob, the management API and the admin form, on the
M2M, token-exchange, authorization-code and ID-JAG arms. It is non-secret so it
stays plaintext and round-trips on admin reads. Unset keeps today's behaviour.

Moving a credential off Authorization means it stops inheriting what Authorization
gets for free, so the slot now carries those protections itself. httpx drops
Authorization when a redirect crosses origin and keeps every other header, so a
custom slot is dropped by the client on the same condition, mirroring httpx's own
scheme/host/port rule with an agreement test that fails if the two ever diverge.
The v1 path also mirrors the v2 conflict rule, so an injected header cannot shadow
the credential the gateway resolved for that slot.

Which header a credential occupies, and what counts as being that header, was
answered independently in nine places by four hand-rolled comparisons. same_header,
has_header and without_header in litellm/types/mcp.py are now the one owner, shared
by both MCP stacks, and the client derives its slot once instead of three times.

The header name reaches egress verbatim, so the RFC 7230 grammar lives in one
place and is checked where servers are built: a bad value fails the config load
and the management API returns 400, rather than raising while a spec is built
and emptying the aggregate tool list for every other server. A blank means unset,
matching what the endpoint already accepts.
2026-08-27 14:32:01 -07:00
yuneng-jiang
3746ba58d7
fix(ui): let the paginated search select keep what the user types (#38475)
* fix(ui): let the paginated search select keep what the user types

The combobox handed Base UI a freshly built option object for the current
selection every time a page of results came back. Base UI answers a changed
value by rewriting the input with that option's label, so every search response
wiped the query mid-typing and the list never narrowed. Once a user had been
picked in the Usage page filter box, no other user could be reached.

The component now owns the input text. It holds the query while the list is
open, falls back to the selected option's label once the list closes, and
remembers the picked option so its label survives later pages that no longer
carry it, the way the multi-select sibling already does.

* refactor(ui): name the paginated select's search state instead of commenting it

* fix(ui): start a fresh query when typing lands on the selected label

Focusing the filter box without clicking it leaves the caret at the end of the
selected option's label, so the next keystroke extended that label into a query
no server could match. Only a click cleared the box first.

A keystroke that arrives while the box is showing a label is now read as the
start of a new query, wherever in the label it landed.
2026-08-27 13:32:20 -07:00
tin-berri
71449b9c55
fix(ui): open select popups below the trigger instead of over it (#38554)
The shared SelectContent wrapper defaulted alignItemWithTrigger to true,
which puts Base UI's positioner into item-aligned mode and places the
popup so the active item sits on top of the trigger. In that mode the
side and sideOffset the wrapper passes two lines above are ignored, and
the popup reports data-side="none".

The overlap only becomes visible once the items are tall enough to
matter, which is why the autorouter Template picker shows it clearly:
its options are three-line cards, so the popup covers both the select
box and its own label.

No call site in the dashboard asked for item-aligned mode. 21 of them
across 15 files already passed alignItemWithTrigger={false} by hand to
undo the default, and the remaining 127 inherited the bug. Flipping the
default makes side and sideOffset live, so collision handling works and
a select with no room below now flips above the trigger rather than
covering it. The 21 hand-written opt-outs are deleted as redundant.
2026-08-27 13:08:13 -07:00
Mateo Wang
452254963e
feat(health): opt-in model-group allowlist for background health checks and health-check routing (#38539)
* feat(health): opt-in model-group allowlist for background health checks and health-check routing

* fix(health): merge shared health states per writer scope instead of replacing

* refactor(health): drop restating comment and parameterize test scope annotations

* chore: remove stray generated prisma migration file

* fix(health): merge health states against the Redis snapshot, not the pod-local copy

* fix(health): fall back to the pod-local snapshot when the Redis read returns nothing
2026-08-27 12:25:56 -07:00
ryan-crabbe-berri
955b26ac08
Merge pull request #38541 from BerriAI/devin_ai_34831_nginx_1_31
build(ui): bump nginx to 1.31-alpine
2026-08-27 11:34:50 -07:00
tin-berri
0fba05800d
feat(ui): run the Anthropic Family preset's reasoning tier on Opus 5 at high thinking (#38490)
The preset put Fable 5 in REASONING, sitting above Opus in a Haiku to Sonnet to
Opus ladder even though Fable is the lighter model. Run Opus 5 there instead, at
high thinking, so the tier above COMPLEX is the same model thinking harder rather
than a different and lighter one.

This is the first bundled preset to carry tier_model_configs. The round trip was
already built and unit tested, but nothing between the bundled JSON and the
create payload asserted on it, so add that coverage here.
2026-08-27 11:31:14 -07:00
tin-berri
490face7de
fix(ui): order the auto-routers table newest first so a new router lands on page one (#38545)
/v2/model/info returns llm_router.model_list, which carries no defined order: the DB
read has no order_by and an edited deployment is popped and re-appended. The Auto
routers table rendered that order verbatim behind a ten-row first page, so on a proxy
with more than ten auto routers a router created moments ago was drawn wherever the
API happened to return it, in practice last, and read as never created

Adopt the ordering the rest of the dashboard already uses, with the two cases this
table has and its siblings do not. created_at is enterprise-gated and config.yaml
routers never carry one, so seeding created_at desc alone leaves every comparison
tied on a non-premium proxy and the fix a no-op. The column now declares
sortUndefined last, which table-core applies before the desc flip so undated rows
stay last in both directions, and the row emits undefined rather than null so that
branch is reachable at all. Name is the secondary key, giving the undated block a
defined order too

Page size is deliberately unchanged: it exposes the missing order rather than
causing it
2026-08-27 11:30:48 -07:00
devin-ai-integration[bot]
a7da7928fa
feat(ui): add cache hit/miss filter to Request Logs (#38432)
* feat(ui): add cache hit/miss filter to Request Logs

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

* fix: guard cache_hit_filter validation for direct handler calls

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

* chore(ui): drop redundant cache filter comment

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

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-27 17:57:07 +00:00
Mateo Wang
62341e96ae
Merge pull request #38410 from BerriAI/litellm_regenerate_lazy_openapi_snapshot
fix(proxy): regenerate lazy OpenAPI snapshot and guard it in CI
2026-08-27 10:55:38 -07:00
Alex Harden
a21eed6c77 build(ui): bump nginx to 1.31-alpine
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-27 17:09:17 +00:00
Mateo Wang
86ef1fb08b
Merge pull request #38391 from BerriAI/litellm_toggle_internal_health_check_logs
feat(ui): toggle internal health check visibility in request logs
2026-08-27 09:29:45 -07:00
tin-berri
cd63c7e5a7
feat(ui): put the auto-router savings hero on a spend rail and a four-tile row (#38470)
The savings card carried four numbers in two stacked halves: the headline
saving with its delta on the left over the two spend rows, and avg saved per
session on the right. Give the headline the whole left half, move the two
spend rows into a rail on the right, and drop avg saved per session into the
metric row below as its first tile, with the session count as an inline hint.

Each spend row stays a description list so assistive tech keeps the label to
value association, with the shadcn Separator between the two rows. Both hero
columns are minmax(0,1fr) so a large total wraps instead of overflowing the
card, which also fixes the clipping the old 1fr columns already had. Metric
grows one optional hint slot so the new tile reuses the same presenter as its
three siblings.
2026-08-27 00:22:38 -07:00
tin-berri
81dc8dba1c
fix(ui): carry a preset's per-tier litellm_params through the prefill (#38453)
* fix(ui): carry a preset's per-tier litellm_params through the prefill

buildPresetPrefill rebuilt the complexity router config field by field and
never emitted tier_model_params, so a bundled preset that declares per-model
litellm_params (reasoning_effort, for instance) lost them before the create
form ever saw them. Both halves of the round trip already existed:
hydrateTierModelParams reads either storage shape, and serializeTierModelConfigs
writes them back on submit.

Hydrating alone is not enough. Tier entries get rewritten to the caller's
registered model spelling, which can differ from the preset's literal string by
version-separator punctuation, while the params stay keyed on what the preset
spelled. serializeTierModelConfigs then drops any param whose key is not in the
tier, silently. The param keys go through the same resolver as the tier entries.

* test(ui): catch a preset spelling the same model two ways in one tier

buildPresetPrefill resolves every model reference through normalizeModelName,
so two spellings of the same model in one tier (e.g. "claude-sonnet-4-5" and
"claude-sonnet-4.5") collapse to one key. For tier_model_configs that means one
model's litellm_params silently overwrites the other's - flagged by Greptile
on #38453 (P2, confirmed real via a throwaway repro, not a regression: on the
merge base both param sets were already dropped).

Nothing else validates preset authoring, and these are trusted, checked-in
JSON, so the fix is a static test over the bundled data rather than runtime
code. Exports normalizeModelName so the test exercises the actual resolution
rule instead of a hand-rolled copy of it. Verified the test fails when a
preset is mutated to spell one model two ways, and passes clean on the real
bundled presets.
2026-08-26 23:48:04 -07:00
tin-berri
166694948f
fix(ui): show custom technical keywords on every router whose scorer runs (#38451)
The keywords feed the scorer's technical dimension, so they change tier decisions
on any router that scores. The control rendered only for classifier_type
'heuristic', while the scoring knobs right below it already gated on
heuristicScoringRole(value) !== 'never'. The two disagreed, so an operator could
edit boundaries and weights on a router whose keywords they could neither see nor
set.

That hid the control on an LLM classifier using the default heuristic fallback,
and on heuristic_first, which runs the scorer on every request to decide whether
to short-circuit. Both now read the same predicate as the panel below them.
2026-08-26 22:37:37 -07:00
devin-ai-integration[bot]
172e3aceaf
fix: bound row count on GET /spend/logs to stop unbounded LiteLLM_SpendLogs scans (#38420)
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-26 20:48:10 -07:00
devin-ai-integration[bot]
1fcdb3d92a
feat(ui): add Teams list CSV export with budgets, model grants, and rate limits (#38436)
* feat(ui): add Teams list CSV export with budgets, model grants, and rate limits

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

* fix(ui): neutralize formula-leading values in teams CSV export

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

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-26 20:44:33 -07:00
tin-berri
587f227b9d
feat(complexity_router): heuristic-first classifier chaining (#38428)
* feat(complexity_router): heuristic-first classifier chaining

Adds classifier_type 'heuristic_first', which scores locally on every request and
only calls the LLM classifier for traffic the scorer could not place at or below
heuristic_first_max_tier. A request short-circuits when the scorer landed at or
below the threshold and produced at least one signal; everything else escalates.

The signal requirement is load-bearing. A prompt where no dimension fires scores
exactly 0.0, which is under simple_medium, so the score-to-tier mapping calls it
SIMPLE by default rather than by evidence, and that is about half of general
traffic. Gating on the tier alone would route it to the cheapest model without
ever consulting the classifier.

Introduces uses_llm_classifier as the single owner of 'does this router call the
classifier model', replacing the classifier_type == 'llm' comparisons in the
config validator, the prompt prebuild, the health dependency graph, the
routing-test authorizer, and six dashboard sites.

* fix(complexity_router): reuse the heuristic verdict on classifier failure, load the threshold on edit

Three review findings, one push.

The heuristic-first fallback re-scored the prompt after a classifier failure,
which the README already documented as a reuse. The outcome computed before
escalation is now handed to the failure path, so the scorer runs once per request.

The edit modal never hydrated heuristic_first_max_tier, while save rebuilds every
managed key from form state, so opening a heuristic-first router and saving it
dropped a field the proxy requires. The dropdown's display fallback hid it. Both
are fixed, and the hydration is extracted into a pure function so a test can pin
the invariant: every managed key present in a stored config survives an untouched
open-and-save. That test also covers every field added later.

Classifier radio labels lost their em dashes, per the repo writing convention.
2026-08-27 02:11:37 +00:00
tin-berri
ff7ba4c6df
fix(ui): block the auto-router submit on a missing classifier model and an orphaned keyword rule (#38427)
Two gaps the create form and the edit modal share today.

The submit gate never asked for a classifier model. Choosing the LLM classifier
and no model leaves Test Routing and Add Auto Router enabled, so Test Routing
posts a config the backend rejects and only the later save says why.

The keyword-rule gate only looked for empty keyword rows. A rule's tier has been
a free string since #37413, and the backend matches it exactly, so a rule naming
a tier the router does not have cleared the gate and failed the save as a raw
400.

Both gates now live in build_complexity_router_config.ts, and each form's submit
handler reads the same blocked reason the button reads instead of re-deriving
its own list, so a disabled button and a refused submit cannot disagree.
2026-08-26 18:56:41 -07:00
yucheng-berri
ecc49764af
feat(guardrails): track Azure Prompt Shield usage and cost with spend isolation (#38387)
* Track Azure Prompt Shield guardrail usage and cost with spend isolation (LIT-5917)

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

* Resolve credential references and pydantic extras in in-place guardrail updates

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

* Suppress LIT001 on the dict-accepting update helper signature

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 17:42:17 -07:00
yuneng-jiang
e80ba92cfa
Merge pull request #38313 from BerriAI/litellm_/hide-unhealthy-virtual-key-models-922c37
feat(proxy): hide unhealthy models from model listings, opt-in
2026-08-26 17:05:41 -07:00
devin-ai-integration[bot]
8a9d5b15b4
feat(langfuse): support langfuse_environment as a per-key dynamic callback param (#38264)
* feat(langfuse): support langfuse_environment as a per-key dynamic callback param

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

* refactor(langfuse): type the langfuse_environment constructor param

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

* fix(langfuse): only pass environment when the SDK client supports it

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

* test(langfuse): drop the request-body metadata test for langfuse_environment

The proxy bans request-body callback params by default (derived from
_supported_callback_params in auth_utils), so the metadata channel this
test asserted is rejected with a 401 on the proxy. The supported channel
is admin-set key/team callback_vars, with LANGFUSE_TRACING_ENVIRONMENT
as the deployment-wide fallback.

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

* fix(langfuse): validate langfuse_environment, avoid redundant clients, honor it in langfuse_otel

Closes the review gaps on the langfuse_environment param:

- Validate values against Langfuse's environment pattern at save time
  (/key/generate, /key/update, /team callback all 400 on e.g. 'Production'
  instead of 200-then-silently-dropping every trace server-side) and at
  logger init; non-string values are str()-coerced instead of crashing
  the SDK's regex check per event.
- Treat empty/whitespace values and values equal to the deployment-wide
  LANGFUSE_TRACING_ENVIRONMENT as non-dynamic so an environment-only
  override that changes nothing no longer mints a duplicate SDK client
  against MAX_LANGFUSE_INITIALIZED_CLIENTS.
- langfuse_otel now reads the per-key/team langfuse_environment from
  standard_callback_dynamic_params instead of only the env var.
- Advertise the param on the discovery surfaces: callback_configs.json
  (langfuse + langfuse_otel), the dashboard callback registry, and the
  /team/{team_id}/callback docstring (schema.d.ts regenerated).

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

* style: ruff format langfuse files

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

* fix(lint): remove duplicate test import, LIT002 dict literal, and mock-echo otel test

- drop redundant in-function import of callback_config_error (F811)
- avoid the `or {}` mutable literal in _set_langfuse_specific_attributes (LIT002)
- rewrite the dynamic-env otel test to observe span.set_attribute output
  instead of patching litellm internals (TQ002/TQ008)

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

---------

Co-authored-by: milan <milan@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: yucheng-berri <yucheng@berri.ai>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 16:56:55 -07:00
mateo-berri
241daa4cb7 Merge branch 'litellm_internal_staging' of https://github.com/BerriAI/litellm into litellm_gemini_maps_grounding_cost
# Conflicts:
#	type-discipline-budget.json
2026-08-26 15:33:28 -07:00
mateo-berri
b9a790899a fix(gemini): bill Google Maps grounding as its own SKU
Gemini API Maps-grounded prompts were billed as web search and Vertex AI Maps-grounded prompts were not billed at all. Classify grounding metadata per candidate into web search vs Maps requests, carry a distinct google_maps_grounding_requests usage counter through non-streaming and streaming paths, and price it via the new google_maps_grounding_cost_per_query cost map key with per-query and per-prompt defaults keyed off web_search_billing_unit. Fixes #35906
2026-08-26 15:31:27 -07:00
ryan-crabbe-berri
52b7bea6f3
Merge pull request #37708 from BerriAI/litellm_team_member_budget_no_reset
fix(team): allow no-reset default budgets for team members
2026-08-26 15:14:30 -07:00
mateo-berri
cd9dcb55b4 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_regenerate_lazy_openapi_snapshot 2026-08-26 15:04:13 -07:00
tin-berri
f0a122d35f
refactor(ui): read the auto-router tier set through one row list (#38408)
The dashboard resolved the complexity-router tier set three different ways: a
private TIER_KEYS in build_complexity_router_config.ts, TIER_ORDER in
complexity_router_tiers.ts, and TIER_KEYS in ComplexityRouterConfig.tsx. The
edit modal went further and re-implemented the whole create payload builder,
kept in sync only by a comment reading "Mirrors buildComplexityRouterConfig".

tier_rows.ts now owns the tier set. Every consumer reads activeTierRows(value)
and a row carries its own id, so the plan-mode floor and per-model params point
at a row rather than at a position, and the leaves that already wanted entries
(buildAutoRouterTestTargets, getRequiredModels, model_info_view) take them.
buildUpdatedComplexityRouterConfig becomes preserve-unmanaged-keys around the
shared builder instead of a second copy of it.

Also drops the literal ", ]" that renders as visible text in two DialogFooter
blocks on the auto-router routing-test and connection-test dialogs, left over
from a JSX array-to-fragment conversion.

No behaviour change: all 566 tests over the touched modules pass with fixture
shape changes only, no assertion edited.
2026-08-26 14:53:06 -07:00
Mateo Wang
16e9efccaf
Merge pull request #38404 from BerriAI/litellm_fix_prompt_data_double_nest
fix(prompts): reject keyed prompt_data with prompt_id and populate prompt version
2026-08-26 14:47:59 -07:00
mateo-berri
afe5a240e5 fix(proxy): regenerate lazy OpenAPI snapshot and guard it in CI
The committed snapshot behind /openapi.json for unloaded lazy features had drifted on 30 of 31 fragments and never had one for a2a_registration or gemini_agents, so those routes showed as placeholder GET stubs or old docstrings until traffic loaded them. Regenerate the snapshot and schema.d.ts, make the check-ui-api-types job and make check regenerate the snapshot and fail on drift, and make the generator refuse to write a snapshot when any feature fails to import so a broken import cannot silently drop fragments.
2026-08-26 14:32:04 -07:00
mateo-berri
f334108f33 docs(prompts): sync lazy openapi snapshot and dashboard schema with the fixed create_prompt example 2026-08-26 14:11:21 -07:00
ryan-crabbe-berri
f2f389cc6f fix(ui): keep an untouched member budget duration distinct from never resets
The member duration dropdown reused its placeholder as "Never resets", so a
team with no member budget yet showed "Never resets" while sending nothing and
inheriting the team's own reset period. Use the dropdown's never-resets
sentinel for an explicit null and label the untouched state as inheriting.
2026-08-26 13:48:09 -07:00
milan
99d4741586 fix(team): allow no-reset default budgets for team members
The Default Budget Duration field in Team Member Settings only offered daily, weekly and monthly, so a team member budget could never be set to never reset. It now uses the shared BudgetDurationDropdown, and /team/update writes an explicitly null duration through to the member budget row along with its reset time.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-26 13:48:08 -07:00
yuneng-jiang
1a431687f0
Merge pull request #38306 from BerriAI/litellm_/page-header-spec-rollout-46f0dd
refactor(ui): move every page header onto the shared PageHeader
2026-08-26 13:42:56 -07:00
mateo-berri
19a1d5c4c6 fix(ui): tolerate malformed persisted hide-health-checks value 2026-08-26 12:52:10 -07:00
ryan-crabbe-berri
32dac12f9b
Merge pull request #38282 from BerriAI/litellm_ui_zindex_scale
refactor(ui): replace hand-picked z-index values with one named scale and lint it
2026-08-26 12:52:01 -07:00
mateo-berri
41192ef085 feat(ui): toggle internal health check visibility in request logs 2026-08-26 12:16:24 -07:00
Mateo Wang
c7b9060fb1
Merge pull request #38291 from BerriAI/devin_ai_lit6160_health_check_image_edit_mode
fix(health): support `mode: image_edit` in health checks
2026-08-26 12:04:53 -07:00
mateo-berri
8ff832901b fix(health): accept image_edit mode on /health/test_connection 2026-08-26 11:18:50 -07:00
Yuneng Jiang
57a616d495
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/remove-stale-new-badges-f08c0f 2026-08-26 11:11:38 -07:00
Yuneng Jiang
8915134864
chore(ui): remove stale "New" badges from the dashboard
The badges flagged UI that shipped a while ago, so they no longer tell
anyone anything. Dropped all four render sites: the Settings and Admin
Settings items in the left nav, the UI Settings tab in the admin panel,
and the Submitted MCPs tab.

The NewBadge component stays so the next genuinely new surface can use
it again. BetaBadge and the "hide new badges" account toggle are
untouched, since that toggle still gates BetaBadge.
2026-08-26 11:11:28 -07:00
yuneng-jiang
f066b01b0a
Merge pull request #38366 from BerriAI/litellm_fix_add_model_public_name_focus
fix(ui): keep focus in the add model public name input while typing
2026-08-26 11:05:09 -07:00
yuneng
e1dcb6c76b style(ui): format the add model mapping column defs
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-26 17:25:04 +00:00
yuneng
3f25e5b9f6 fix(ui): keep focus in the add model public name input while typing
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-26 17:17:32 +00:00
Devin AI
055b6f6f69 chore: merge litellm_internal_staging into rolling techdebt branch
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-26 07:44:58 +00:00
Yuneng Jiang
2d1e3a1c80
feat(proxy): hide unhealthy models from model listings, opt-in
Adds `general_settings.model_list_healthy_only`, which makes `/models`,
`/v1/models/{id}` and `/model/info` hide models whose backing deployments are
all marked unhealthy by background health checks, for every caller, without
each client having to pass `healthy_only=true`. `/model/info` also gains the
per-request `healthy_only` parameter that `/v1/models` already had.

Everything here is opt-in. With the setting absent, the endpoints take the same
code path they do today and no health lookup runs at all.

The listing filter reads the deployment health cache, which until now was only
populated when `enable_health_check_routing` was on, so `healthy_only=true`
silently did nothing in a plain `background_health_checks` setup. The setting
now also keeps that cache filled. That is a pure write: every routing-time
reader is itself gated on `enable_health_check_routing`, and the cooldown and
failure bookkeeping stays behind that flag, so routing is untouched.

Filtering stays presentation-only and fails open. A hidden model is still
callable, and missing, stale or empty health state hides nothing.
2026-08-26 00:13:52 -07:00
Yuneng Jiang
41e6e58915
test(ui): drop duplicated subtitle assertions in the header tests 2026-08-25 23:30:17 -07:00
Yuneng Jiang
7c777a0b4b
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/page-header-spec-rollout-46f0dd 2026-08-25 23:19:45 -07:00
Yuneng Jiang
ca77b32454
refactor(ui): move every page header onto PageHeader and drop the legacy one
Virtual Keys, Budgets, Projects, Access Groups, Guardrails Monitor and Cost
Optimization all move onto the shared PageHeader, matching the Teams page.
That empties LegacyPageHeader, so it and its test are deleted.

Each page now uses its own sidebar icon, so the nav and the page agree:
Virtual Keys keeps KeyRound and Budgets keeps Wallet, Projects picks up
Folder and Access Groups picks up Boxes, and Guardrails Monitor swaps the
indigo Shield for the sidebar's HeartPulse. Cost Optimization keeps
PiggyBank but drops its hardcoded size and stroke, which PageHeader owns.

Control rows follow the spec instead of each page inventing one. Virtual
Keys had its create button rendered as a sibling below the header, Budgets
hand-rolled a row with a bottom border that closed the header off, and
Projects and Access Groups sat their button next to the title. All four now
pass primaryAction. Guardrails Monitor's date picker moves out of the parent
and joins Export Data in utilities. Cost Optimization's tabs move into the
tabs slot with the standard 22px spacing.

Page insets go to p-8 with a 24px gap to content, replacing p-6 px-12,
p-6, mx-4 and py-2.

Every page test now asserts its heading, subtext and sidebar icon. Swapping
any of the six icons fails its suite.
2026-08-25 23:19:22 -07:00
Yuneng Jiang
d2aea2d4e7
refactor(ui): re-pull label, textarea, separator and skeleton from the registry
These four primitives still wrapped their body in React.forwardRef, which
the dashboard has not needed since it moved to React 19: a function
component receives ref as an ordinary prop and the existing {...props}
spread already hands it to the DOM node.

Re-pulling each from base-vega drops the wrapper and its displayName.
These four were picked because the ref plumbing is their only divergence
from current upstream, so the class strings, data-slot values and exports
are untouched and nothing renders differently. The other seven primitives
that still carry forwardRef have also drifted on their class strings, so
re-pulling them would ship a visual change alongside the cleanup and they
are left alone here.

Textarea is the one with real ref call sites, roughly seventeen of them
through react-hook-form's field.ref, and ref-forwarding.test.tsx did not
cover it. Add that case next to the Label, Separator and Skeleton ones
already there.
2026-08-25 22:56:59 -07:00
Yuneng Jiang
a5b6f31585
refactor(ui): install the shadcn alert primitive
components/shared/Alert.tsx was base-vega's own alert.tsx copied in by
hand, carrying the same four exports and the same class strings, so
npx shadcn add could never reach it and it would drift from every
upstream fix silently. It also still wrapped each part in forwardRef,
which React 19 no longer needs.

Install the primitive into components/ui/ where the CLI can update it,
and reduce the shared file to a wrapper that adds the four status
variants (info, success, warning, error) the dashboard actually uses on
top of upstream's default and destructive.

Rendered output is unchanged: every variant produces byte-identical
classes, role and data-variant, so all 45 call sites look the same.
2026-08-25 21:24:07 -07:00