Commit graph

43871 commits

Author SHA1 Message Date
mateo-berri
ef104acdaf fix(cli): report a token file logout cannot remove instead of crashing
A ~/.litellm that has gone read-only, or one left root-owned by a sudo login,
refuses both the scrubbed rewrite and the removal. The removal was unguarded,
so 'lite logout' ended in a PermissionError traceback with the credential still
readable in the file. It now comes back as an outcome the command reports,
naming the file and what to do about it, and a file that holds no secret is
still not worth alarming anyone over.
2026-08-20 02:28:48 -07:00
mateo-berri
f86aeba1e7 fix(cli): stop a file-held secret from vouching for an unreadable keychain
A logout run from an install without the keyring package treated a token file
holding its own secret as proof that no keychain entry could exist. That only
holds for the login which wrote the file. A login before it may have had the
package and put its credential in the keychain, where it outlives both the
uninstall and the file that replaced it, so logout reported a clean sweep over
a live credential. Every keychain that cannot be reached is now treated the
same way, and the message says the keychain went unchecked rather than
asserting what is in it.
2026-08-20 02:21:21 -07:00
mateo-berri
b6fef179ff fix(cli): stop a repeat logout from retracting its own keychain warning
A logout that could not reach the keychain deleted the token file whenever it
still held its own secret, and the next logout read that missing file as proof
the keychain was clean. It answered the warning the first run had just issued
with "Logged out successfully" while the entry an earlier login left behind was
still live. The file is the only record that something may still be in there,
which is what `_nothing_left_behind` already says it relies on, so keep it and
take only the secret out.

A keychain that did answer is a different case. `SecretStranded` means the entry
is confirmed there and would not delete, and that needs no note in the file,
while keeping one lets every later command read the credential straight back out
of the keychain, which makes "Logged out locally" untrue. That one drops the
file, as it did before.

The secret still goes first either way: a copy that cannot be replaced with a
secret-free one is removed rather than kept.
2026-08-20 02:08:04 -07:00
mateo-berri
c7da91d47f fix(cli): say so when the keychain took a credential the file cannot name
Staging the token file can succeed and the replacement still fail afterwards,
and that is the one save path where the keychain has already taken the new
secret. It was reported as a save that kept nothing, which sends the user
looking for a credential that is sitting in their keychain, and it claimed the
previous login was untouched when the one keychain slot had just been written
over.

Give that path its own outcome and its own notice. The new secret stays where
it is: the entry it replaced went the moment it landed, so no rollback brings
that back, and removing the new one too would turn a login this machine may
still be able to use into no login at all.

The remaining `CredentialNotSaved` paths all leave both stores untouched, so
the reassurance they carry is now true wherever it is printed.
2026-08-20 01:47:50 -07:00
mateo-berri
ba637553f8 fix(cli): keep lite login and logout honest when the keychain will not answer
Three ways the credential commands could mislead or hang.

`lite logout` on a machine that never logged in warned that a credential may
be stranded in a keychain it could not check, and told the user to install
keyring to go clear it. There was nothing there. A missing token file is now
read as the evidence it is, because logout keeps a secret-free file behind
whenever the keychain is left unconfirmed, so a later run can tell a machine
with a credential it cannot reach apart from one that never had a login. That
holds on the LITELLM_CLI_DISABLE_KEYRING path too.

`KeyringDiscardsWrites` was handled on the read and erase paths, which cannot
produce it: the null backend returns None from `get_password` rather than
raising, so only a write ever detects it. It now lives on `SecretWrite` alone
and the unreachable arms are gone.

`keyring.set_password` blocks forever under a HOME with no usable login
keychain, which is what containers, CI images, `sudo -H`, and service accounts
run with, and reads answer normally there so nothing cheaper tells them apart.
`lite login` never touched a keychain before this, so a sign-in that simply
never returns would be a new way for it to fail. Writes are pre-flighted with
a throwaway value on a bounded wait, and a keychain that stays silent falls
back to the token file. The real credential is never the thing handed to a
call that might land long after we stopped waiting.

Saving also stages the token file before the keychain is given anything, since
the file is the half a read-only or full directory refuses. A save that cannot
land now leaves both stores as it found them, which matters most when the
login it failed to replace still works.
2026-08-20 01:39:43 -07:00
mateo-berri
26f6237745 build: skip deleted files in the changed-file ruff format check
`make lint` hands every path in the diff against the base branch to `ruff
format --check`, including the ones the branch deleted, so any branch that
moves or removes a file under `litellm/` fails the gate with "No such file or
directory" instead of a formatting complaint.

test-linting.yml already filters those out with `--diff-filter=ACMR`, so the
Makefile was the half that drifted. Match it.
2026-08-20 01:39:43 -07:00
mateo-berri
3c73a39877 fix(cli): verify every credential store transition before reporting it done
A keyring backend can accept a write and keep nothing. That is exactly what
`keyring --disable` and PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring
select, and it raises nothing to distinguish itself, so `lite login` was
handing the credential to a black hole, scrubbing its own copy from
token.json, and printing a success message over a login that no longer
worked. Reading the value back is the only way to tell that backend apart
from a keychain that really stored the secret.

The same rule closes the rest of the gaps. A credential the token file will
not record is taken back out of the keychain instead of being left live on a
machine with no record of it, and is reported rather than raised. The
migration stages its scrubbed file before the keychain is handed anything,
so a directory that will not accept the rewrite stops the move rather than
leaving the secret in two places. Logout no longer reads a key in the file
as proof that the keychain is clear, which was never sound across two
separate runs, and only draws that conclusion when the `keyring` package is
missing outright, where nothing could have reached a keychain at all.
2026-08-19 21:10:59 -07:00
mateo-berri
424e74ba9f fix(cli): roll the keychain write back when the plaintext copy cannot be removed
Removing the file when it could not be rewritten covered a full disk, but not a
~/.litellm that permits neither the rewrite nor the delete, which is what a
`sudo lite login` leaves behind. There the secret was copied into the keychain and
kept in cleartext on disk, so migration widened exposure instead of narrowing it

Migration now only keeps the vault copy if the file's copy is gone. When it is not,
the write is rolled back and the user is left exactly as they were, logged in with
one copy of the credential
2026-08-19 20:31:43 -07:00
mateo-berri
1750893a69 fix(cli): never report success while a credential is still readable
Migration moved the secret into the keychain and then suppressed any OSError from
rewriting token.json, so a file that could not be rewritten kept the credential in
cleartext while every command reported success. That file is now removed instead:
signing in again costs one command, a stranded live credential costs the credential

`lite logout` also reported a clean logout whenever the keyring package was missing,
on the reasoning that an install without it could never have stored anything. The
entry belongs to the OS, so a keychain-backed login survives a logout run from a venv
without the cli extra. erase() now reports which keychain state applies, and logout
warns with the advice that fixes each one, staying quiet for file-backed logins whose
token file still carries its own secret

Also pins the migration path's tightening of a world-readable legacy token.json, and
moves the logout tests off patch() onto the injected vault
2026-08-19 20:26:41 -07:00
mateo-berri
65d834f01d Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_cli_refresh_tokens
# Conflicts:
#	basedpyright-code-budget.json
2026-08-19 19:50:26 -07:00
mateo-berri
01add58298 fix(cli): name why a login fell back to the token file
lite ships with every install of litellm, but the keyring package it needs
for keychain storage only ships with the cli extra. Such a user on a Mac was
told 'No OS keychain available' about a machine that plainly has one, with
nothing pointing at the missing package.

The vault now reports which of the three unusable states it is in, so login
can point at the install, name the kill switch, or report a genuinely absent
keychain.
2026-08-19 19:45:38 -07:00
mateo-berri
bd322ed8a7 refactor(cli): state the credential-store precedence rules as contracts
Drop the inline notes on keychain erasure and disk-vs-vault precedence in favour
of docstrings on the two functions that own those rules, and remove a stale
section header and a field note that the code already says plainly.
2026-08-19 19:11:01 -07:00
yucheng-berri
c2b3c4b1e4
feat(ptu): accrue flat cost for PTU deployments declared in config.yaml (#37556)
* feat(ptu): accrue flat cost for PTU deployments declared in config.yaml

The flat-cost rollup reads deployments from LiteLLM_ProxyModelTable, and config.yaml
models never reach that table by design, so a PTU deployment declared there accrued no
flat cost at all while still billing its traffic per token. The provider bills the
reservation whichever file declared it.

The rollup now also reads the deployments the router holds that no database row owns,
identified by db_model, skipping the per-request credential clones that carry
original_model_id and reuse their source's PTU config under a fresh id. Registering such
a deployment zeroes its pricing, since reserved capacity already pays for the traffic it
serves, and leaving a rate unset falls back to the public cost map, which makes the double
charge the default rather than an opt-in.

The rules both halves apply now live in one module. The rollup's test for what it will
charge and the router's test for what to zero have to agree, or a deployment one accepts
and the other declines serves its traffic for free. That module also owns the fields the
write endpoints already zero, so the two paths cannot drift: tiered_pricing is emptied
rather than zeroed because its tiers outrank the rates beside them, the search context
table is written zeroed because an absent one means the provider default, and any further
rate the deployment itself declares is zeroed alongside the standing set.

The prune is bounded to the deployments a run scanned, but only for a run that priced a
config-declared deployment. Deciding a row is garbage on staleness alone stays correct
while every run derives its charges from the same table, so a database-only run sweeps
exactly as it did before; once one host's charges come from a file the others cannot read,
a row it never considered is not evidence of anything.

Behaviour change worth calling out: a zeroed deployment sorts ahead of an unpriced sibling
in QualityRouter's cost tiebreak, where an unset rate previously sorted last. Reserved
capacity really is the cheaper choice, but the ordering moves.

* refactor(ptu): drop a Final rebind and two redundant isinstance guards

The basedpyright budget rejected reassigning a Final in the datetime coercion and
two isinstance calls the router entry's own type already guarantees. Filtering the
built records rather than the raw entries removes both guards and leaves
_router_deployment as the single validator.
2026-08-19 19:08:14 -07:00
mateo-berri
0de829d3e4 feat(cli): store the lite login credential in the OS keychain
lite login used to write the minted cli-session key in cleartext to
~/.litellm/token.json. The secret material (key plus any JWT) now goes
to the OS keychain through the optional keyring package, with the 0600
file kept for non-secret metadata and as the fallback on headless boxes.
Legacy plaintext files keep authenticating and are migrated into the
keychain, then scrubbed, on first read. A secret still on disk always
outranks the keychain entry, so a failed keychain write can never
resurrect a stale key. LITELLM_PROXY_API_KEY and --api-key precedence
is unchanged, lite logout clears both stores and warns when the
keychain will not release the entry, and ~/.litellm is created 0700
(tightened from 0755 where an older CLI left it broader).
LITELLM_CLI_DISABLE_KEYRING=1 forces the file fallback.
2026-08-19 18:57:35 -07:00
yuneng-jiang
a0f367fcd1
Merge pull request #36897 from BerriAI/litellm_standard_page_header
feat(ui): standardize the Teams page header
2026-08-19 18:52:55 -07:00
devin-ai-integration[bot]
3a04860122
feat(proxy)!: default audit logs on for enterprise licenses (#37518)
* feat(proxy): enable audit logs by premium license

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

* test(proxy): support premium audit logging mocks

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

* test(proxy): disable audit logging for key rotation mocks

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

---------

Co-authored-by: yucheng <yucheng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-19 18:49:21 -07:00
yuneng-jiang
0edd245545
fix(ui): render optional array and object MCP tool parameters as JSON inputs (#37548)
* fix(ui): render optional array and object MCP tool parameters as JSON inputs

A Python signature like `tags: list[str] | None = None` serialises to
`{"anyOf": [{"type": "array"}, {"type": "null"}]}` with no top-level
`type`, so the tool test panel's control dispatch fell through to the
generic text input. Whatever the user typed was sent verbatim, and the
server rejected it as the wrong type.

Resolve a property to its single non-null union member before choosing a
control, validating, seeding defaults, and coercing the submitted value,
so all four agree and an optional array or object gets the same JSON
textarea a required one already got.

* fix(ui): keep a null-defaulted optional MCP parameter out of the call

A parameter declared `list[str] | None = None` carries `default: null`,
which means the caller should send nothing. Seeding its editor with an
empty container made the field non-blank, so an untouched parameter was
submitted as `[]` or `{}` instead of being omitted.

Treat an explicit null default as "no value" everywhere it is read: the
editor starts blank and shows its placeholder, and the submitted
arguments leave the key out entirely.
2026-08-19 18:48:43 -07:00
Yuneng Jiang
f99eec5ecb
Merge branch 'litellm_internal_staging' into litellm_standard_page_header
Teams.tsx and Teams.test.tsx both conflicted with staging's antd -> shadcn
migration of the team create form.

Teams.tsx: took staging's rewritten import block and dropped `theme` from the
antd import, since this branch replaced `<Content style={{ padding: token... }}>`
with the Tailwind inset. Dropped both `const { Text } = Typography` (staging
removed its last use) and `const { token } = theme.useToken()` (this branch
removed its last use).

Teams.test.tsx: took this branch's PageHeader-shaped assertions over staging's
older tab-bar lookup, and restored the `within` import that staging had dropped.

Removed the `toHaveClass` snapshot of the antd tab-bar Tailwind classes and the
`.closest(".ant-tabs")` lookup: staging added local/no-antd-class-selectors as a
zero-violation error rule, and those assertions are inert in jsdom anyway. Every
behavioural assertion in that test is unchanged.
2026-08-19 18:45:04 -07:00
yuneng-jiang
2672b36dc3
fix(ui): clear pass-through header rows when the create modal is reopened (#37549)
KeyValueInput and QueryParamInput each seeded a private copy of their rows
from the value prop with a one-time useState initializer. The antd form they
were written for hid that: rc-field-form bumps an internal resetCount key on
resetFields, which remounts a Field's children, so the private copy was thrown
away on every reset. react-hook-form's reset does not remount, and the modal is
hidden rather than unmounted, so after Cancel the rows stayed on screen holding
the old values while the form value went back to empty.

The visible cost was a blocked create flow. A leaked header row made the modal
look configured, but the form value behind it was gone, so submitting a fresh
path and target was refused with "Please configure the headers" and no request
was sent. Typing one character into the leaked row put a value back and the
submit went through, which is not something a user can guess.

Both inputs are now controlled off the value prop, which is an array of pairs
rather than a record. A record cannot represent a row whose name is still empty,
which is the reason the private copy existed: two blank rows collapse into one
and a half-typed row disappears as it is typed. With pairs the field value is
the editable shape, the second source of truth is gone, and a form reset clears
the rows like every other field. add_pass_through converts to a record at submit,
so the request payload is unchanged.

Headers now require at least one row with a non-empty name. Previously that was
enforced by accident, because adding a row did not notify the form at all.
2026-08-19 18:44:01 -07:00
Mateo Wang
8922aaab95
fix(anthropic): log partial stream spend when a /v1/messages client disconnects mid-stream (#37558) 2026-08-19 18:43:46 -07:00
devin-ai-integration[bot]
f5cfa84220
feat(router): allow per-tier litellm_params in complexity autorouter config (#37064)
* feat(router): support complexity tier request params

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

* fix(router): make complexity tier params immutable

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

* refactor(router): simplify complexity tier overlays

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

* fix(router): preserve plain tier config round trips

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

* fix(router): mask tier params in routing decisions

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

---------

Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-20 01:39:02 +00:00
yuneng-jiang
7b6e16cfd3
perf(ci): gate the lint, MCP and dashboard jobs on the pull request's file list (#37559)
PR #37550 taught the backend unit-test shards to read the pull request's own
file list, but four required jobs were never wired to that gate and ran in full
on every pull request regardless of what it touched. A UI-only pull request
still paid roughly 17 runner-minutes of Python work it could not have affected,
and a backend-only one still installed and built the dashboard.

Lint and the MCP suite now take the existing backend decision. The dashboard
build and unit tests take a new ui decision, which tracks ui/ rather than
reusing client: client deliberately runs whenever the backend changes, because
it gates CircleCI's end-to-end jobs that drive a real proxy, while the build and
the unit tests cannot see the backend at all. CI config counts as ui-relevant
too, so a pull request that rewrites the dashboard workflows still exercises
them instead of shipping unvalidated.

The gate stays inside the job rather than moving to on.paths or to a job-level
condition on the shard callers. A workflow filtered out by on.paths never starts
and never reports, so a required check waits forever, and a skipped caller job
publishes its own name instead of the nested "<shard> / Run tests" the ruleset
requires. Both were measured before settling on this shape.

Three setup steps in the shard base and in the documentation job also leaked
past the gate, so a skipped shard still spent about twelve seconds installing uv
and restoring its cache. They now carry the same condition, and the documentation
job stops cloning litellm-docs when it has nothing to validate.
2026-08-19 18:32:21 -07:00
ryan-crabbe-berri
0ab1725757
refactor(ui): migrate the model and router settings pages off antd (#37523)
* refactor(ui): migrate the model and router settings pages off antd

Converts the add model flow, credential panels, model settings and router
settings onto the shadcn primitives, moves the mapping table onto the
shared DataTable, and drops the dead uploadProps prop chain that only
existed to carry antd's UploadProps type.

* fix(ui): split comma-separated custom technical keywords into one term each
2026-08-20 01:19:09 +00:00
Mateo Wang
26841dae43
Merge pull request #37520 from BerriAI/litellm_lit_5795_failed_request_deployment_details
fix(proxy): populate deployment attribution on failed-request spend logs
2026-08-19 18:14:01 -07:00
Mateo Wang
a6163e0146
Merge pull request #37543 from BerriAI/litellm_lit_5785_vertex_regional_pricing
fix(vertex_ai): apply regional endpoint uplift to cost tracking
2026-08-19 17:56:34 -07:00
ryan-crabbe-berri
cac4870271
refactor(ui): migrate the MCP servers pages off antd (#37522)
* refactor(ui): migrate the MCP servers pages off antd

Converts the MCP server create, edit, connect and permission screens plus
the MCP tools and selector components onto the shadcn primitives, and
rewrites the test helpers that drove antd's select and collapse DOM.

* fix(ui): finish the MCP servers antd migration so the shared field rules have one contract

mcpFieldRules and MCPPermissionManagement were already flipped to the shadcn
prop shape, but CreateMCPServer and UserEnvVarsModal were still rendering antd,
so the create modal spread onValueChange onto an antd Select that ignores it and
passed searchValue props that no longer exist. Convert both off antd, drop the
searchValue plumbing the MultiSelect now owns, and normalise tag values before
the tag list renders so a delimited or empty string cannot crash it.

Rewrite testUtils.selectOption to drive the shadcn listbox instead of
.ant-select, expand the collapsed permission panel before querying its switches,
and assert the dismiss case after a reopen now that Dialog unmounts closed
content.

* fix(ui): split multi-tag entries the MCP tag inputs commit as one value

The tag input hands back whatever the admin typed as a single custom value, so
"read,write" was stored verbatim and reached the backend as one malformed scope.
tagsControl already split delimited values on the way in; run the same
normalisation on the way out and dedupe, so both directions agree.

* fix(ui): stop splitting tag entries that are not scope lists

The previous commit split every tag field on whitespace and commas, but only a
scope list is delimited. A stdio arg or an access description item may contain
both characters as part of the value, so splitting them changed the argv the
process receives. Keep those entries verbatim and move the splitting behind
scopesControl, which the OAuth, token exchange and ID-JAG scope fields use.

* fix(ui): split tag entries on comma only, matching the antd token separator

Every tag field here was an antd Select carrying a comma token separator, so a
comma committed a tag and nothing else did. Splitting on whitespace as well
broke stdio args, and splitting neither left comma-separated extra headers and
access groups stored as one malformed value. Apply the comma rule in both
directions, trim each entry, and drop the scope-specific helper the previous
commit added, since the backend types scopes as a list rather than the
space-delimited string that helper assumed.

* fix(ui): stop rewriting stored tag values that an admin never edited

Stdio args are process argv, so a comma inside one argument and a
deliberately repeated flag both have to survive a round trip through the
edit modal. Two places were rewriting them. tagsControl split and deduped
the value it read back from the server, and MultiSelect re-split every
already-committed chip on each change rather than only the entry just
typed. Both now leave settled values alone, which keeps the antd token
separator applying to typing and nothing else.
2026-08-20 00:50:02 +00:00
Mateo Wang
6292489192
fix(spend-logs): backfill created_at/updated_at from endTime instead of migration time (#37554) 2026-08-19 17:27:33 -07:00
ryan-crabbe-berri
fc8a6b2a8d
refactor(ui): migrate shared primitives and common components off antd (#37521)
* refactor(ui): migrate shared primitives and common components off antd

Adds the success variant to the shared Alert plus success, warning and
info variants to Badge, introduces UtcDateTimeInput to replace antd's
DatePicker, and converts the common components and key/team helpers onto
the shadcn primitives.

* fix(ui): keep MultiSelect and budget input faithful to their antd behaviour

Restore the clear-all control MultiSelect lost, split comma-separated
custom entries into one value per token, and stop rounding the budget
input on every keystroke so a fractional amount survives typing.

* test(ui): drive the access group picker through the migrated MultiSelect

AccessGroupSelector no longer renders an antd Select, so the placeholder
is an input label rather than a text node and the popup inerts the page
until it closes.
2026-08-20 00:25:09 +00:00
mateo-berri
1140366bee fix(vertex_ai): resolve passthrough serving location in the logging cost recompute 2026-08-19 17:24:44 -07:00
ryan-crabbe-berri
629d7683f2
refactor(ui): swap @ant-design/icons for lucide-react (#37553)
The dashboard drew its icons from two libraries at once: lucide-react,
which shadcn/ui ships with, and @ant-design/icons, left over from antd.
This moves the last 39 files onto lucide and drops the dependency, so
the icon set matches the component library everywhere.

antd icons sized themselves from the inherited font-size and rendered as
role="img" with an aria-label, neither of which a lucide svg does, so the
swap carries explicit size classes and gives the two icon-only plugin
buttons real accessible names.
2026-08-20 00:04:14 +00:00
Yassin Kortam
f22eeb2ce0
fix(proxy): initialize the secret manager before resolving os.environ config references (#37544)
`ProxyConfig.get_config()` walked the parsed config and replaced every
`os.environ/<KEY>` string with `get_secret(value)` before anything initialized
the secret manager, so a key held only by the manager resolved to `None` and
that `None` was written back into the config. The later fallback in
`load_config` could not recover it, because the key now existed with a `None`
value.

Hoist the initialization into `get_config()`, ahead of the resolution pass, so
every entrypoint gets it: the CLI already did this itself, but the microservice
entrypoints (`gateway/main.py`, `backend/main.py`) uvicorn the app directly and
bypass the CLI. `load_config`'s own call is now redundant and is dropped, so
startup builds the manager once instead of building one and discarding it.

`get_config()` also runs on management-endpoint request paths, so this returns
early once a manager exists rather than rebuilding the client per request.

Also warn when a reference the manager would have been asked for resolves to
`None`. The reporter had no log line at all to work from. `get_secret` only
reaches the manager when reads are enabled and the name is in `hosted_keys`, so
`secret_manager_would_be_consulted` mirrors that gate and keeps the warning off
env-only references, which are expected rather than an error.
2026-08-19 17:00:26 -07:00
mateo-berri
919bf1a097 fix(proxy): strip client standard_logging_object before the failure logging handler 2026-08-19 16:53:02 -07:00
Mateo Wang
2bd897a49d
Merge pull request #37551 from BerriAI/litellm_codeowners_model_prices
chore(codeowners): require pricing owner approval for the model prices jsons
2026-08-19 16:52:06 -07:00
mateo-berri
c549cddada fix(vertex_ai): price passthrough calls on the URL's serving location 2026-08-19 16:44:52 -07:00
yuneng-jiang
eecb226762
fix(ci): gate backend unit tests on the pull request's own file list (#37550)
detect-backend-changes diffed the event payload's base.sha against the
checked-out ref. Those are two different points in time: actions/checkout
resolves refs/pull/N/merge, and GitHub recomputes that ref whenever the base
branch advances, so the diff picked up whatever landed on staging between the
event firing and the job starting. On a recent UI-only pull request three
backend commits from staging were attributed to the branch, and every backend
shard ran in full

Ask the API which files the pull request touches instead. That is the same set
the Files changed tab shows, and it is immune to either endpoint moving. The
shell body moves into .github/scripts/detect_backend_changes.sh so it can be
exercised directly, and the fail-open paths now also cover an API failure, a
file list past the API's 3000-entry listing ceiling, and a classifier that
prints something unexpected
2026-08-19 16:41:56 -07:00
yucheng-berri
b7181a8914
perf(otel): build the credential-scoped tracer Resource once per logger (#37542)
Every dynamic tracer-provider build called Resource.create, which scans the entry
points of every installed distribution, roughly 3ms and 200 file opens. The dynamic
providers reach it from the async logging path, which runs on the event loop serving
requests, so past the provider cache bound every request paid it and delayed the
requests in flight alongside it

The value derives only from the logger's config and process environment, so it is
built once per logger and reused. This logger's own init-time providers share it,
which also removes redundant startup builds. ArizeLogger overrides _init_tracing and
still builds its own, so it keeps one extra build

Refs LIT-5437
2026-08-19 16:40:40 -07:00
mateo
dc70c144d7 chore(codeowners): require @mateo-berri approval for the model prices jsons
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-19 23:35:54 +00:00
mateo-berri
7b6f537855 fix(anthropic_messages): price native /v1/messages vertex calls on the deployment location
The proxy pre-creates the logging object before the router picks a deployment,
and the native /v1/messages handler never copied the deployment's
vertex_location into the logging params it updates, so cost resolution fell
back to the environment or the default region and priced every call on this
surface with the regional endpoint uplift. Copy the explicitly configured
location from the request's litellm params, the same source dispatch builds
the request URL from, and register the new regional_endpoint_uplift_multiplier
field in the cost map schema test.
2026-08-19 16:26:34 -07:00
Mateo Wang
75a290ec1d
Merge pull request #37545 from BerriAI/litellm_gitignore_claude_local
chore: gitignore CLAUDE.local.md
2026-08-19 16:25:43 -07:00
Mateo Wang
0a3504c8a3
Merge pull request #37527 from BerriAI/litellm_batch_file_upload_validation
feat(proxy): fast-fail validation for batch input files at /v1/files
2026-08-19 16:24:39 -07:00
yuneng-jiang
94374beb6d
fix(ui): toggle unlimited budget when its text is clicked (#37547)
The checkbox and its visible text both sat inside the max_budget
FormField label, which renders a single <label htmlFor> pointing at the
number input, so clicking the text focused Max Budget instead of ticking
the box. The checkbox only carried an aria-label, so it had no label of
its own to catch the click.

Wrap the checkbox and its text in their own <label>, the association the
antd checkbox wrapper used to provide. The accessible name now comes
from that label, so the aria-label is dropped rather than duplicated,
and the outer field label still points at the number input.
2026-08-19 16:24:12 -07:00
mateo-berri
dce207add4 fix(proxy): strip client standard_logging_object and zero-fill unknown recovered cost on the failure path
Auth and pass-through failures reach post_call_failure_hook with the raw request body unstripped, so a client-supplied standard_logging_object could feed the new attribution fallback when the logging object carries none. Pop the key before the lift so only the logging object may supply it. Also coalesce a None recovered cost to 0.0 so the lift always overwrites any client-supplied response_cost, matching the merge base's clobber semantics.
2026-08-19 16:19:26 -07:00
yuneng-jiang
663e647bc8
refactor(ui): migrate antd Modal onto the shared shadcn Dialog (#37540)
* test(ui): cover the two modals no test would catch breaking

Both files sit in the antd Modal migration's blind spot. EditSSOSettingsModal's
test replaced antd wholesale with a stub Modal and asserted the stub's own
data-testid markup, so it proved nothing about the modal a user sees and would
have stayed green through any regression. routing_groups had no test at all.

Rewrite the first against the real antd Modal, querying by dialog role and
accessible name so the assertions hold under either library, and add an
integration test for the second that drives the row menu and the delete
confirmation end to end.

Modal width drops out of the SSO assertions: antd carries it as an inline style
and shadcn as a max-width class, so either form couples the test to the library
rather than to anything a user perceives.

* refactor(ui): move the straightforward antd Modals onto the shared Dialog

Twenty files whose Modal only used title, open, width, footer, className and
onCancel, so each one maps onto Dialog without judgement calls. Width becomes a
max-width class, the body gets the house scroll cap so tall content stays
reachable, and destroyOnHidden goes away because Base UI unmounts a closed
dialog on its own.

EditMembership needed a real fix rather than a translation. Clearing the form
after a submit resolved only ever worked by accident: the reset set every field
to undefined, which react-hook-form does not push out to a subscribed
Controller, and the fields looked cleared only because antd's Modal happened to
re-render the subtree afterwards. Dialog does not, so the stale values showed
through. emptyMemberFormValues now returns the empty value each control
actually understands, an empty string, null or an empty list, and the reset
lands whatever renders around it. Its test asserted the undefined shape while
describing the behaviour it was missing, so it now checks the values instead.

* refactor(ui): migrate the antd Modals that needed a judgement call

Sixteen more files. Most carried a prop that does not translate literally:
maskClosable becomes disablePointerDismissal, afterOpenChange becomes
onOpenChangeComplete, and closable={false} becomes showCloseButton={false}.

The styles prop went away everywhere it appeared. All but one instance set the
body to 24px and the header to 24px with no border, which is what DialogContent
already renders, so keeping it would have meant writing the default back by
hand.

Several Modals passed onOk alongside footer={null}, so antd rendered no OK
button and the handler could never fire. Each of those handlers was a
character-for-character copy of the neighbouring onCancel, so they are gone
rather than translated.

Rich titles now sit inside DialogHeader with DialogTitle carrying the heading
text, instead of the whole header block being nested inside DialogTitle. That
had put an h2 inside another h2, which is invalid and gave one dialog two
headings.

UserEnvVarsModal loses its formGeneration counter. Remounting the form when the
modal finished opening only mattered because antd kept a closed modal's
children mounted; Base UI unmounts them, so reopening is blank on its own. Its
test helper had encoded that remount as a timing assumption, so the file now
states the requirement outright and checks that reopening shows an empty field.

CreateMCPServer's cancel test read the tool list while the modal was closed,
which only worked because forceRender kept it mounted. It now asserts what a
user can actually observe: the panel is gone while closed, and reopening brings
back an empty URL and no tools.

Unmounting an open Base UI dialog leaves its scroll lock on <html> and <body>,
which survives cleanup() and makes every later test in the file see a locked
page where popups compute pointer-events: none and clicks quietly do nothing.
The shared setup now releases it.

* refactor(ui): finish the antd Modal migration onto the shared Dialog

Fifteen files whose Modal relied on antd's built-in footer. okText, cancelText,
onOk, okButtonProps, cancelButtonProps and confirmLoading collapse into two
explicit buttons in a DialogFooter, with danger becoming the destructive
variant and the various loading flags becoming disabled plus aria-busy. The one
okButtonProps that also hand-set a red background drops it, since the variant
already carries that.

add_guardrail_form keeps its own chrome, so its DialogContent turns off the
built-in close button and the padding, and its heading becomes the DialogTitle.
Under antd it passed title={null} and had no accessible name at all.

Modals that positioned themselves near the top of the viewport needed
translate-y-0 alongside top-8, because DialogContent centres itself with a
transform that top alone does not undo.

TeamGuardrailsTab's test reached its Mode select by index into every combobox on
the page. A modal dialog hides the rest of the page from assistive technology,
which antd never did, so the count changed and the index pointed at the wrong
control. It asks for the field by label now.

The mask-dismissal test drove antd's .ant-modal-wrap class directly; it uses the
overlay slot our own component exposes, and still fails if
disablePointerDismissal is dropped.

CreateUserButton stays on antd. Its Modal converts cleanly, but the colocated
test file then fails a varying handful of cases, and the cause sits in the test
file rather than the component, so it wants its own change.

Pruning suppressions for the touched files also cleared four
react-hooks/set-state-in-effect entries on CreateMCPServer that were already
stale before this branch.

* test(ui): pick Base UI select options through the shared helper

React 19's flush timing loses the race this test was relying on: the option
lands in the DOM one render before its positioner drops pointer-events: none,
so user-event refused the click. tests/test-utils already exports
chooseSelectOption for exactly this, added alongside the React 19 upgrade.
2026-08-19 23:16:42 +00:00
mateo
7bdcfa65b7 chore: gitignore CLAUDE.local.md
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-19 23:09:25 +00:00
Yassin Kortam
3f2e0badb4
fix(helm): default litellm-helm to the ghcr.io/berriai/litellm image (#37491)
The chart shipped ghcr.io/berriai/litellm-database as its image default,
with a comment offering it as the "optimized image with database". That
distinction no longer exists: Dockerfile and docker/Dockerfile.database
differ only in comment text and one builder-stage apk package, and both
published images bake the prisma CLI, engines, schema.prisma and
prisma_migration.py into /app, so either one runs the migrations job.

Point the default at the canonical image the release notes, the cosign
verification instructions and the chart's own README already name, and
update the chart's tests and README so nothing still refers to the
legacy repository.
2026-08-19 16:04:20 -07:00
Mateo Wang
634e699555
Merge pull request #36331 from BerriAI/devin_ai_agentcore_search
feat(search): add Amazon Bedrock AgentCore web search provider
2026-08-19 15:59:17 -07:00
mateo-berri
8494a4deee fix(vertex_ai): read the served location from optional_params when pricing proxy calls 2026-08-19 15:58:12 -07:00
Mateo Wang
da9d406e8d
Merge pull request #34887 from RayJueWang/litellm_fix_spend_deadlock_retry
fix(proxy): retry spend updates on Postgres deadlock instead of dropping them
2026-08-19 15:53:45 -07:00
Mateo Wang
449bf68498
Merge pull request #36987 from BerriAI/litellm_infer_single_worker_redis_banner
feat(proxy): auto-suppress the no-Redis banner for confirmed single-worker deployments
2026-08-19 15:52:58 -07:00
ryan-crabbe-berri
5d6033f8b4
refactor(ui): migrate the remaining dashboard pages off antd (#37524)
* refactor(ui): migrate the remaining dashboard pages off antd

Converts the teams, usage, guardrails, vector stores, cost tracking,
agents, policies, login and onboarding screens onto the shadcn
primitives, including the team info tab shell and the virtual keys
hover cards.

* fix(ui): close out the antd migration's failing type checks and tests

Alert and Badge were missing the success and info variants their call
sites already used. Combobox dropped disabled because Base UI merges the
primitive's own props over the render child, so the flag never reached
the input, and the guardrails status filter had no accessible name, which
left two comboboxes indistinguishable to the tests.

The remaining test updates swap antd's title-based queries for the roles
the shadcn controls expose.
2026-08-19 22:46:55 +00:00