Commit graph

47875 commits

Author SHA1 Message Date
Oliver Jensen
7f07a4da7f
fix(auth): annotate the strict-rule suppressions the merged gates now count
Some checks are pending
LiteLLM Rust / rust-lint (push) Waiting to run
LiteLLM Rust / rust-test (push) Waiting to run
Terraform Modules / fmt, validate, test (aws) (push) Waiting to run
Terraform Modules / fmt, validate, test (gcp) (push) Waiting to run
The staging merge brought BLE001 into the strict ruff set and lowered the
LIT002 ceiling, so the HIBP fail-open except and the params/headers dicts
in password_policy.py now need their noqa and mutable-ok reasons. The
headers dict moves to an annotated Final so the suppression fits the line
limit.
2026-09-09 21:45:12 +02:00
Oliver Jensen
4439c329c9
Apply suggestion from @greptile-apps[bot]
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-09-09 21:45:12 +02:00
Oliver Jensen
332826e4cb
fix(auth): screen bulk-update passwords concurrently before any db write
/user/bulk_update awaited a separate HIBP lookup for each user in the
batch, so a degraded-slow HIBP (5s timeout per lookup) could stretch a
500-user batch to ~2500s and time out the request after some updates
had already persisted.

validate_passwords_bulk dedupes the batch's passwords, strength-checks
first, then fires every needed HIBP lookup concurrently, bounding the
worst case at one 5s timeout window. bulk_update_processed_users now
screens the whole batch before the serial update loop, so a rejected
password fails only its own entry and validation failures precede any
persistence.
2026-09-09 21:45:12 +02:00
Oliver Jensen
e08f697d95
move hibp url to constants 2026-09-09 21:45:12 +02:00
Oliver Jensen
82e9287b2e
fix(ui): regenerate schema.d.ts for the new_user password docstring 2026-09-09 21:45:12 +02:00
Oliver Jensen
eb899f6085
fix(auth): drop general_settings import left unused in new_user 2026-09-09 21:45:12 +02:00
Oliver Jensen
8c76a76157
fix(auth): document /user/new password rejection and format password_policy 2026-09-09 21:45:12 +02:00
Oliver Jensen
e4d165efa5
hibp support in password policy 2026-09-09 21:45:12 +02:00
devin-ai-integration[bot]
096984bfc2
fix(proxy): pin multi-root CA bundle to the server's root before handing it to Prisma (#40428)
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-09 11:19:29 -07:00
ryan-crabbe-berri
f89e9ac749
Merge pull request #40425 from CaptainAni187/fix_aws_tests_ambient_ssl_cert_file
test: isolate bedrock aws tests from ambient SSL env vars
2026-09-09 11:06:14 -07:00
tin-berri
c82c9cbced
fix(router): strip encrypted reasoning on an auto-router tier change instead of a 503 (#40280)
A Responses API follow-up that replays reasoning.encrypted_content is pinned to the
deployment that minted it. Behind an auto-router the pre-routing hook rebinds the model
to the tier it picked before the candidate pool is built, so a turn that classifies into a
different tier never finds the origin and the affinity check raised its fail-fast 503,
whose text claims a cooldown that does not exist

When the deployment that minted the reasoning is not a member of the model group this turn
is routed to, strip the encrypted reasoning (keeping any readable summary, string or block
form) and dispatch to the routed group. Membership is tested by deployment id against the
candidate set the router itself resolved for the route (routing group, model_name, team,
and pattern alike), not by model-group name, so an alias, a provider-qualified spelling, a
team-public name, or a pattern route of the same group is not misread as a tier change.
An unknown origin (a removed deployment, or a forged/unauthenticated marker) is handled the
same as a cross-group one and its reasoning is stripped, so a real cross-group id and a
nonexistent id return the same response and cannot be used to enumerate deployment ids.
Unavailability within the origin's own group keeps the existing 429/503 fail-fast, so the
cooldown contract is unchanged

Resolves LIT-7195


Claude-Session: https://claude.ai/code/session_01KAumQbhzk6jdWWHFLA8Jar

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-09 11:04:11 -07:00
Animesh Kumar
6195fbf5f3 test: isolate bedrock aws tests from ambient SSL env vars
Nine cases assert the sts client is built with verify=True, but get_ssl_verify
reads SSL_CERT_FILE and SSL_VERIFY, so the argument depended on the ambient
environment. The published images set SSL_CERT_FILE, so the suite failed there
while passing in CI.

Fixes #40357
2026-09-09 23:24:12 +05:30
devin-ai-integration[bot]
c7163a80dd
perf(proxy): collapse per-worker SGR upserts into one statement per flush (#40362)
Each proxy worker flushed one Prisma upsert per active (date, category, route)
bucket every interval, so the Postgres primary saw workers x routes statements
per interval across the deployment. A flush now builds a single multi-row
INSERT ... ON CONFLICT DO UPDATE, and with use_redis_transaction_buffer on the
workers push snapshots to a Redis list that one lease-holding pod folds and
commits, so the whole deployment costs one statement per interval. The leader
keeps popping until the list is empty so a deployment wider than the dequeue
cap cannot build a backlog, and rows that fail both the commit and the Redis
re-queue fall back to the leader's own accumulator instead of being lost.

Resolves LIT-7371

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-09 10:48:06 -07:00
Clement
699ae63b2a
feat(router): support percentile-based TTFT routing (#40352)
* feat(router): support percentile-based TTFT routing

* fix(router): apply routing_strategy_args updates to the live selector

Runtime routing_strategy_args updates (config reload, update_settings)
only rebuilt the strategy selector when routing_strategy itself changed,
so a newly added ttft_percentile sat unused until the proxy restarted.

Also drops a comment that only restated the code it sat above.

Claude-Session: https://claude.ai/code/session_01PmqjhFYcUh6vA72d8W9gdB

* refactor(router): drop unreachable empty-samples guard in percentile latency

_percentile_latency is only called behind use_ttft, which already requires
a non-empty ttft sample list, so the early return was dead code and the one
line Codecov flagged as uncovered on this patch.

Claude-Session: https://claude.ai/code/session_01PmqjhFYcUh6vA72d8W9gdB

* test(router): cover the no-selector path of a routing_strategy_args update

simple-shuffle has no selector attribute to re-link, so the early return
guards a setattr with a None attribute name. Dropping the guard makes the
new test fail with "attribute name must be string, not 'NoneType'".

Claude-Session: https://claude.ai/code/session_01PmqjhFYcUh6vA72d8W9gdB

* fix(test): assert ValidationError on out-of-range ttft_percentile

pytest.raises(ValueError) tripped PT011 for being too broad. Pydantic
raises ValidationError for the gt/le constraint, so naming it satisfies
the rule and pins the assertion to the constraint under test.

Claude-Session: https://claude.ai/code/session_01PmqjhFYcUh6vA72d8W9gdB

* fix(router): drop Final from a per-deployment loop variable

basedpyright rejects "A Final variable cannot be assigned within a loop",
which pushed reportGeneralTypeIssues one over its budget. selected_latency
is rebound each iteration, so it matches its unannotated neighbours in the
same loop.

Claude-Session: https://claude.ai/code/session_01PmqjhFYcUh6vA72d8W9gdB

* test(router): exempt _apply_updated_routing_strategy_args from the name scan

The scan only reads test files with "router" in the filename, so it cannot
see the update_settings tests in router_strategy/test_lowest_latency.py.
Calling the private helper directly would test structure rather than
behaviour, so it joins the existing entries ignored for the same reason.

Claude-Session: https://claude.ai/code/session_01PmqjhFYcUh6vA72d8W9gdB
2026-09-09 10:47:34 -07:00
devin-ai-integration[bot]
7c6e33ef70
fix(proxy): ignore team_id="" on /key/update so team-less keys can be updated and imported (#40421)
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-09 10:38:26 -07:00
devin-ai-integration[bot]
996ee5635a
perf(proxy): pipeline spend counter increments into one Redis call per request (#40371)
* perf(proxy): pipeline spend counter increments into one redis call

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

* fix(proxy): apply surviving spend increments before raising scope error

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

* style(proxy): ruff format spend counter helpers

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

* fix(proxy): settle inner spend counter gathers and fall back per key on pipeline failure

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

* fix(proxy): suppress BLE001 on pipeline fallback catch

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

* fix(proxy): invalidate all batched spend counters on pipeline failure

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

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-09 10:35:50 -07:00
joshua-berri
ea0851de79
Merge pull request #40359 from BerriAI/litellm_fix_mcp_connection_errors_31318
fix(mcp): surface connection failures across transports
2026-09-09 10:22:25 -07:00
ryan-crabbe-berri
ff2f122846
Merge pull request #40174 from BerriAI/litellm_cost_estimate_cache_tokens
feat(proxy): price cache and reasoning tokens in /cost/estimate
2026-09-09 10:18:59 -07:00
ryan-crabbe-berri
1763aeff62
Merge pull request #40303 from mubashir1osmani/litellm_fix_delete_passthrough_ui
fix(ui): repair pass-through delete confirm dialog and disable delete for config endpoints
2026-09-09 10:15:38 -07:00
ryan-crabbe-berri
53d23b90ee Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_cost_estimate_cache_tokens
# Conflicts:
#	litellm/litellm_core_utils/litellm_logging.py
2026-09-09 10:05:48 -07:00
kerry-berri
bfcc6404d3
Merge pull request #40350 from BerriAI/litellm_cost_map_background_retries
fix(cost-map): keep first fetch blocking, run retries in background
2026-09-09 09:59:07 -07:00
ryan-crabbe-berri
36f3ca95d8 fix(proxy): report /cost/estimate rates from the call that billed them
The estimate looked the reported per-token rates up a second time, with the
provider this endpoint resolved rather than the one completion_cost infers.
The provider decides whether a token tier threshold is inclusive, so an
unrouted xai model sitting exactly on 200k billed at the tier rate and
reported the base rate, half of it.

completion_cost now hands back the rates its own lines were billed at, and
the endpoint reports those.

Claude-Session: https://claude.ai/code/session_01RLKy5DMi3XCBUJ37WzfNi1
2026-09-09 09:57:38 -07:00
yujonglee
1183b2abc6
fix(integrations): pass original request object to post-call guardrail hooks (#40414) 2026-09-09 09:46:37 -07:00
Joshua Valluru
dbf9490229 fix(mcp): expose shared SDK timeout normalization 2026-09-09 09:44:14 -07:00
Mateo Wang
9b6c7c8bf0
Apply suggestion from @greptile-apps[bot]
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-09-09 09:17:14 -07:00
ryan-crabbe-berri
e8140eb269
Merge pull request #39985 from BerriAI/litellm_lit_5858_jwt_team_grants
fix(proxy): apply team model aliases on the JWT auth path
2026-09-09 09:09:01 -07:00
ryan-crabbe-berri
360fa65631 Merge branch 'litellm_internal_staging' into litellm_lit_5858_jwt_team_grants
Claude-Session: https://claude.ai/code/session_01Hn5E8Jz1LjGLFyiYxBRcBW
2026-09-09 08:58:50 -07:00
Joshua Valluru
a1588c2602 fix(mcp): preserve stream failures across transports 2026-09-09 08:28:18 -07:00
Joshua Valluru
0225a16f48 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_mcp_connection_errors_31318 2026-09-09 08:00:01 -07:00
yuneng-jiang
47b15ffb67
Merge pull request #40383 from BerriAI/litellm_revert_40047
revert(model-management): roll back #40047
2026-09-09 00:17:17 -07:00
Yuneng Jiang
9d90a54491
revert(model-management): roll back #40047
This reverts commit e8e3172d7d

Restore the previous model update and router cost registration behavior while pricing compatibility is investigated
2026-09-09 00:06:26 -07:00
devin-ai-integration[bot]
ef3a3c16ae
feat(guardrails): map each guardrail scan id to its guardrail, stage and provider (#40327)
* feat(guardrails): map each guardrail scan id to its guardrail, stage and provider

Adds the x-litellm-guardrail-scan-metadata response header, a JSON list of
{guardrail, stage, provider, scan_id} entries, next to the existing
comma-separated x-litellm-guardrail-scan-id header. Prisma AIRS records the
execution stage for every scan and OpenAI Moderation now records its
moderation id too. The new metadata key is internal: client-supplied values
are stripped and it is exposed through the UI CORS allow list.

Resolves LIT-6018

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

* feat(guardrails): cap the scan metadata response header at a configurable length

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

* refactor(guardrails): hardcode the scan metadata header cap

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-09-08 23:32:31 -07:00
yuneng-jiang
e8e3172d7d
fix(model-management): honor an explicit null as a clear on model update (#40047)
* fix(model-management): honor an explicit null as a clear on model update

PATCH /model/{model_id}/update merged the patch with exclude_none and then
popped explicit nulls only for the mirrored pricing fields, so a null sent for
max_input_tokens, mode, supports_vision or any other key was dropped and a value
pinned by an earlier save could never be removed.

The route now follows JSON Merge Patch over both blobs: a key absent from the
body is unchanged, a key sent as null is removed from the stored row, and a key
sent with a value is set. Ownership and identity keys keep ignoring a null, as
do the fields the stored models require, since clearing one writes a row no
reload can rebuild. Mirrored pricing keys still clear from both blobs.

Clearing a price also needed the router to stop merging a deployment's cost-map
entry onto its previous registration, which left the old rate in place and kept
billing at a price the deployment no longer carried.

Adds a create, read, partial-update, clear, enforce, delete lifecycle e2e that
reads back on every replica, and a harness helper for that read-back.

* fix(router): keep a deployment id that names a real model from evicting its catalog entry

Deployments are keyed into litellm.model_cost alongside the built-in catalog, so
evicting a deployment's stale entry by id could take a real model's entry with it:
registering a deployment whose model_info.id is "gpt-4o" stripped that model's
pricing, context window and capability flags process-wide, for every other
deployment of it, until the next price-map reload.

Only evict an entry this registration owns. A colliding id keeps the previous
merge, which pollutes the catalog entry rather than emptying it.

Also pins the Admin UI round trip: the model edit form echoes the whole /model/info
row back on save, and that read reports every key the deployment never stored as an
explicit null, so the clear path has to leave those keys alone.

* fix(router): decide cost-map eviction by what this registrar created

The previous guard read a catalog entry off `litellm_provider`, so a deployment
that declares its own provider in model_info was treated as one and kept billing
at a price it no longer carried. It also only held for a single registration: a
second one under a colliding id saw the id the first merge left behind and
evicted the catalog entry anyway.

Track the cost-map keys this registrar creates instead. A key it created is
evicted before re-registration; one it did not is left to merge, which is what a
deployment id colliding with a catalog model name needs.

Also folds the required-fields comment into the docstring that already gives the
reason.

* fix(router): release a deployment's cost-map key when it is deleted

The ownership ledger only grew. A deleted deployment kept its claim, so if a
later catalog refresh started publishing a model under that same name, the next
registration would treat the catalog entry as the deployment's own and evict it.

Deleting a deployment now gives the key back, which also stops the ledger
growing for the life of the process.

* fix(router): hold a cost-map key while another live router still serves it

The claim is process-wide but the release was per-deletion, so with two routers
serving one deployment id, the first deletion put the survivor back on merging
and the price it had just cleared would keep billing.

Release the key only once no live router still serves that id.

* fix(router): register a router in the live set when it gains a deployment

_live_routers was only joined when a router was constructed with a model_list,
but a router built empty is populated through add_deployment, and the empty
branch exists for exactly that. Such a router was invisible to the live-router
scan, so deleting the deployment from another router released the shared
cost-map key while it was still serving that id.

Joining the set where a deployment enters the list covers every path, and it
also lets a price reload rebuild what a dynamically built router serves.

* fix(e2e): read the stored model row from the control plane, not each gateway

The lifecycle suite polled /model/info on every URL in PROXY_REPLICA_URLS. Those
URLs are the stack's gateways, and gateway/routes/allowlist.py trims them to the
LLM data-plane surface, so /model/info answers only on the backend and 404s on
every replica. All five tests failed at their first read-back in CI while passing
against a monolith, where one process serves both planes.

The stored row has one answer behind it, so it is read through the shared
transport, which routes control-plane paths to the backend. What every gateway
must agree on is which models it serves, so the create and delete steps poll
/v1/models per replica instead, a route the gateway does serve.
read_back_everywhere now rejects a control-plane path outright rather than
timing out on it.

Two things surfaced behind that. /public/ was missing from the transport's
control-plane prefixes, so model_cost_map() was routed to a gateway and 404'd,
and the billing steps needed a data-plane wait: a PATCH lands on the backend and
each gateway picks it up on its own config reload, measured here at 12-24s, so
they now drive calls until the new rate reaches the spend row and let the
deadline fail them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C1S92J8gSxxKVe1JBzxWBF

* test(models): keep polling outcomes immutable and document shared ownership

* test: validate opaque stream IDs and hide log-reader credentials

* test: isolate auto-router scenarios and clean partial setup

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 06:10:18 +00:00
yuneng-jiang
802e526cf9
Merge pull request #40161 from BerriAI/litellm_batch_e2e_cleanup
fix(batches): clean up E2E resources across providers
2026-09-08 22:53:48 -07:00
yuneng-jiang
36bd7f1138
fix(mcp): honor an explicit null on toolset update, cover MCP lifecycle e2e (#40022)
* fix(mcp): honor an explicit null on toolset update, cover MCP lifecycle e2e

PUT /v1/mcp/toolset dumped its payload with exclude_none, so a field sent as
null looked exactly like one the caller left out and the stored value
survived. An admin could not clear a toolset's description: the save reported
success and the old text came straight back. It now dumps with exclude_unset,
so absent keeps and null clears, which is what PUT /v1/mcp/server already did.
A null tools list clears the selection to empty, and a null toolset_name is
ignored because a toolset always has a name.

Adds create, read, partial-update, clear and delete e2e coverage for MCP
servers and toolsets, with every read-back polled on every replica so an edit
that lands on one replica and not another fails the test, plus an enforcement
test proving a key granted a toolset lists exactly that toolset's tools
against the real Datadog upstream.

* fix(e2e): refuse a read-back that no replica serves

A read-back over an empty replica mapping satisfied every predicate and
returned as if it had converged, so it would have asserted nothing and
passed. No wiring can produce that today, since the replica list always
falls back to at least one URL, but a helper whose whole job is proving a
write reached every replica should not have a shape that passes vacuously.

* fix(mcp): keep a null tools list a no-op on toolset update

Treating a null tools list as a clear meant an existing client that sends
tools=null during a partial update, meaning "leave the selection alone",
silently lost every tool the toolset grants. That is a permission surface,
so the quiet version of it is the worst version.

A toolset always has a tool list, the same way it always has a name, so a
null on either is now a no-op. Emptying the selection is an explicit [],
which cannot be confused with a field the caller left out, and which is
what the dashboard already sends.

* fix(e2e): keep MCP admin routes on the data plane

/v1/mcp/* is a lazily mounted feature, so a gateway registers it on the first
matching request, which happens after the startup route trim that drops
management endpoints. Routing it to the control plane therefore sent every MCP
call to the one backend process: the new lifecycle read-backs proved a single
process rather than every replica, and mcp_client's await_registered barrier
waited on a registry that does not serve the tools/list call it guards, so the
existing MCP suites polled a gateway that had not synced yet until poll_timeout

Verified against a two-gateway split stack (backend on 4001, gateways on 4010
and 4011, one postgres): both gateways answer /v1/mcp/server and /v1/mcp/toolset,
and each served 6 server reads and 7 toolset reads over the run

* fix(e2e): grant the toolset by the tool's own name, not the wire name

tools/list serves a tool as <prefix><tool_name>, but a toolset grants by the
tool's own name: resolve_toolset_permissions reads toolset.tools[].tool_name
straight through, and the prefix is added on the way out. The test built the
toolset from the names tools/list reported, so the grant matched nothing, the
scoped key listed no tools, and await_tools ran out its whole poll_timeout
before failing

Measure the prefix off search_datadog_logs, whose own name is known, rather than
guessing it from the alias, since the proxy can be configured to prefix with a
short server id instead. The expectation compared against tools/list stays in
wire names; only what the toolset stores crosses back

* test(mcp): build immutable lifecycle updates and replica results

* test: validate opaque stream IDs and hide log-reader credentials

* test: isolate auto-router scenarios and clean partial setup

* test: honor Datadog search rate-limit reset headers

* test: share the Datadog read-back deadline across retries

* test: preserve captured MCP toolset update fields
2026-09-08 22:50:13 -07:00
yuneng-jiang
b3151073d2
test(e2e/ui): cover key budget window, non-admin model scope edit, and key blocking (#40027)
* test(e2e/ui): cover key budget window, non-admin model scope edit, and key blocking

Three Playwright specs for the Virtual Keys flows customers hit most, each
reading its result back through /key/info and /v1/chat/completions rather
than trusting the toast:

- a monthly spend cap and reset window set through Edit Settings, surviving
  a reload, with clearing the window leaving the cap in place
- a team member narrowing their own team key's models, and the proxy
  refusing the model they dropped
- blocking a key from its detail page, then unblocking it

Each test owns the key it edits and deletes it on teardown, so retries and
--repeat-each never run out of fixtures.

* test(e2e/ui): tighten virtual key specs from review feedback

Replace the mutable suite-level key state with a Playwright fixture, so the
alias and token are never reassigned and cleanup stays tied to the test.

Assert /key/delete succeeded instead of discarding the response, so a failed
cleanup surfaces rather than leaving rows behind.

Drop the explanatory JSDoc the repo's comment policy disallows, keeping only
the one line explaining why Date.now() alone is not unique enough.

Type the master-key POST helper against a real guard instead of casting to
Record<string, any>.

Assert the unblocked key is served with a 200, not just the response text,
and that clearing the reset window also clears budget_reset_at.

* test(ui): assert the team response through Playwright
2026-09-08 22:49:54 -07:00
yuneng-jiang
0721163cac
test(e2e/ui): cover team-scoped model visibility, re-editing litellm params, and model health checks (#40039)
* test(e2e/ui): cover team-scoped model visibility, re-editing litellm params, and model health checks

Three Models and Endpoints flows had no end-to-end coverage, and all three
keep coming back as bug reports.

modelsByTeam walks an internal user through the Current team control and
asserts the table lists exactly what each team grants. It creates one
deployment that belongs to no team, proves that deployment is visible under
Personal, then proves it is absent under both seeded teams, so an empty
table cannot pass the same assertions.

editLitellmParams adds a temperature and a custom pair to a deployment,
saves, then re-edits the temperature and drops the custom pair. It checks
both update request bodies, polls the stored deployment until the new
temperature is there, reloads the page to confirm the second save is what
renders, and sends one chat completion to prove the deployment still serves.

modelHealthStatus runs the health check on a reachable deployment and on one
pointed at a dead port, asserts the healthy and unhealthy cells and the two
detail dialogs, and reloads to confirm both statuses are stored.

Every deployment these specs create carries a unique name and is deleted in
afterEach, including on the failure path.

* test(e2e/ui): find health rows across every page of the health table

The health table pages server-side at 50 rows with no search box, so on a
proxy carrying more deployments than that the two deployments the spec
creates can land on a later page and the lookup finds nothing.

Row lookups now walk the pages, using the table's own page indicator to
know when to advance and when to wrap back to the first page.

* test(e2e/ui): build the created deployment ids without mutating the array

* test(ui): scope model deployments to Playwright fixtures
2026-09-08 22:49:40 -07:00
kerry
884f90c727 refactor(cost-map): inline background retry, trim tests
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-09 05:15:53 +00:00
Joshua Valluru
7b6ef9206e test(mcp): cover server notifications during tool listing 2026-09-08 22:14:47 -07:00
Joshua Valluru
15392e7b3a fix(mcp): surface connection test failures safely 2026-09-08 21:26:04 -07:00
kerry
9a721abf0d test(cost-map): clear LITELLM_LOCAL_MODEL_COST_MAP in register_model url test
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-09 03:50:49 +00:00
Mateo Wang
ee7c7e14f3
Merge pull request #40189 from BerriAI/litellm_lit_3157_azure_ai_catalog_models
fix(azure_ai): price seven Foundry catalog names and charge the model router fee once
2026-09-08 20:08:40 -07:00
kerry
4179f086e0 refactor(cost-map): share local-fallback and remote-accept paths
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-09 02:58:51 +00:00
tin-berri
902dd7b2b6
fix(mcp): log proxy tool dispatch exceptions (#40351) 2026-09-08 19:56:21 -07:00
kerry
536a85b429 fix(cost-map): keep register_model url fetch to a single attempt
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-09 02:48:17 +00:00
kerry
aa0a9ab3ea refactor(cost-map): drop initial_outcome flag from retry loop
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-09 02:29:56 +00:00
kerry
7d52832692 merge: litellm_internal_staging into litellm_cost_map_background_retries
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-09 02:17:30 +00:00
Mateo Wang
24ef3ec63b
Merge pull request #37781 from ZXT-zjbiliy/fix/build-base-response-empty-choices
fix(stream_chunk_builder): guard empty choices and missing role in build_base_response
2026-09-08 19:14:03 -07:00
Mateo Wang
f8e456d105
Merge pull request #40275 from BerriAI/litellm_lit6852_spend_attribution
fix(spend-tracking): recover key alias for session tokens from spend logs
2026-09-08 19:05:43 -07:00
kerry
0086b62b45 fix(cost-map): keep first fetch blocking, run retries in background
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-09 02:05:08 +00:00