Commit graph

4221 commits

Author SHA1 Message Date
shivam
60d250f786 fix(ui): import shadcn Badge in organization_view
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-30 00:51:20 +00:00
shivam
5ad0aeeca2 merge(litellm_internal_staging): resolve otel/team/org conflicts
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-30 00:50:55 +00:00
ryan-crabbe-berri
0a6b372126
feat(ui): link organization teams to their team detail pages (#35120)
Some checks failed
CodSpeed Benchmarks / benchmarks (push) Waiting to run
UI Unit Tests / ui-unit-tests (push) Waiting to run
GitHub Actions Security Analysis / zizmor (push) Waiting to run
LiteLLM Rust / rustfmt, clippy, test (push) Has been cancelled
* feat(ui): link organization teams to their team detail pages

On the organization info page the teams shown for an org were plain
badges, so walking to a team meant copying its id and finding it by
hand on the teams page

Team badges now link to /teams?team=<team_id>, which opens that team's
detail page directly since #35112. Adds a shared BadgeLink (a badge
rendered as a real anchor with modifier-aware client-side navigation,
so cmd-click opens a new tab) and a teamDetailHref builder for reuse by
future entity links

* fix(ui): format BadgeLink, split its modifier-click chain, and size it up

prettier wanted the Badge props wrapped, and local/no-long-condition-chain
flagged the four-way modifier-click guard; the guard is now two named
conditions. Linked badges also render slightly larger (text-sm, roomier
padding) than plain badges so clickable entries stand out

* feat(ui): size org model badges to match the linked team badges

BadgeLink's href is now optional; without one it renders the same
enlarged plain badge (no pointer, no hover), so the org page's model
badges share the component and the size while staying non-clickable
2026-07-29 17:20:59 -07:00
Yucheng Zhu
440277c97b fix(ui): keep non-OTEL logging callbacks in the Add Callback list
The Add Logging Callback dropdown was filtering out langfuse (v2 SDK),
otel (Open Telemetry), and the other non-OTEL callbacks along with the
OTEL trace destinations, so those integrations disappeared from the UI.
Restrict the destination filtering to the OTEL trace-destination
backends only (arize, langfuse_otel, weave_otel, generic), so non-OTEL
logging callbacks keep their existing global-callback path.

Also relabel the langfuse_otel destination to "Langfuse OTEL" so it is
distinct from the Langfuse SDK entry, collapse the duplicate
destination-id set into a single LOGGING_BACKEND_IDS, and format the
logging UI files.
2026-07-29 16:19:46 -07:00
ryan-crabbe-berri
ba7d8ae17f
feat(ui): deep link organization detail page via ?org= query param (#35117)
* feat(ui): deep link organization detail page via ?org= query param

The organizations page kept the selected organization in React state, so
an org detail page had no URL: it could not be shared, bookmarked, or
opened from another page, and the browser back button dropped you out of
the page instead of closing the detail view

Adds useOrgDetailRouting reading ?org= (same pattern as the api-keys,
models, logs, and teams deep links) and derives the open organization in
OrganizationsPanel from the URL

* fix(ui): reset org edit mode on plain row selection and type test mocks

Greptile P1: with the selected org now URL-derived, browser Back leaves
the detail view without running onClose, so a stale editOrg=true made
the next plain row click open on the Settings tab. Reset the flag on
row selection, matching the teams page

Greptile P2: type the panel test's captured table and detail-view props
from the real components instead of any
2026-07-29 11:56:37 -07:00
ryan-crabbe-berri
bb769702b1
feat(ui): deep link team detail page via ?team= query param (#35112)
The teams page kept the selected team in React state, so a team detail
page had no URL: it could not be shared, bookmarked, or opened from
another page, and the browser back button dropped you out of the page
instead of closing the detail view

Adds useTeamDetailRouting reading ?team= (same pattern as the api-keys,
models, and logs deep links) and derives the open team in Teams.tsx from
the URL. TeamInfo now also derives team-admin rights from the fetched
team data, so team admins arriving via a deep link are not stuck with a
read-only view
2026-07-29 11:35:06 -07:00
yuneng-jiang
74244ddd45
Merge pull request #35041 from BerriAI/litellm_/ui-perf-regression-d7888c
fix(ui): point the navbar and sidebar logos at the dashboard home route
2026-07-29 10:59:53 -07:00
Mateo Wang
2348ccc977
Merge pull request #35107 from BerriAI/litellm_usage_public_model_names
fix(ui): show public model names in usage breakdowns
2026-07-29 10:42:13 -07:00
ryan-crabbe-berri
fdea50daa2
feat(ui): shareable log links via log_id query param on the logs page (#34879)
* feat(ui): shareable log links via log_id query param on the logs page

Clicking a log row now writes ?log_id=<request_id> to the URL, closing the
drawer removes it, and loading the logs page with ?log_id= opens the drawer
for that log. When the log is not in the loaded page, it is fetched by
request_id (the backend already drops the date window for id lookups), so
links keep working for logs of any age. Drawer open state derives from the
URL, mirroring the models page ?model= pattern.

* fix(ui): close the log drawer on browser back after opening via session id

Session opens now write ?session_id= to the URL instead of holding local
state, so back removes both params and the drawer closes (Greptile P1).
Session views become shareable links as a side effect. In-drawer log
switching now replaces the history entry instead of pushing, so back
always closes the drawer in one step rather than replaying every viewed
log.

* fix(proxy): scope /spend/logs/session/ui to the requesting user's visible logs

Non-admin callers now only receive session rows they could already see on
/spend/logs/ui: their own logs plus logs of teams where they hold the
spend-logs permission. Previously any authenticated user could read any
session's log metadata by id, which shareable ?session_id= links made
trivial to trigger. Admin views are unchanged. Also, clicking a log row
now clears a lingering ?session_id= from the URL so the drawer shows the
clicked log instead of a stale session (Greptile P1).
2026-07-29 17:01:09 +00:00
ryan-crabbe-berri
9b7a6b9b90
feat(ui): split failed requests into their own series on the cache dashboard (#34862)
* feat(ui): chart failed requests as their own series on the cache dashboard

Spend logs for failed requests are stored with an empty call_type, so the
Cache Hits vs API Requests chart lumped them into an Unknown bar that read
as normal LLM API traffic. The activity query now also returns a per-group
failed_rows count (status = 'failure') and the dashboard charts it as a
third stacked series, so failures are visibly separate from successful
requests and cache hits. The chart data transform moves into a pure
summarizeCacheActivity helper with unit tests; header stats keep their
existing semantics (cache hit ratio still counts failures in the
denominator).

* refactor(ui): move cache dashboard aggregation server-side with a typed response

The /global/activity/cache_hits endpoint previously returned raw per
(key, call_type, model) spend-log aggregates typed as LiteLLM_SpendLogs
(wrong), and the dashboard reduced them in the browser: grouping by
call_type, relabeling empty call_type as Unknown, and computing the stat
card totals. All of that now happens server-side. The SQL groups per
call_type and splits cache hits vs successful vs failed requests, a new
cache_activity module validates rows into Pydantic models and computes
totals plus the key-alias/model filter options, and the endpoint declares
a real response_model so schema.d.ts types it correctly. The dashboard
consumes it through a typed $api react-query hook (filters ride the
query key and are applied in SQL instead of the browser), the hand-rolled
summarizeCacheActivity transform and the adminGlobalCacheActivity fetch
helper are deleted, and the refresh button now actually refetches.

The endpoint is UI-internal (hidden from the public swagger), so the
response reshape is not a public API break.
2026-07-29 09:48:17 -07:00
ryan-crabbe-berri
40878a1ed5
fix(proxy): allow /key/update to identify the key by key_alias (#34851)
* fix(proxy): allow /key/update to identify the key by key_alias

* fix(ui): drop machine-dependent union-order churn from generated schema.d.ts
2026-07-29 09:48:08 -07:00
ryan-crabbe-berri
fe1670fc06
fix(ui): size object permissions card grid by container width (#35019)
The card variant used viewport breakpoints (md:grid-cols-2 lg:grid-cols-3)
but every card usage sits in a one-third-width grid cell, so on desktop the
narrow card still rendered three internal columns of roughly 100px each and
the text spilled out of its boxes. Switch to Tailwind container queries so
the internal column count follows the card's own width
2026-07-29 09:47:54 -07:00
mateo-berri
802ed1c74f fix(ui): show public model names in usage breakdowns 2026-07-29 09:46:49 -07:00
Yucheng Zhu
738a37613e Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_otel_v2_admin_owned_destinations
# Conflicts:
#	tests/test_litellm/proxy/test_litellm_pre_call_utils.py
2026-07-29 00:55:59 -07:00
Yucheng Zhu
4ff358fcb8 refactor(otel/v2): make destination access the sole routing determinant
A logging destination's credential_info.access (global / teams / orgs) now fully
decides which requests it receives; a destination fires for a request exactly when
its access grants the request's team or org. This removes the second, redundant way
to express the same team-to-destination mapping that the admin-only model left
behind: the auto_enable flag and the per-team/org logging_exporters assignment
column both existed for tenant self-service opt-in, and once assignment became
proxy-admin-only they only duplicated what access already says.

Removed: the auto_enable field on CredentialInfo; the team and organization
logging_exporters columns and their assignment gate (validate_logging_exporter_field
/ validate_logging_exporter_assignment); the request-time naming union in
litellm_pre_call_utils; and the dashboard's per-team/org destination picker and the
"Enable for entire scope" toggle. The access-shape validator stays, the credential's
access fields stay, and /team/info and /organization/info still disclose
resolved_logging_exporters computed from access alone.

This also removes the /v2/organization write that two review bots flagged (there is
no longer a logging_exporters field on that endpoint) and the "(via scope)" UI
ambiguity that came from carrying two representations of the same mapping.

Verified live on a 2-org / 4-team matrix against Langfuse, Arize, Weave, a generic
OTLP collector, and a self-hosted Phoenix: per-team and per-org isolation, empty
access as deny-all, injection defense, admin-only credential management, and
complete trace trees read back from each destination's own API.
2026-07-28 20:52:30 -07:00
Yucheng Zhu
36faa5de5a refactor(otel/v2): drop per-key destination assignment; keys inherit from team and org
Destinations now bind at tenancy granularity only. The per-key logging_exporters
surface was half-shipped (edit-form picker but no create flow) and unrequested,
so it goes: the column leaves the key tables and the migration, /key/generate,
/key/update, and /key/regenerate stop accepting the field, the resolver's union
reads team and org columns only, and the key pages drop the picker and exporter
badges. A key's traces route by its team and org, which the live check confirms:
a brand-new team key exports to the team's destinations with no assignment.

Subset targeting below a whole scope remains available at team granularity
(a multi-team scope with enable-for-entire-scope off, named on specific teams).
Re-adding key granularity later is a purely additive column and field.
2026-07-28 14:41:13 -07:00
Yucheng Zhu
69ba1840d5 chore(ui): drop the via-scope badge suffix and clarify the auto-enable toggle
Viewers only need to know which destinations receive their traces; whether the
binding came from an assignment or the destination's scope is admin plumbing, so
the (via scope) suffix goes. The add-destination toggle is relabeled Enable for
entire scope with a tooltip spelling out on (whole scope exports automatically)
versus off (only explicitly assigned keys, teams, or orgs export)
2026-07-28 13:50:08 -07:00
Yucheng Zhu
670396ee95 feat(otel/v2): disclose resolved trace destinations on team and org info
The team and org pages showed different Logging Exporters lists per role: the
(via scope) badges were derived client-side from GET /credentials, which is
proxy-admin only, so non-admin viewers saw only the identity's own assignments.

Add resolved_logging_exporters to the /team/info and /organization/info
responses: the destination names that will receive the identity's traces,
computed server-side with the same selection the request-time resolver uses
(access grants the identity AND auto_enable or named). Names only; endpoints,
headers, and the access map stay proxy-admin information. The UI renders the
badges from this field, deleting the client-side credentials derivation, so
every role sees the identical list.
2026-07-28 13:39:08 -07:00
yuneng-jiang
f4a68a75ff
feat(ui): mark Cost Optimization as beta in the left nav (#34984) 2026-07-28 12:04:45 -07:00
Yucheng Zhu
9043a39327 chore(ui): regenerate schema.d.ts for the get_credentials docstring change 2026-07-28 11:54:55 -07:00
Yucheng Zhu
53d745a4b6 chore(otel/v2): sweep remaining tenant-surface leftovers after admin-only refactor
Full-diff audit against the admin-only design surfaced leftovers in three layers.

Backend: drop dead code the refactor stranded (is_admin_gated_credential_info,
is_destination_visible, the CredentialInfo decider-era fields, the unused
LLMCallEvent.dynamic_params carrier, the _is_user_org_admin_for_org_id extraction)
and correct every comment/docstring still describing the removed team-admin
self-service or scoped-read designs.

UI: gate the Logging Exporters form rows behind a shared proxy-admin-only
LoggingExportersFormItem so non-admin forms no longer render an orphaned label;
skip the GET /credentials fetch for roles it would 403 (team/org/key views); make
the callbacks table read-only for the admin viewer (no Add/Edit/Delete actions
that would 401); reword access tooltips to routing-scope semantics; regenerate
schema.d.ts from the corrected endpoint docstrings.

Tests: flip the credential-migration non-admin expectation to the route-gate 401,
port the visibility tests to access_grants, drop tests of the removed helpers,
and update copy assertions and stale rationale to the admin-only contract.
2026-07-28 10:35:44 -07:00
tin-berri
d91fd084f7
Fix cache leakage card layout to keep date picker on right (#34885)
* Fix cache leakage card layout to keep date picker on right and prevent content overlap

Removes flex-wrap and mt-3 to ensure date picker stays pinned to the right side of the card header regardless of zoom level, preventing it from covering card content below

* Remove overflow-hidden from Card to allow dropdowns and overlays to display fully

Fixes date picker dropdown being clipped when opened in cards like the Cache Leakage Card. By removing overflow-hidden from the Card container, popovers, dropdowns, and other overflow content can now display properly without being clipped by the card boundaries.

* Make cache leakage card descriptions consistent with line clamping

Adds line-clamp-2 to ensure both 'by model' and 'by virtual key' cards maintain consistent height. Removes conditional anthropic-specific text that caused height variations between dimensions.
2026-07-28 10:12:00 -07:00
Yucheng Zhu
04e4effba3 refactor(otel/v2): scope admin-owned trace destinations to proxy admin
Admin-owned OTEL v2 logging destinations and their access scoping are now managed
only by the proxy admin. Trace routing to identity-scoped destinations is unchanged
because it runs server-side in the resolver; this removes only the tenant-facing
read/write surface the earlier revision exposed.

GET/POST/PATCH/DELETE /credentials are proxy-admin only again (a proxy-admin-viewer
may read); the two credential routes leave self_managed_routes, and the non-admin
scoped list, the team-admin PATCH self-service grant, and the access_decision decider
are deleted. Assigning logging_exporters on a key, team, or org is proxy-admin only,
dropping the team-admin and org-admin widening. In the UI the logging destinations
table and the exporter picker render only for a proxy admin, so non-admins no longer
call GET /credentials.
2026-07-27 19:41:03 -07:00
Yucheng Zhu
1042b56d2f merge: resolve conflict with litellm_internal_staging (ruff budget ceilings) 2026-07-27 16:51:24 -07:00
tin-berri
9bb75d67af
Merge pull request #34675 from BerriAI/litellm_tool_spend_rollup
fix(proxy): roll up tool spend daily instead of scanning SpendLogs
2026-07-27 15:31:19 -07:00
ryan-crabbe-berri
0171170fc7
fix(ui): validate default team values in Default User Settings (#34815)
* fix(ui): validate default team values in Default User Settings

The Default User Settings form accepted any free-text team id, and the
proxy persisted it without checking the team exists. New users were then
silently never added to the default team because the consume-time 404
from team_member_add was swallowed at debug level.

Backend: PATCH /update/internal_user_settings now rejects unknown and
duplicate team ids with a 400 naming them, before any persistence or
team budget side effects. Team-add failures in _add_user_to_team now log
at ERROR with user and team ids.

UI: DefaultUserSettings rewritten as a shadcn + react-hook-form + zod
form following the org-settings pattern. The team id free-text input is
replaced with a searchable server-backed team picker, so only existing
teams can be selected; zod blocks empty and duplicate rows. The shared
deriveErrorMessage helper now unwraps the HTTPException detail.error
shape so backend validation errors surface readably in toasts.

* fix(ui): restore read-only view with Edit Settings toggle on default user settings

Parity with the pre-migration form: the tab renders a read-only summary
of the saved defaults, Edit Settings opens the RHF form, Cancel discards
pending edits and returns to the summary, and a successful save returns
to the summary showing the new values. Model sentinel labels in the
summary are derived from ModelSelect's now-exported special values
instead of duplicating the strings.

* refactor(ui): rename MODEL_SELECT_SPECIAL_VALUES_ARRAY to MODEL_SENTINEL_OPTIONS

* fix(ui): move Edit Settings into the card header action slot
2026-07-27 15:05:51 -07:00
Yucheng Zhu
e1da5a7e6c Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_otel_v2_admin_owned_destinations
# Conflicts:
#	tests/test_litellm/integrations/otel/test_otel_v2_logger.py
2026-07-27 12:24:12 -07:00
yuneng-jiang
2b7e01bb7e
Merge pull request #34691 from BerriAI/litellm_/management-endpoint-standards-b1cd57
refactor(management): move the logs end-user filter onto /management/v1
2026-07-27 11:30:51 -07:00
Yucheng Zhu
65dd6b8376 fix(credentials): empty access is deny-all; proxy-wide requires access.global
An empty access grant on a logging destination now means no one can use it,
for both visibility and request-time routing, regardless of auto_enable.
Previously an auto_enable destination with empty access was treated as
proxy-wide, an implicit allow-all that let a team-admin widen a scoped
destination by emptying its grants and let a client-invisible destination
fan out to every tenant. Proxy-wide export now requires an explicit
access.global=true (already proxy-admin-only).

Removes the auto_enable empty-access fallback from is_destination_visible
and the request resolver, deletes the now-unused _has_explicit_access_grants
helper, and drops the last-grant-removal guard in decide_credential_patch
that only existed to contain the old widening behavior. Emptying the last
grant is now allowed and simply disables the destination.

auto_enable is unchanged in meaning (fires without being named, scoped by
access) and the logging_exporters assignment path is untouched. UI Mode
cell renders Disabled for an auto_enable destination with no access grants.
2026-07-27 10:52:37 -07:00
Yuneng Jiang
c9d067fccc
chore(deps): bump gitpython to 3.1.55 and brace-expansion to 5.0.8
gitpython arrives transitively through mlflow-skinny; re-resolved with uv so the
lock moves that one package only. brace-expansion is a dev-only transitive dep
already pinned in the dashboard 'overrides' block, so the pin is bumped
alongside the lockfile to keep the change durable across reinstalls.

5.0.8 narrows its engines range from '18 || 20 || >=22' to '20 || >=22'; the
dashboard already requires node >=20.9.0 and every CI job pins node 20, so
nothing loses support.
2026-07-27 10:06:50 -07:00
yuneng-jiang
2f2e1e7519
Merge pull request #34689 from BerriAI/litellm_/model-table-dropdown-truncate-2b2a3f
fix(ui): truncate long team names in the models table team dropdown
2026-07-27 09:54:34 -07:00
yuneng-jiang
19348db0a6
Merge pull request #34679 from BerriAI/litellm_/modal-size-restoration-c06977
fix(ui): restore the Add MCP Server dialog size and header spacing
2026-07-27 09:45:52 -07:00
yuneng-jiang
9354849cc8
Merge pull request #34684 from BerriAI/litellm_/model-table-divider-center-b75b6d
fix(ui): center vertical toolbar dividers
2026-07-27 09:45:29 -07:00
Tin Chi Lo
1240c1a76d fix(proxy): close the adversarial-review findings on the tool spend rollup
Three fixes from an adversarial review of this branch, each at the owning
seam rather than the report site.

The flush retried DB_CONNECTION_ERROR_TYPES, which includes ReadTimeout.
A ReadTimeout is the committed-but-unacked case: the review reproduced the
engine abandoning the transaction open on the pooled connection, the retry
stacking its statements into it, and one commit applying both increment
sets while the flush reports success. The retry now covers only
ConnectError, the one failure that proves the statements never reached the
database; post-send failures drop the batch with an error log. The
docstring no longer claims an idempotency the pattern does not have. The
same hazard exists in the untouched daily spend writer and is left for its
own change.

get_tool_calls_from_response read choices[0] only, so a tool invoked in a
later choice of an n>1 response earned spend but never reached the rollup,
the index, or the registry. Choice scope is now an explicit parameter:
accounting passes include_all_choices=True because every choice costs
money; guardrails keep the primary-choice default because they rebuild the
primary assistant message. First multi-choice fixtures in the suite pin
both scopes.

maxBarSize=64 had been added to the shared BarChart unconditionally,
resizing every existing consumer. It is now a prop; only the tool spend
charts opt in. The legend flex-wrap changes stay global because clipping
overflow was a defect, not a preference.
2026-07-26 01:55:47 -07:00
Yuneng Jiang
78e76fff4d
Merge branch 'litellm_internal_staging' into litellm_/management-endpoint-standards-b1cd57 2026-07-25 23:57:32 -07:00
Yuneng Jiang
cb78491482
refactor(management): move the logs end-user filter onto /management/v1
`/customer/aliases` shipped two days ago and has not been in a release, so its
wire contract is still free to change. This lands it on the control-plane
contract before that stops being true, since after a release the path, the param
names and the envelope would all need a permanent legacy adapter

The endpoint becomes `GET /management/v1/spend_logs/end_users`. It is a facet,
the distinct values one column takes over a filtered query on a resource, not an
entity collection; naming it after `customers` implied it listed the end-user
table when it actually reads spend logs, which is a different row set. Serving it
under the parent resource means its filters are the parent's filters, so the
dropdown offers exactly the values the logs table can show without two endpoints
having to keep agreeing on that

Contract changes: `size` becomes `page_size`, `search` becomes `q`, the window
moves from flat `start_date` / `end_date` to `filter[startTime][gte]` / `[lte]`,
and the body becomes `{data, meta, links}`. Unknown query params are now a 400
rather than being silently dropped, because an ignored filter over-returns data.
Errors are RFC 9457 problem documents on this prefix only; every other route
keeps the shape its callers already parse

`links` is what makes the rest deferrable. The dashboard hook follows the
server's `links.next` instead of computing `page + 1`, so moving this to cursor
pagination later changes the links and nothing the client does. That matters
because the inner scan is a sliding window, so offset paging can currently skip
or repeat an end user across pages; the fix is a follow-up, and the hypermedia
means it will not be a breaking one

Cursor mode, `sort`, `include`, ETag / `If-None-Match` and the generic `ListSpec`
framework are all deliberately out of scope here. They are additive or internal,
so none of them needs to beat the release
2026-07-25 23:57:25 -07:00
tin
708a3a19df fix(ui): use a single muted blue ramp for the tool charts
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-26 06:53:26 +00:00
Yuneng Jiang
7b655086e5
Merge branch 'litellm_internal_staging' into litellm_/model-table-dropdown-truncate-2b2a3f 2026-07-25 22:56:41 -07:00
Yuneng Jiang
55ff0e10eb
fix(ui): truncate long team names in the models table team dropdown
The Team dropdown popup is pinned to the trigger width via
w-(--anchor-width) and clips its overflow, while Base UI's ItemText
wrapper is flex-1 shrink-0 with min-width: auto, so it sizes itself to
the full nowrap label and simply overflows the popup. Teams without a
team_alias render their 36-char id, so those options were sliced
mid-character with no ellipsis.

Clears min-width: auto off the text wrapper and truncates the label at
the call site. The underlying gap is in the shared Select primitive,
which any long-labelled select in the dashboard will hit; that is left
for a separate change.
2026-07-25 22:56:37 -07:00
tin
5d77c39bbb fix(ui): color spend-by-tool charts with an ordered ramp
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-26 05:23:57 +00:00
tin
9dbf7c363b fix(ui): keep the spend-by-tool legend from overlapping the charts
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-26 04:58:37 +00:00
Yuneng Jiang
1912ea200c
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/mcp-tabs-styling-dd340c 2026-07-25 21:55:17 -07:00
Yuneng Jiang
984051f74d
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/model-table-divider-center-b75b6d 2026-07-25 21:53:39 -07:00
Tin Chi Lo
c8b0530c30 fix(proxy): roll up tool spend daily instead of scanning SpendLogs
GET /v1/tool/spend served the Cost Optimization card with two raw queries
over LiteLLM_SpendLogToolIndex x LiteLLM_SpendLogs on every dashboard load;
the totals query's driving scan was all of SpendLogs in the window. Both
per-request tables reach 1M+ rows at customer scale, so the card cost
O(traffic) per view and had to be capped at 30 days.

The index writer also mined proxy_server_request.tools, i.e. tools DECLARED
in the request body, attributing each request's full spend to tools that
never ran; and all non-MCP mining ran against payload fields that are '{}'
unless store_prompts_in_spend_logs is enabled, so non-MCP coverage silently
depended on a privacy setting.

Now the spend writer builds a ToolUsageTransaction at request time from
invoked tools only, resolved by the shared get_tool_calls_from_response
normalizer so every response surface (chat completions, Responses API,
Anthropic Messages) is covered; the tool registry's response arm delegates
to the same owner. Transactions queue beside the spend-log queue and the
flush job writes index rows plus a new LiteLLM_DailyToolSpend rollup
(date, tool_name PK) in one transaction, retrying connection errors with
backoff (a failed batch commits nothing, so the retry cannot double-count)
and dropping the batch with an error log on anything else.

The endpoint aggregates in SQL: by_tool is the top TOOL_SPEND_TOP_TOOLS
tools by spend via group_by and daily covers only those tools, so the
response is bounded by days x TOOL_SPEND_TOP_TOOLS regardless of range or
tool-name cardinality; the 30-day clamp is gone. total_spend is dropped
from the response; it was never rendered and its deduplicated semantics
are not computable from a rollup. Spend-log retention deliberately does
not touch the rollup, so tool spend history outlives per-request rows.
2026-07-25 21:52:58 -07:00
tin-berri
f7078e2e08
Merge pull request #34265 from BerriAI/litellm_lit4339_upstream_resource
feat(mcp): send RFC 8707 resource indicators on upstream OAuth legs
2026-07-25 18:53:45 -07:00
Yuneng Jiang
086cbb2d85
fix(ui): center vertical toolbar dividers
The shadcn separator primitive ships `data-vertical:self-stretch` so a bare
vertical divider fills its row, but every call site overrides the height with
`h-5`. A definite cross size makes `align-self: stretch` behave as
`flex-start`, so the dividers rendered flush with the top of their flex line
instead of centered: 0px above and 18px below in the dashboard header, 0px
above and 12px below in the models table toolbar

Routes the three vertical dividers through a ToolbarSeparator that pairs the
fixed height with a same-variant `data-vertical:self-center`. Matching the
variant is what matters; tailwind-merge then drops the conflicting class
outright, whereas a plain `self-center` ties on specificity (the variant is
defined with `:where()`) and loses on utility order. The CLI-managed primitive
is left untouched
2026-07-25 18:41:06 -07:00
Yuneng Jiang
a3f81eddcd
fix(ui): stop the custom-server action colliding with the dialog close button
DialogContent's close button is absolutely positioned 16px from the right
edge at 32px wide, so it overlays the rightmost 24px of the p-6 content
box. The justify-between header pins "+ Custom Server" to that same edge
and, being out of flow, the close button reserves nothing. Give the action
a right margin that clears it; keeping the margin on the button rather
than the row leaves the header rule full-bleed
2026-07-25 18:38:06 -07:00
Yuneng Jiang
2ce5900770
style(ui): match MCP Servers tabs to the dashboard's line tab pattern
The MCP Servers page was the only page-level tab bar using the segmented
(pill) TabsList stretched with w-full, which rendered a full-width grey
bar with a lone pill on the left. Every other page-level tab bar
(budgets, vector stores, access groups, organizations, routing groups,
API reference) uses the underlined line variant, so use that here too.
2026-07-25 17:43:53 -07:00
Yuneng Jiang
ecc491756a
fix(ui): restore the wide Add MCP Server dialog
The shadcn migration carried the antd modal's 1000px width over as an
unprefixed max-w-[1000px], which tailwind-merge keeps alongside the
DialogContent base class sm:max-w-md; the responsive variant wins from
640px up, so the dialog rendered at 448px. Prefix the override so the
merge drops the base clamp
2026-07-25 17:42:47 -07:00
Tin Chi Lo
1fa40bd168 feat(cost-optimization): anchor the savings line at a $0 range start
The "Savings over time" chart plotted a single floating dot for short
ranges: the daily rollup keys spend by YYYY-MM-DD, so a one-day range is
one point by construction. Rather than stand up an hourly SpendLogs data
source, read that same daily rollup and make the cumulative line legible.

- Cumulative | Per day toggle. Cumulative accumulates within the range;
  Per day shows the raw stacked bars.
- Cumulative prepends a synthetic $0 point at the range start
  (withStartAnchor) so the line rises from zero to the running total
  instead of floating. An empty series is left untouched so the chart's
  own "No data" state shows.
- Order the daily series oldest-first (the rollup arrives newest-first)
  so the axis reads left to right and the total accumulates forward.
- Header legend, dots on small series, and a "No data" guard on BarChart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 15:35:51 -07:00