Commit graph

3908 commits

Author SHA1 Message Date
mateo-berri
64e7ab1beb fix(cli): keep the login record when the proxy cannot record a logout's revocation
A 503 from POST /revoke means the proxy could not write the single-use record, so clearing the local record left a live refresh token nobody could revoke and a hint to retry with nothing left to retry. lite logout now keeps the record, exits 1, and asks to be run again shortly. A refused or unreachable revocation still clears the record and warns as before, and a re-login that replaces a record keeps its existing warning because the new record already stands
2026-08-20 08:16:30 -07:00
mateo-berri
238609ea5d fix(mcp): answer 503 when a refresh-token burn cannot be recorded
revoke_refresh_token discarded the single-use claim result, so a revocation that arrived while Redis was unreachable answered 200 and left the refresh token live. The token endpoint reported the same outage as invalid_grant "already used". The guard now reports first, replayed, or unavailable, and both endpoints answer 503 temporarily_unavailable for an outage (RFC 7009 section 2.2.1, RFC 6749 section 5.2), which the CLI surfaces as a one-line warning while keeping the key it has
2026-08-20 07:56:38 -07:00
mateo-berri
a20cfc5f0e fix(cli): let lite up trust the key the group already renewed
The lite group resolves the stored key once for every command and renews a --pkce key on the way in. lite up then asked the token file again, so every start sent a second refresh to the proxy, and once the refresh token was burned the refusal printed twice. _ensure_fresh_login now reuses the key the group resolved when the group read it from the token file, and only re-reads the file after the interactive login it starts itself. Also covers print-token through the group with a renewing session in the tests
2026-08-20 07:40:17 -07:00
mateo-berri
36e9e813a7 fix(cli): renew the key once per lite auth print-token run
The cli group already resolves the stored key for the server it was pointed
at, so print-token re-ran the renewal and, when the refresh token had been
revoked, posted to /token twice and printed the reason twice. print-token now
reuses the group's result whenever the stored record was issued for that
server and no --api-key or LITELLM_PROXY_API_KEY took precedence, and only
resolves the key itself when invoked bare for a different server.
2026-08-20 07:29:04 -07:00
mateo-berri
44d3fc7ec8 fix(cli): name lite login --pkce and the refresh failure when a key cannot be renewed
A PKCE credential whose renewal is refused (for example after lite logout ran
on another copy of it) used to fail lite auth print-token with the classic
'Token expired. Run lite login again' hint and no reason, while lite whoami
already named lite login --pkce. fresh_api_key now reports why a renewal
failed through a warn callback whenever no sibling rotation rescued it, the
CLI prints that reason on stderr, and the expiry hint names the command that
produced the credential. Both READMEs document the admin revocation semantics
and the Redis precondition for refresh single use on several workers.
2026-08-20 07:21:42 -07:00
mateo-berri
39bdacb3a6 fix(cli): revoke the previous login's refresh token when lite login replaces a stored record 2026-08-20 06:26:57 -07:00
mateo-berri
218a3ec370 fix(cli): never follow a redirect when posting to the proxy's OAuth endpoints
Discovery checks that every endpoint sits on the proxy origin, but the CLI's
requests.Session followed redirects, and requests replays a POST body on
307 and 308, so a token or revocation endpoint answering with one of those
would have sent the code and verifier, or the refresh token, wherever
Location pointed. Every POST now goes out with allow_redirects=False and a
3xx answer fails the command with a message naming where it pointed
2026-08-20 06:03:13 -07:00
mateo-berri
17455f4086 fix(proxy): refuse a teamless native-client grant for a user who has teams
The consent page offers the team picker, but a form posted without a team
sealed a teamless grant and the token endpoint minted an unscoped
credential for a team member, escaping the team attribution classic lite
login always applies. The minter now refuses such a grant on redemption
and refresh alike; memberships whose team rows are gone still count as no
team so they cannot lock a user out
2026-08-20 05:37:35 -07:00
mateo-berri
c011b4b56f fix(cli): tell whoami's expired PKCE key to sign in again instead of promising a renewal 2026-08-20 05:22:26 -07:00
mateo-berri
52c94327bd fix(cli): renew a --pkce key wherever lite checks freshness and show its expiry in whoami 2026-08-20 05:17:00 -07:00
mateo-berri
fb123b33de fix(cli): bind the sibling refresh fallback to the same user and team 2026-08-20 04:41:43 -07:00
mateo-berri
912206832c fix(cli): pin PKCE discovery to the typed proxy and ignore foreign sibling records
The discovery document is accepted only when its issuer is the --base-url the user
typed and every endpoint and the resource share that origin (RFC 8414 section 3.3),
so a tampered or redirected document can no longer point the code, verifier, or
refresh token at another host. After a failed refresh the re-read token record is
used only when it continues the same credential (same proxy, token endpoint, and
resource) and has not expired, so a concurrent login against a different proxy can
never hand this one its key
2026-08-20 04:29:01 -07:00
mateo-berri
2c691d3820 feat(proxy): native CLI login with OAuth authorization code + PKCE
The proxy's OAuth authorization server (dynamic registration, PKCE S256,
loopback redirects, single-use codes, refresh rotation) gains a proxy-API
audience: /authorize?resource=<proxy origin> renders a consent page with
team selection and /token mints the same per-user credential lite login
mints, so a native CLI can sign a user in through the system browser and
call /v1/* with user and team attribution. Adds GET /.well-known/litellm-cli-auth
as the versioned discovery contract for non-Python clients, POST /revoke
(RFC 7009) for logout, and lite login --pkce, lite logout, and
lite auth print-token on the CLI side. Proxy-API grants only ever redirect
to a loopback address and the server never picks a team on the user's behalf.

Fixes #37332
2026-08-20 04:10:10 -07:00
Mateo Wang
47a7e1742e
Merge pull request #37539 from BerriAI/litellm_batch_enqueued_token_limit
feat(proxy): enqueued-token rate limiting for batches with refund on completion and cancellation
2026-08-19 22:22:05 -07:00
yucheng-berri
8cf0b50125
fix(ptu): hand the prune a plain delete filter the query builder can serialise (#37571)
* fix(ptu): hand the prune a plain delete filter the query builder can serialise

The bounded sweep built its predicate as a read-only mapping view, which the query
builder refuses to serialise, so the nightly job raised as soon as a config-declared
deployment was priced. The charges were already written by then, which is why the run
looked like it had produced its rows.

The in-memory table these tests run against accepts any mapping, so only a live run
caught it. A predicate builder now returns a plain dict and is asserted as one, and the
catch-up pass has a test covering a config-declared reservation.

* refactor(ptu): build the prune predicate in one shot

Both filter shapes are known upfront, so the bounded one is constructed
directly rather than by mutating a value already declared Final.

The catch-up test took two independent clock reads, which disagree across
UTC midnight; it now derives both the reservation start and the expected
last charged day from a single read, matching the three sibling tests.
2026-08-19 22:18:58 -07:00
mateo-berri
d5ac49588a Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_batch_enqueued_token_limit 2026-08-19 19:31:46 -07:00
hiraku-miyoshi
2a771caf02 fix(proxy): clamp reservation record TTL so stale records never outlive their counters 2026-08-19 19:12:22 -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
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
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
mateo-berri
5ab20c3678 fix(batch_enqueued_tokens): tombstone popped Redis reservation records so local ghosts cannot double-refund 2026-08-19 17:47:06 -07:00
mateo-berri
1140366bee fix(vertex_ai): resolve passthrough serving location in the logging cost recompute 2026-08-19 17:24:44 -07:00
hiraku-miyoshi
6b17b8a8f2 fix(proxy): restrict batch_enqueued_token_limit metadata writes to proxy admins
The field replaces the standard RPM/TPM checks for batch submissions, so a
key holder or team admin writing it could pick their own batch quota.
Mirrors the output-token-estimate admin gate: change-based, so resending
the stored value stays allowed, and enforced on key generate, update, bulk
team-key update, regenerate, and team new/update.
2026-08-19 17:22:15 -07: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-berri
504112d5ca fix(batch_enqueued_tokens): keep the over-limit verdict on rollback failure, find locally saved records on pop
A Redis over-limit verdict now survives a failing rollback DECRBY instead of
escaping into the in-memory fallback and granting tokens the counter already
rejected; the unrolled increments expire with the TTL. pop_reservation now
falls through to the local record when the Redis pop succeeds but finds
nothing, so a reservation saved in memory after a transient Redis save
failure still refunds on cancel or completion.
2026-08-19 16:52:46 -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
mateo-berri
4333d52813 fix(batch_enqueued_tokens): scope in-memory refunds to the granting worker
In-memory grants now record an owner token, and a refund only debits local
counters when the popping worker is the one that granted them, so a terminal
response handled elsewhere can no longer shrink another worker's unrelated
fallback reservations. A Redis-granted refund that fails no longer falls back
to decrementing local counters either: the leaked Redis increments expire
with the TTL and only tighten the allowance.
2026-08-19 16:36:27 -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
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
mateo-berri
50896f21b3 fix(batch_enqueued_tokens): roll back partial reserves, route refunds by backend, lowercase terminal statuses
Reserve-script failures now roll back the scopes already incremented before
re-raising into the in-memory fallback, so a partial redis outage no longer
leaks counter increments that shrink the shared allowance. Reservations
record which backend granted them, so a refund never debits redis counters
an in-memory grant did not charge. Terminal-status matching is now
case-insensitive because the Bedrock async-invoke retrieve path returns raw
AWS-cased statuses like Completed.
2026-08-19 16:16:08 -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
mateo-berri
7e27e211a1 Merge branch 'litellm_internal_staging' of https://github.com/BerriAI/litellm into litellm_lit_5785_vertex_regional_pricing
# Conflicts:
#	type-discipline-budget.json
2026-08-19 15:44:05 -07:00
mateo-berri
160d3dac42 fix(proxy): issue enqueued-token Lua calls one key at a time for Redis Cluster compatibility 2026-08-19 15:40:15 -07:00
mateo-berri
9bb5483991 fix(proxy): apply db-backed max_batch_file_size_mb on config reload 2026-08-19 15:37:44 -07:00
mateo-berri
b477d0967a fix(proxy): lift standard_logging_object onto request_data before the logging object is popped 2026-08-19 15:34:38 -07:00
ryan-crabbe-berri
74b279bc44
fix(auth): resolve bare model names against wildcard deployments in model access groups (#37492)
* fix(auth): resolve bare model names against wildcard deployments in model access groups

* test(e2e): cover model access group permission checks on keys and teams
2026-08-19 15:33:29 -07:00
Yassin Kortam
6ca48efc8b
feat(cli): add lite login --config-claude to wire Claude Code at login (#37507)
`lite up` already patches ~/.claude/settings.json, but only for as long as it
runs in the foreground, and it restores the original file on exit. Users
proxying Claude Code through LiteLLM therefore have to re-wire it by hand after
every login.

--config-claude makes that write persistent. It reuses the settings shape
`lite up` writes (env.ANTHROPIC_BASE_URL plus an apiKeyHelper invocation),
preserves every unrelated key, creates the file when missing, and writes it
atomically with owner-only permissions. Plain `lite login` is unchanged.

Reaching the credential through apiKeyHelper rather than copying it into the
file means a later login refreshes it with no further action, and keeps the
short-lived CLI token out of settings.json entirely.

The shared parts of the settings-file handling move from up.py into a new
claude_settings.py, since up.py imports auth.py and so auth.py cannot import
up.py back. That module now also owns the registry of commands that can be
temporarily managing the file, so the persistent write refuses while either
`lite up` or `lite autoroute up` holds a backup it would later restore over
this write.

Because this write has no backup and no `lite down`, it is stricter than
`lite up` about the user's file: it writes through a symlinked settings.json
rather than replacing the link with a regular file, and it refuses rather than
silently discarding a non-object `env` value.

Also fixes the apiKeyHelper command itself: --base-url belongs to the
top-level `lite` group, so `lite auth print-token --base-url X` is rejected by
click with "No such option". Every settings file `lite up` has written carries
that malformed command, which makes the helper return nothing and every Claude
Code request lose its token. The existing tests only string-matched the
generated command, so the new tests parse it through the real CLI instead.
2026-08-19 15:32:14 -07:00
mateo-berri
a870d45a8a Merge branch 'litellm_internal_staging' of https://github.com/BerriAI/litellm into litellm_lit_5785_vertex_regional_pricing
# Conflicts:
#	litellm/model_prices_and_context_window_backup.json
#	model_prices_and_context_window.json
2026-08-19 15:31:42 -07:00
Mateo Wang
d192ceec73
Merge pull request #37457 from BerriAI/litellm_batch_empty_line_cost
fix(batches): stop one bad output line from zeroing an entire batch's spend
2026-08-19 15:26:37 -07:00
mateo-berri
0f4c2d71fb test(files): hoist shared batch line fixture into one constant 2026-08-19 15:22:43 -07:00
mateo-berri
b39a339b7d fix(vertex_ai): apply regional endpoint uplift to cost tracking 2026-08-19 15:21:06 -07:00
mateo-berri
708ff0b910 fix(proxy): retry end-user spend updates on Postgres deadlock instead of dropping them 2026-08-19 15:20:59 -07:00
mateo-berri
31090d122e Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_infer_single_worker_redis_banner
# Conflicts:
#	litellm/proxy/proxy_server.py
2026-08-19 15:12:57 -07:00
mateo-berri
7a6a677b72 feat(proxy): enqueued-token rate limiting for batches with refund on completion and cancellation 2026-08-19 15:09:05 -07:00
mateo-berri
680e4a5736 fix(files): treat nonpositive max_batch_file_size_mb as no cap 2026-08-19 15:05:17 -07:00
mateo-berri
c5194a8507 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_spend_deadlock_retry 2026-08-19 14:55:58 -07:00
mateo-berri
2a4598219d feat(proxy): fast-fail validation for batch input files at /v1/files 2026-08-19 14:43:22 -07:00