Commit graph

45730 commits

Author SHA1 Message Date
Mateo Wang
0f19b5b9ab
Merge pull request #37361 from sytianhe/litellm_spend_log_timestamps
feat(spend-logs): add lifecycle timestamps
2026-08-19 13:29:49 -07:00
ryan-crabbe-berri
73e7105e60
fix(ui): restore tab strip styling and panel persistence lost in the shadcn migration (#37403)
Tremor's TabList defaulted to the underlined `line` variant and its TabPanel
rendered every panel, hiding the inactive ones with a class. The shadcn
TabsList defaults to a segmented pill and Base UI's TabsPanel unmounts a
hidden panel unless it carries `keepMounted`, so the conversion waves quietly
changed both on the pages that took tremor's defaults.

Restores the underline on the nine strips whose tremor markup carried no
`variant`, leaving the ones that were explicitly `variant="solid"` as pills,
and puts `keepMounted` back on the thirteen files whose panels used to stay
mounted, so filters, scroll position and in-progress input survive a tab
switch again.

Seeding the old usage page's activity state properly comes with it: it was
cast from `{}`, so the panel crashed on `data.length` the moment it mounted
before its fetch resolved, which only stayed hidden while the panel was
unmounted until first opened.
2026-08-19 13:28:20 -07:00
mateo-berri
6acf970009 chore(ui): regenerate dashboard API types for spend log timestamps 2026-08-19 12:41:52 -07:00
yassin
49dca4996c fix(search): point the AgentCore region error at AWS_DEFAULT_REGION
boto3's session resolution ignores AWS_REGION, so an operator following the
old message still hit the same failure.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-19 19:31:19 +00:00
yuneng-jiang
4bb3152cc5
test(ui): drive fields with change events where the typing is not the behaviour (#37495)
user.type dispatches one event per character and re-renders the whole form on
each one, so a test that only needs a field to hold a value pays for every
keystroke. Where the value is all the test wants, fireEvent.change sets it in
one go.

Measured against a control on the same tree: the 100 converted files went from
654s to 619s of CPU, 5.4% cheaper, while the files nobody touched drifted 1.2%
the other way. Modest, and honest about it.

Rolled out one file at a time, running each before and after and keeping the
conversion only where the file stayed green. That rejected 35 files, all cases
where the keystrokes are the behaviour: Base UI comboboxes drive their filter
from real keyboard input and ignore a raw change event, and the same goes for
autocompletes, debounces and key handlers. Those keep user.type.
2026-08-19 12:11:37 -07:00
Mateo Wang
4d100bdd89
Merge pull request #37439 from BerriAI/litellm_decrease_anys_fable_round3
chore(typing): drop 1.3k basedpyright errors across 42 Any hotspot files
2026-08-19 12:07:26 -07:00
yassin
ae18f055ee fix(search): harden AgentCore gateway trust, error and SSE handling
Refuse to SigV4-sign requests to hosts that are neither an AgentCore gateway
hostname nor AGENTCORE_GATEWAY_URL's host, match gateway hostnames on the URL
host instead of anywhere in the URL, accept the env token when api_base is a
real gateway, raise on tools/call responses with result.isError, and split
CRLF-framed SSE events.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-19 19:07:17 +00:00
mubashir1osmani
852827c6bb Merge remote-tracking branch 'berri/litellm_internal_staging' into litellm_managed_batches_observability
# Conflicts:
#	tests/test_litellm/batches/test_batch_utils.py
2026-08-19 15:06:00 -04:00
Devin AI
f80cb0d9f8 refactor(ui): drop redundant comment above guardrail mode formatter
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-19 18:33:06 +00:00
Devin AI
881aa20808 fix(ui): format tag-based guardrail mode in delete modal, playground, and policy picker
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-19 18:31:42 +00:00
Devin AI
b8680e6bae fix(ui): render tag-based guardrail mode instead of crashing guardrails page
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-19 18:22:31 +00:00
tin-berri
da7a10ebbd
fix(mcp): forward the per-server auth header on OpenAPI tool calls (#37410)
Both OpenAPI dispatch arms sourced the upstream credential only from the
deprecated global / BYOK mcp_auth_header and never from mcp_server_auth_headers,
so x-mcp-{alias}-authorization was silently dropped on spec_path servers and the
upstream API received no Authorization at all. The managed path already resolves
it through lookup_mcp_server_auth_in_headers, so the two had drifted.

_resolve_openapi_tool_auth now owns that resolution for both arms. A per-server
value is already a complete header value and is forwarded verbatim, while a BYOK
credential keeps its auth-type prefix, so the two are never conflated into
"Bearer Bearer <token>". The resolved credential is also handed to
resolve_openapi_upstream_auth, whose passthrough arm reads it through
_passthrough_token_from_mcp_auth_header and outranks the ContextVar.

server.py loses its inlined copy of the forwarded-header logic along with its
mcp_server is None guards, which are unreachable after the 503 raised above them.

Credit to the earlier analysis and approach in #33349, which this supersedes
against the current v2 credential resolver.
2026-08-19 11:15:19 -07:00
mateo-berri
19b5381e63 Merge branch 'litellm_internal_staging' of https://github.com/BerriAI/litellm into litellm_decrease_anys_fable_round3
# Conflicts:
#	ruff-strict-budget.json
#	type-discipline-budget.json
2026-08-19 10:52:48 -07:00
Mateo Wang
133e72c8fd
Merge pull request #36263 from BerriAI/litellm_replica_registry_read_through
fix(proxy): read through to the DB on registry misses so just-created models, guardrails, and agents resolve on sibling replicas
2026-08-19 10:46:35 -07:00
Mateo Wang
18d07e3a6b
Merge pull request #37432 from BerriAI/litellm_e2e_tag_denial_reason
test(e2e): pin the tag-routing denial to its actual cause
2026-08-19 10:43:27 -07:00
yuneng-jiang
481c08de4e
refactor(ui): port the MCP server forms off antd Form onto react-hook-form (#37483)
* refactor(ui): port the MCP server forms off antd Form onto react-hook-form

The MCP create and edit forms were the last antd `Form` graph in the
dashboard. antd's `onFinish` hands back only the fields mounted at submit
time, while react-hook-form with `shouldUnregister: false` hands back the
whole store, so a direct port would quietly widen every create and update
request.

All 14 files now bind through `MountedFormField`, whose mount registry
reproduces antd's mounted-only submit: both roots build their payload from
`projectMountedValues` instead of `getValues`. `mcpFormStore` carries the
rest of the FormInstance surface the two roots relied on, each piece
matched to what rc-field-form actually does rather than to what the API
name suggests: `setFieldsValue` deep-merges plain objects and writes an
explicit `undefined`, `resetFields` restores the seeded values rather than
clearing the key, and `onValuesChange` is rebuilt from a `watch`
subscription filtered to user input, carrying a single changed branch.

Two watches needed the mount gate moved rather than translated.
`MCPPermissionManagement` renders outside the transport gate that mounts
`auth_type`, so antd's watch read `undefined` there and mounted the
pass-through toggle; the effective auth type now arrives as a prop that
each root computes with exactly that gate. The edit root reads every watch
off the mounted projection for the same reason, which also stops the token
material in a saved server's credentials from reaching the tool preview.

`Form.List` becomes `useFieldArray` plus a `useMountedName` registration
for the list key itself, because antd registers a list as one field: a
per-user variable row keeps the `value` its scope hides, and an empty list
still submits `env_vars: []` instead of dropping the key.

* test(ui): cover the mount registry's unregister path on the real primitive

The existing MountedFormField suite drove a hand-written registry whose
register returned a no-op, so nothing exercised useMountRegistry's
ref-counting or the cleanup that React wires from useMountedName's effect
return value. A reviewer read that gap as a missing unregister.

These three cases drive the real hook through a gated tree: a key leaves
the submitted payload when its gate unmounts the field, a required field
that unmounts stops blocking submission, and a name held by two fields
survives one of them releasing it.

Verified by mutation: rewriting the effect body to discard the cleanup
turns the first two red, the second reporting the reviewer's exact
symptom, "expected [ 'server_name', 'token_url' ] to not include
'token_url'".

* test(ui): prove the permission panel's booleans reach the create payload

CreateMCPServer.integration.test.tsx mocks MCPPermissionManagement, so the
four booleans createServerPayload writes were invisible to every existing
create-side test. vi.mock is file-scoped, so rendering the real panel needs
its own file.

Four cases, each killed by a different mutation:

  unbind allow_all_keys                -> "sends allow_all_keys true"
  unbind available_on_public_internet  -> "sends the panel's defaults"
  invertedSwitchControl -> switchControl -> "sends ... false when the
                                            operator restricts"
  isOAuth2 gate forced open            -> "omits delegate_auth_to_upstream"

A payload assertion expecting false cannot detect an unbound field, since
Boolean(undefined) is false too, so the two cases carrying unbinding
detection are the ones asserting true. The other two are pinned by the
switch-inversion and mount-gate mutations instead.
2026-08-19 10:26:20 -07:00
Mateo Wang
b402fea745
Merge pull request #37451 from BerriAI/litellm_isolate_proxy_url_env_in_tests
fix(tests): keep a host PROXY_BASE_URL out of request-derived URL tests
2026-08-19 10:06:26 -07:00
Devin AI
dcd8bb3f38 test(model_prices): update DeepSeek V4 pricing expectations
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-19 13:47:44 +00:00
Devin AI
5a0a8ffafe fix(model_prices): correct gemini and deepseek pricing and add deprecation dates
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-19 13:17:15 +00:00
mateo-berri
5c6391d7d2 refactor(batches): parameterize the batch output Mapping annotations 2026-08-19 02:15:22 -07:00
mateo-berri
f8a23aab09 fix: gate guardrail read-through to active rows and serialize it with the reload reconcile 2026-08-19 01:46:33 -07:00
mateo-berri
1dbc7177bb chore(typing): close budget headroom left after merging staging 2026-08-19 01:03:54 -07:00
mateo-berri
882efc18ac Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_e2e_tag_denial_reason 2026-08-19 01:03:52 -07:00
mateo-berri
967970042a Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_decrease_anys_fable_round3 2026-08-19 00:59:46 -07:00
mateo-berri
9ef6a8826d test: trim the PROXY_BASE_URL fixture and regression docstrings 2026-08-19 00:56:37 -07:00
mateo-berri
e9c01da233 test: drop unused imports in the direct restore test 2026-08-19 00:54:46 -07:00
mateo-berri
43389e987a test: call _restore_deployment_after_failed_upsert directly for the router coverage gate 2026-08-19 00:54:08 -07:00
mateo-berri
790d645d34 Merge branch 'litellm_internal_staging' of https://github.com/BerriAI/litellm into litellm_replica_registry_read_through
# Conflicts:
#	tests/test_litellm/proxy/test_proxy_server.py
2026-08-19 00:48:03 -07:00
yuneng-jiang
c696fdfb05
fix(ui): gate the pass-through guardrail field inputs when the section is disabled (#37435)
PassThroughGuardrailsSection threads its `disabled` prop to the guardrail
selector and to all three quick-add buttons, but not to the two `TagsInput`
controls for request and response field targeting. Every other prop crossed,
so the section greys out while a user can still type field names straight
into both tag inputs.

Latent rather than live: neither call site passes `disabled` today, so the
prop is currently dead across the tree. This arms the gate for whoever passes
it first.

`TagsInput` already honours `disabled` end to end, measured rather than read:
with it set, the input carries a real disabled attribute and all three commit
paths, token separator, Enter and blur, are inert. So the fix belongs at the
call sites and the primitive needs no change.

The new test drives the control rather than asserting an attribute, since a
Base UI control can paint a disabled state while still accepting input. The
enabled cases are the liveness gate: they type the same text through the same
gesture and assert it commits, so the disabled cases cannot pass vacuously.
2026-08-19 07:00:33 +00:00
yuneng-jiang
0700b1e54e
fix(ui): rebuild nested and list paths in the mounted-field projection (#37450)
* refactor(ui): extract the MCP server edit save payload into a pure builder

`handleSave` built the update payload inline across 276 lines, spreading
`...restValues` straight off a mounted-only `onFinish`. That makes the payload a
function of which fields happen to be mounted, and it leaves no seam to test the
shape without rendering the whole edit form.

Move the payload construction into `editServerPayload.ts` as
`buildEditServerPayload(values, ui)`, a pure function over the submitted values
plus the nine pieces of component state the handler reads. Failures become values
rather than early returns with a toast: the six error branches are a tagged union
that `editPayloadErrorMessage` maps back to the exact strings shown today, via an
exhaustive switch. `handleSave` keeps the network call, the OAuth token
persistence and its own try/catch.

This is a move, not a rewrite. To prove that, `editServerPayload.differential.test.ts`
holds a baseline machine-extracted from the pre-refactor function body by line
range, with the failure branches converted by exact string replacement. The
generator refuses to emit unless the slice is still present verbatim in the
source, every conversion matches exactly once, no toast call survives, and a
deliberately corrupted probe still trips that check. 59 scenarios run both
implementations and compare the payload object, its key order, and its serialised
bytes, so a re-ordering that leaves values untouched is caught too.

The duplicate local `AUTH_TYPES_REQUIRING_CREDENTIALS` is dropped in favour of the
identical exported list, and `reduceStaticHeaders` is shared with the create side.
Both were verified equal before reuse.

Behaviour is unchanged. The 466 pre-existing tests in the directory pass unedited.

* refactor(ui): type the MCP edit payload builder instead of Record<string, any>

The extraction created a new public signature, so it should carry a real
contract. buildEditServerPayload now takes EditServerFormValues and returns
EditServerPayload, both declaring every field the builder actually reads and
writes, with an unknown-valued index signature for the keys the form passes
straight through. handleSave is annotated too, so antd's untyped onFinish
value is narrowed once at the boundary rather than travelling as any.

Fields that arrive from the store with their own runtime validation
(static_headers, env_vars, credentials) stay unknown rather than being given a
narrower declared type the form does not actually guarantee. Values are not
run through a parser: the payload's serialised key order is part of the
contract this module exists to hold, and rebuilding the object would reorder
it.

The credentials assignment moves from two post-hoc mutations to a single
resolved entry, which keeps the payload readonly end to end and lands the key
in the same position in all four branches.

Behaviour is unchanged. The 59 differential scenarios still match the frozen
pre-extraction body on object, Object.keys order and JSON.stringify bytes, and
the mcp-servers suite is 525/525 across all 30 files. Three tsc probes confirm
the new types have teeth: a wrong payload assignment, a misspelled field read
and an invalid value each fail the type check.

* feat(ui): add mounted-field projections for the MCP server form graph

antd's onFinish reports exactly the fields mounted at submit time, and both MCP
server payload builders spread that object straight through. react-hook-form
with shouldUnregister false hands back the whole store instead, so a port needs
the mount set written out explicitly before any JSX moves.

This adds mountedEditFieldNames / mountedCreateFieldNames as pure functions over
the form values, plus the projections that apply them, covering all 22 gates
across the graph's 89 named bindings. No JSX changes, nothing imports them yet.

Three behaviours are probed against antd 5.29.3 rather than assumed, and the
tests pin all three:

- a mounted-but-unset field is EMITTED as a key holding undefined, at the root
  and inside credentials, so the projection emits rather than omits
- a Form.List row is NOT projected down to its mounted sub-fields, so env_vars
  rows pass through whole; filtering them would drop per-user values
- the two roots disagree on more than StdioConfiguration: edit gates url on a
  deny-list while create uses an allow-list, and create additionally gates the
  whole auth section on a non-empty transport

* refactor(ui): port the create key form off antd Form onto react-hook-form

antd hands onFinish exactly the fields mounted at submit time, so a collapsed
section contributes nothing to the request while the values typed into it
survive for re-expansion. react-hook-form reaches only one of those two
behaviours per shouldUnregister setting, so the store is kept intact and
projected down to the mounted set through an explicit mount registry.

MountedFormField carries the rest of the Form.Item contract the payload
depends on: defaults taken from each field's own declaration rather than a
blanket empty value, and help text that replaces the rule message instead of
sitting beside it.

The 60-case submit differential runs unedited against the port, joined by
cases for the writers outside the submit path, mounted-set validation, Enter
to submit, and switch coercion.

* test(ui): pin the mounted-field sets by membership, not array order

Two credential assertions compared the returned array with toStrictEqual
against a literal in source order, so they failed when two names were
swapped inside the source array even though the projected payload was
unchanged. Key order is not observable in the payload, so those two
assertions rejected a refactor that changes nothing a caller can see.

Route both through the same sorted() helper the other fifteen set
assertions already use. Membership keeps its teeth: deleting any one of
the eighty emitted field names still fails the suite, while reordering
two of them now passes.

* docs(ui): state the mounted projection's static-name limit at its export

The registry counts by name and the projection emits flat keys, so a
Form.List row and its per-row sub-fields, whose names are generated at
runtime, are never in the mounted set and go missing from the payload.
That is silent and it is correct for every static field around it, so the
contract belongs where the next consumer reads it.

* refactor(ui): cut the mounted field's explanatory comments to the contract limit

The mechanism the projection uses and the reason a helped field hides its
rule message are both derivable from the code, so they belong in the pull
request rather than in two places. What survives is the one thing no reader
can derive: that a runtime-generated name is silently absent from the payload.

* refactor(ui): drop the doc comment from MountedFormField

The static-name constraint it described moves to the PR description, where
it is not a second place to keep in sync with the code.

* fix(ui): rebuild nested and list paths in the mounted-field projection

projectMountedValues emitted one flat key per registered name, so a field
registered under a dotted path produced a literal "credentials.client_id"
key instead of a nested credentials object, and a field-array row produced
"env_vars.0.name" instead of a row.

Both are silent. buildEditServerPayload destructures credentials and passes
it to buildCredentials, which returns undefined for a non-object, so every
credential drops out of the payload while the code compiles and the existing
suites pass. reduceStaticHeaders loses its rows the same way.

Split each registered name on "." and rebuild the value, treating a numeric
segment as an array index so field-array rows come back as arrays rather
than objects keyed by digits. Nested credentials and both field-array sites
are the same defect and take the same fix.

* fix(ui): make mounted-field nesting opt-in via array names

The first cut split every registered name on ".", which diverges from antd.
antd's getNamePath is toArray, so a string name is a one-element path and
"a.b" is stored as a literal flat key; only an array name nests.

check_openapi_schema registers names taken from a live /openapi.json at
runtime, so the field set is an input rather than source. A spec property
containing a dot would have silently nested under the unconditional split
and changed that payload with nothing failing.

Accept string | readonly string[]. An array nests, with a numeric segment
as an array index, which is what the credential paths and the field-array
rows already use. A string stays one literal key. The registry keys by the
joined path but projects from the original shape, so the two cannot drift.
2026-08-18 23:44:12 -07:00
yuneng-jiang
14c05628b4
refactor(ui): host KeyLifecycleSettings in react-hook-form instead of antd Form (#37449)
The suppression said exercising this component requires a real antd Form.
That stopped being true once the create key form began driving it through
a bare mounted field, so the harness and the suppression go together.

Two of the auto-rotation cases were racing the Base UI select popup all
along: findByText resolves as soon as the option lands, while the popup
still carries pointer-events none until its open transition settles. The
antd harness happened to hide it, at 0 failures in 6 runs against 1 in 6
for plain state and 5 in 6 under a Controller, since each extra render is
another chance to catch the popup mid-transition. Both now take the same
PointerEventsCheckLevel.Never their sibling case already used.
2026-08-18 23:44:03 -07:00
yuneng-jiang
5a899f596b
refactor(ui): port the add model form off antd Form onto react-hook-form (#37446)
* fix(ui): restore the cache control Role and Index field hints

The add_model cache control editor lost both field hints when it moved off
antd Form.Item in #37392. "LiteLLM will mark all messages of this role as
cacheable" and "(Optional) If set litellm will mark the message at this index
as cacheable" went with the Form.Item tooltip props and neither string exists
in dashboard source any more. The Index hint was the only thing telling a user
that field is optional, so this is lost information rather than styling.

Both come back as shadcn tooltips beside their labels, matching how the
surviving switch-level hint is already rendered.

Also adds the payload characterization net this graph did not have. Before
this commit the seven suites over add_model and model_add held 37 cases, no
antd module mock, and zero toStrictEqual, so nothing pinned the submit
payload. AddModelPanel.integration.test.tsx drives the real panel, the real
antd store and the real prepareModelAddRequest, and asserts the object handed
to modelCreateCall.

It pins the distinctions only a strict assertion can see: litellm_credential_name
arrives as null from its initialValue while api_key, api_base, mode and
access_groups arrive as undefined, and team_id is absent entirely until the
Team-BYOK switch mounts it. It also pins the mount gate in both directions,
since a collapsed Advanced Settings drops both its keys and anything typed
into it while re-expanding restores them, and the empty-string skip, since a
cleared api_base must vanish rather than arrive as "".

Every fixture was captured from the running component rather than written by
hand. A 12-mutation battery over the bindings, the empty-string skip, the two
required rules and an added keepMounted all go red, each run gated on having
executed the expected case count.

* refactor(ui): port the add model form off antd Form onto react-hook-form

The Add Model form graph is shared by three antd hosts, so it only moves as
one piece: AddModelPanel, LlmCredentialsPanel and CredentialModal all mount
the same children. Form and Form.Item are replaced everywhere, and every
widget inside them is left alone, so the change is the binding layer only.

antd submits the mounted fields, react-hook-form submits its whole store. A
shared mount registry keeps that difference from reaching the request: each
field registers on mount, and the panel projects the store down to the
registered names before it builds the payload. shouldUnregister would have
been the other option, but it drops a collapsed section's typed values, so
re-expanding Advanced Settings would come back empty.

The antd rules modules are reused as-is through a thin validator adapter, so
the messages stay in one place rather than being reworded per field.

Advanced Settings held a Form.useForm() instance in a component that renders
no Form, which made ten imperative calls dead. They are removed rather than
translated, and the three behaviours they looked like they drove were checked
against the antd original first: invalid LiteLLM Params still blocks submit,
the pass-through toggle still leaves LiteLLM Params empty, and turning custom
pricing off then on still keeps the typed cost.

The existing 14 case payload net runs unedited against the port.

* test(ui): pin the three add model behaviours the dead form instance looked like it drove

Advanced Settings used to hold a form instance it never rendered, and the ten
imperative calls against it were dead. The inherited payload net covered none
of the three behaviours those calls appeared to own, so removing them looked
riskier than it was. These cases characterise what the antd original actually
did, checked against it before the port.

Invalid LiteLLM Params blocks the submit, which also closes the one mutation
the inherited net could not kill: dropping the JSON rule left all 14 green.
2026-08-18 23:43:54 -07:00
mateo-berri
69133f6baa fix(tests): keep a host PROXY_BASE_URL out of request-derived URL tests
The proxy resolves its own public origin from PROXY_BASE_URL before it
looks at anything on the request, so a developer who has that set for
their own deployment watched 65 OAuth discovery, redirect_uri, and client
registration cases fail against an origin no test ever asked for

An autouse fixture now clears it for every unit test, matching the host
AWS config isolation that already sits beside it, and the tests that do
exercise a configured public origin keep setting it in their own body
2026-08-19 06:21:19 +00:00
yuneng-jiang
963c7fb0d4
refactor(ui): port the create key form off antd Form onto react-hook-form (#37442)
* refactor(ui): port the create key form off antd Form onto react-hook-form

antd hands onFinish exactly the fields mounted at submit time, so a collapsed
section contributes nothing to the request while the values typed into it
survive for re-expansion. react-hook-form reaches only one of those two
behaviours per shouldUnregister setting, so the store is kept intact and
projected down to the mounted set through an explicit mount registry.

MountedFormField carries the rest of the Form.Item contract the payload
depends on: defaults taken from each field's own declaration rather than a
blanket empty value, and help text that replaces the rule message instead of
sitting beside it.

The 60-case submit differential runs unedited against the port, joined by
cases for the writers outside the submit path, mounted-set validation, Enter
to submit, and switch coercion.

* docs(ui): state the mounted projection's static-name limit at its export

The registry counts by name and the projection emits flat keys, so a
Form.List row and its per-row sub-fields, whose names are generated at
runtime, are never in the mounted set and go missing from the payload.
That is silent and it is correct for every static field around it, so the
contract belongs where the next consumer reads it.

* refactor(ui): cut the mounted field's explanatory comments to the contract limit

The mechanism the projection uses and the reason a helped field hides its
rule message are both derivable from the code, so they belong in the pull
request rather than in two places. What survives is the one thing no reader
can derive: that a runtime-generated name is silently absent from the payload.

* refactor(ui): drop the doc comment from MountedFormField

The static-name constraint it described moves to the PR description, where
it is not a second place to keep in sync with the code.
2026-08-18 23:16:35 -07:00
mateo-berri
f614f039c5 fix(ui): drop stale user search answers so Enter commits the current match
The Add Member modal and the Create Key owner picker both search users
server-side on a 300ms debounce with nothing sequencing the requests, so a
slow answer to an earlier, shorter search can land after the current one and
replace the list. With the first row highlighted at all times, Enter then
commits whoever sits on top of that abandoned batch, ordered newest account
first rather than best match.

Each search now takes a sequence number and only the newest one is allowed to
reach the option list or clear the spinner.
2026-08-18 23:00:47 -07:00
Mateo Wang
61029814ab
Merge pull request #37444 from BerriAI/litellm_fix_vertex_batch_cost_assertion
test: derive vertex batch cost expectation from the cost map
2026-08-18 22:58:02 -07:00
yuneng-jiang
8bb8525047
refactor(ui): extract the MCP server edit save payload into a pure builder (#37436)
* refactor(ui): extract the MCP server edit save payload into a pure builder

`handleSave` built the update payload inline across 276 lines, spreading
`...restValues` straight off a mounted-only `onFinish`. That makes the payload a
function of which fields happen to be mounted, and it leaves no seam to test the
shape without rendering the whole edit form.

Move the payload construction into `editServerPayload.ts` as
`buildEditServerPayload(values, ui)`, a pure function over the submitted values
plus the nine pieces of component state the handler reads. Failures become values
rather than early returns with a toast: the six error branches are a tagged union
that `editPayloadErrorMessage` maps back to the exact strings shown today, via an
exhaustive switch. `handleSave` keeps the network call, the OAuth token
persistence and its own try/catch.

This is a move, not a rewrite. To prove that, `editServerPayload.differential.test.ts`
holds a baseline machine-extracted from the pre-refactor function body by line
range, with the failure branches converted by exact string replacement. The
generator refuses to emit unless the slice is still present verbatim in the
source, every conversion matches exactly once, no toast call survives, and a
deliberately corrupted probe still trips that check. 59 scenarios run both
implementations and compare the payload object, its key order, and its serialised
bytes, so a re-ordering that leaves values untouched is caught too.

The duplicate local `AUTH_TYPES_REQUIRING_CREDENTIALS` is dropped in favour of the
identical exported list, and `reduceStaticHeaders` is shared with the create side.
Both were verified equal before reuse.

Behaviour is unchanged. The 466 pre-existing tests in the directory pass unedited.

* refactor(ui): type the MCP edit payload builder instead of Record<string, any>

The extraction created a new public signature, so it should carry a real
contract. buildEditServerPayload now takes EditServerFormValues and returns
EditServerPayload, both declaring every field the builder actually reads and
writes, with an unknown-valued index signature for the keys the form passes
straight through. handleSave is annotated too, so antd's untyped onFinish
value is narrowed once at the boundary rather than travelling as any.

Fields that arrive from the store with their own runtime validation
(static_headers, env_vars, credentials) stay unknown rather than being given a
narrower declared type the form does not actually guarantee. Values are not
run through a parser: the payload's serialised key order is part of the
contract this module exists to hold, and rebuilding the object would reorder
it.

The credentials assignment moves from two post-hoc mutations to a single
resolved entry, which keeps the payload readonly end to end and lands the key
in the same position in all four branches.

Behaviour is unchanged. The 59 differential scenarios still match the frozen
pre-extraction body on object, Object.keys order and JSON.stringify bytes, and
the mcp-servers suite is 525/525 across all 30 files. Three tsc probes confirm
the new types have teeth: a wrong payload assignment, a misspelled field read
and an invalid value each fail the type check.
2026-08-18 22:56:09 -07:00
mateo-berri
afeed48a70 fix(proxy): serialize read-through with reloads, gate db object types
The model resync now mutates the router under MODEL_RECONCILE_LOCK, and the
agent resync shares the new AGENT_RECONCILE_LOCK with the periodic agent
reload, so a reconcile built from a pre-write DB snapshot can no longer evict
or duplicate what a read-through just registered. Every resync checks
should_load_db_object for its object type, keeping read-through consistent
with what the replica is configured to load, and the a2a raise sites tag
ProxyModelNotFoundError as non-retryable so an agent miss no longer burns the
model resync budget.
2026-08-18 22:43:44 -07:00
yuneng-jiang
da2532a739
feat(ui): add mounted-field projections for the MCP server form graph (#37440)
* feat(ui): add mounted-field projections for the MCP server form graph

antd's onFinish reports exactly the fields mounted at submit time, and both MCP
server payload builders spread that object straight through. react-hook-form
with shouldUnregister false hands back the whole store instead, so a port needs
the mount set written out explicitly before any JSX moves.

This adds mountedEditFieldNames / mountedCreateFieldNames as pure functions over
the form values, plus the projections that apply them, covering all 22 gates
across the graph's 89 named bindings. No JSX changes, nothing imports them yet.

Three behaviours are probed against antd 5.29.3 rather than assumed, and the
tests pin all three:

- a mounted-but-unset field is EMITTED as a key holding undefined, at the root
  and inside credentials, so the projection emits rather than omits
- a Form.List row is NOT projected down to its mounted sub-fields, so env_vars
  rows pass through whole; filtering them would drop per-user values
- the two roots disagree on more than StdioConfiguration: edit gates url on a
  deny-list while create uses an allow-list, and create additionally gates the
  whole auth section on a non-empty transport

* test(ui): pin the mounted-field sets by membership, not array order

Two credential assertions compared the returned array with toStrictEqual
against a literal in source order, so they failed when two names were
swapped inside the source array even though the projected payload was
unchanged. Key order is not observable in the payload, so those two
assertions rejected a refactor that changes nothing a caller can see.

Route both through the same sorted() helper the other fifteen set
assertions already use. Membership keeps its teeth: deleting any one of
the eighty emitted field names still fails the suite, while reordering
two of them now passes.
2026-08-18 22:41:43 -07:00
mateo-berri
df0d8ff15e test: assert the vertex batch output rate is a real discount 2026-08-18 22:38:13 -07:00
yuneng-jiang
413fc7e517
fix(ui): restore the cache control Role and Index field hints (#37437)
* fix(ui): restore the cache control Role and Index field hints

The add_model cache control editor lost both field hints when it moved off
antd Form.Item in #37392. "LiteLLM will mark all messages of this role as
cacheable" and "(Optional) If set litellm will mark the message at this index
as cacheable" went with the Form.Item tooltip props and neither string exists
in dashboard source any more. The Index hint was the only thing telling a user
that field is optional, so this is lost information rather than styling.

Both come back as shadcn tooltips beside their labels, matching how the
surviving switch-level hint is already rendered.

Also adds the payload characterization net this graph did not have. Before
this commit the seven suites over add_model and model_add held 37 cases, no
antd module mock, and zero toStrictEqual, so nothing pinned the submit
payload. AddModelPanel.integration.test.tsx drives the real panel, the real
antd store and the real prepareModelAddRequest, and asserts the object handed
to modelCreateCall.

It pins the distinctions only a strict assertion can see: litellm_credential_name
arrives as null from its initialValue while api_key, api_base, mode and
access_groups arrive as undefined, and team_id is absent entirely until the
Team-BYOK switch mounts it. It also pins the mount gate in both directions,
since a collapsed Advanced Settings drops both its keys and anything typed
into it while re-expanding restores them, and the empty-string skip, since a
cleared api_base must vanish rather than arrive as "".

Every fixture was captured from the running component rather than written by
hand. A 12-mutation battery over the bindings, the empty-string skip, the two
required rules and an added keepMounted all go red, each run gated on having
executed the expected case count.

* refactor(ui): drop the explanatory comments from the add-model payload net

The repo bans explanatory source comments. The liveness-gate pairing the
second one described now lives in the test name instead, where a reader
deleting the paired case will actually see it.

* fix(ui): make the cache control field hints reachable without a pointer

SimpleTooltip renders its trigger as a bare span with no tabindex, so the
guidance behind it is mouse-only and the focus-visible ring classes it
already carries can never fire. The antd tooltips these hints replaced set
tabIndex null too, so this is an improvement on the original rather than a
restoration of it.

The primitive is shared by 20 files and is CLI-managed, so the trigger is
composed at the call site instead: a real button, an accessible name that
says which field it explains, and the icon marked aria-hidden.

Two tests cover it by tabbing to each trigger rather than counting keys,
so they keep working when a field is added between them. Swapping the
button for a span with role=button turns exactly those two red and leaves
the other five green.
2026-08-18 22:38:07 -07:00
yuneng-jiang
b9a267c693
refactor(ui): migrate the teams form graph off antd Form onto react-hook-form (#37417)
* test(ui): pin the teams create and update payloads before the form migration

The teams graph (Teams.tsx, TeamInfo.tsx and the MetadataKeyValueFields child they
share) is next for the antd Form to react-hook-form migration, and its submit payload
is a function of which collapsible sections the user happened to open. Nine sections
across the two files use the shadcn Collapsible, none of them passes keepMounted, and
Base UI unmounts the closed branch, so a closed section registers nothing and its keys
never reach the request body.

That matters beyond parity. /team/update reads the body with exclude_unset, so an
omitted key is never written, while an explicitly null team member budget key reaches
clear_team_member_budget_fields and nulls max_budget, budget_duration, rpm_limit and
tpm_limit on the shared budget row. antd cannot reach that today because the field is
unregistered rather than null. A port that seeds those fields or coalesces on the way
into the payload would turn a save with the section never opened into a silent clear.

The coverage that shipped with the team modal reached one of the four gating sections
on the create side and asserted key sets rather than the request body, so a null where
antd sent undefined would have passed. These cases assert both the raw payload and its
JSON round trip with toStrictEqual, which is what separates absent from null from
undefined, and they cover every gating section on both screens.

Also pinned, because each is a live behaviour a port can quietly change:

- the create path sends max_budget, tpm_limit and rpm_limit as strings, while
  team_member_budget arrives as a number through its normalize prop
- an invalid secret manager config blocks the create with its rule message suppressed
  by the item's help prop, so nothing is shown to the user
- the disable global guardrails switch is inert for a non premium user
- a value typed into a section survives collapsing and re-expanding it

Verified by adding keepMounted to all nine panels, which is the change a porter reaches
for on noticing that fields go missing: 35 of 118 went red, including every one of these
cases. The files were restored byte identical afterwards.

No production file changes here. 145 tests pass across the three files.

* refactor(ui): migrate the teams form graph off antd Form onto react-hook-form

Teams.tsx and TeamInfo.tsx were the last large antd `Form` graph in the
dashboard. Both now use `useZodForm` + `FormField`, with the shared
`MetadataKeyValueFields` child converted to a `useFieldArray`.

antd only returns the mounted registered fields from `onFinish`, so a
closed collapsible contributed no keys at all. react-hook-form keeps
unmounted values in the store (and `shouldUnregister: true` would lose
them on re-expand), so both forms project the submitted values through
the currently mounted section list before handing them to the existing
payload builders. Closed sections therefore still produce absent keys
rather than nulls, which matters at /team/update where an explicit null
clears the shared budget row.

Widgets that had no shadcn equivalent are replaced with the existing
shared ones: SearchSelect for the organization pickers, MultiSelect for
default member models, TagsInput for guardrails/policies, and a new
GuardrailsSelect for the grouped global/other guardrail dropdown.

* refactor(ui): forward the field ref to NumericalInput in the teams forms

staging turned NumericalInput into a forwardRef, so the teams graph can stop
dropping the react-hook-form ref on the floor.

* test(ui): pin the capability gate, required rules and guardrail kill switch

A mutation run over the ported teams forms found five survivors the payload
cases did not reach: the viewPolicies gate on both forms, the team name rule
on both forms, the guardrail kill switch resync, and the number coercion on a
typed model rate limit. Six cases close them.
2026-08-18 22:37:04 -07:00
Mateo Wang
559310f077
Merge pull request #37423 from BerriAI/litellm_fix_thinking_bool_crash
fix: accept bool thinking param instead of crashing with AttributeError
2026-08-18 22:30:14 -07:00
Mateo Wang
d6afe728aa
Merge pull request #37365 from BerriAI/litellm_lit_5690_failed_request_token_counts
fix(proxy): record estimated input tokens in spend logs for failed dispatched requests
2026-08-18 22:29:51 -07:00
HarryZhou
2d4e6afe1c fix(guardrails): inspect responses reasoning content and summary text 2026-08-19 12:57:43 +08:00
HarryZhou
de95372dfb fix(responses-bridge): type-safe reasoning_content assignment in merge pass 2026-08-19 12:57:43 +08:00
HarryZhou
438c1850fe fix(responses-bridge): satisfy type-discipline budget in reasoning merge 2026-08-19 12:57:43 +08:00
HarryZhou
5911124f1d fix(responses-bridge): satisfy ruff strict-rule budget in reasoning merge 2026-08-19 12:57:43 +08:00
HarryZhou
3a77556dc1 fix(responses-bridge): preserve reasoning merge order when assistant already has reasoning_content 2026-08-19 12:57:43 +08:00
HarryZhou
b6ee13803d fix(responses-bridge): preserve reasoning input items as reasoning_content 2026-08-19 12:57:42 +08:00