* fix(vector_stores): stop leaking stored credentials in direct search debug logs
Direct vector store providers (RESP datastores like Valkey) have no HTTP
request to echo, so both search handlers called `logging_obj.pre_call` with
no `api_base`. The logging helper treats an empty `api_base` as "nothing to
render" and falls back to `str(self.model_call_details)`, which carries the
resolved `litellm_params`: the stored `valkey_password` and the embedding
config's `api_key` among them.
The stdout logger's regex redaction hid this, but `pre_call` also writes the
same string to `litellm_params["metadata"]["raw_request"]`, which ships
unredacted to every logging callback (Langfuse, OTel, etc.).
Pass a synthetic `<provider>://<vector_store_id>` endpoint plus an explicit
`request_str` so the debug output describes the call instead of dumping call
details, and fold the duplicated sync/async blocks into one helper so the
sanitized descriptor cannot drift between them.
* fix(vector_stores): type direct search query as Sequence[str]
The new helper's list[str] annotation pushed LIT001 over its
type-discipline ceiling. Sequence is the read-only shape the helper
actually needs, and list[str] still satisfies it at both call sites.
* test(ui): characterize the model info and cache control submit payloads
Pins the antd behaviour these forms have today, ahead of moving them onto
react-hook-form: the full model info PATCH body, the sticky touched-field
semantics that decide which pricing keys ship, the mounted-only cache control
keys, and the string-typed injection point index.
* refactor(ui): move the model info edit form off antd Form
The deployment edit form on the model info view now runs on react-hook-form
with a zod resolver and shadcn controls, extracted into ModelInfoEditForm so
the view keeps the payload builder and the form keeps the fields.
Cache control injection points become a presentational value/onChange child,
which lets the model info view host it through react-hook-form while the add
model form keeps hosting it through antd. That child never wrote to a real
store on either side: it registered under cache_control_points while both
parents read cache_control_injection_points, so its form prop was inert.
antd marks a field touched on change and never clears it, and neither
touchedFields nor dirtyFields reproduces that, so the four pricing keys that
gate on it track first change explicitly.
The PTU rules move from antd validator wrappers to pure predicates that both
surfaces share, since the add model form still feeds the wrappers to its own
antd form.
* refactor(ui): trim comments and type the model record prop on the edit form
Cuts the explanatory comments that the house rules do not allow, keeping
only the three that record non-obvious library behaviour plus the eslint
directive, and narrows the modelData prop to the two fields the form reads.
Corrects the claim in 5b7ecede4e that the cache control child registered
the wrong key. At the staging tip the Form.List registration is on
cache_control_injection_points and is live, which is why this PR rehosts
it into advanced_settings. The dead part is the three
getFieldValue("cache_control_points") readers, whose key nothing
registers, so updateCacheControlPoints dereferences undefined and the
caught error reaches the console on every role, index or remove change.
The last tremor component import left the dashboard when the primitive
sweep merged, so the package, its v3 compatibility shim, its @theme token
block and the palette safelist it needed at runtime all have no consumer.
Removing the safelist is what shrinks the shipped stylesheet: tremor built
class names at runtime, so Tailwind had to emit every bg/text/border/ring/
stroke/fill utility across 22 palettes and 11 shades in case one was used.
Nothing in the app constructs a class name that way any more, so the
scanner finds every utility on its own.
The date-fns overrides pin also goes. It only existed because tremor and
react-day-picker@8 peered on date-fns 3 while Base UI wanted 4, and the
lockfile still resolves a single hoisted 4.4.0 without it.
The dashboard had no shared tokens for non-destructive status colours, so
components reached for raw Tailwind shades instead. Add --success, --warning
and --info alongside the existing --destructive, in both :root and .dark, and
register them in @theme inline so the usual utilities resolve.
Light values are picked for legibility as foreground text rather than by
copying a fixed shade number. Tailwind's ramps are not perceptually aligned
across hues, so amber-600 and green-600 sit at 66.6% and 62.7% lightness and
fail WCAG AA on white (3.19:1 and 3.22:1). green-700, amber-700 and blue-600
land at 52.7%, 55.5% and 54.6%, the same band as --destructive at 57.7%, and
clear AA. Dark mode uses the -400 shades, matching --destructive.
The .dark values are populated even though nothing can apply that class yet.
They are the artifact the later theme switch work will turn on.
Alert moves its info and warning variants onto the tokens. The tint is /5
rather than /10 because /10 drops both below AA. The error variant keeps its
existing shades: it involves no new token, and its current 9.21:1 is better
than anything the token form would give it.
basedpyright rejects a Final variable assigned inside a loop body
(reportGeneralTypeIssues); the type-discipline checker doesn't flag
this line without Final either, so the annotation only bought a
basedpyright budget regression.
* feat(bedrock): add a config toggle to disable agent-runtime pass-through
The /bedrock pass-through dispatches agents, knowledge bases, flows, rerank,
retrieveAndGenerate, generateQuery and optimize-prompt to bedrock-agent-runtime,
so an operator who only wants to expose model invoke and converse has no way to
narrow that surface
Adds general_settings.disable_bedrock_agent_runtime_passthrough. When set, those
routes are rejected with a 403 before credentials are fetched or the request is
signed. Plain bedrock-runtime model pass-through is unaffected, and the setting
defaults to off, so existing deployments behave exactly as before
The branch is inverted to an early return for the non-agent-runtime case so the
toggle can reject outright instead of falling through to model extraction, which
would surface a confusing 400 about an unparseable model
* style(bedrock): drop redundant docstrings from the agent-runtime toggle
- Resolved merge conflicts in _handle_completed_batch and
CheckBatchCost._track_completed_batch_cost, keeping BatchCostUsageResult
while adopting upstream's model_info threading and improved
deployment-pricing lookup
- Fixed two upstream tests and one mock that still expected the old
tuple(cost, usage, models) return shape
- Suppressed the one new LIT002 violation from the empty-output-file
BatchCostUsageResult literal
* refactor(ui): move the agent, guardrail, prompt, policy and skill forms off tremor
The agent info Save Changes button used to rely on tremor's implicit
submit inside the antd Form, so it now carries an explicit type="submit".
Every converted TabsContent is keepMounted to keep tremor's always-mounted
panel semantics, pinned by a new guardrail info test. Prunes the tremor
no-restricted-imports suppressions these nine files no longer need.
* fix(ui): keep the line tab strip on the agent, guardrail and prompt info views
tremor's TabList defaulted to the line variant while shadcn's TabsList
defaults to the filled pill, so the bare conversion turned three underlined
tab strips into segmented pills. Restores the line variant plus the bottom
border and the tab padding the strips used to have.
* test(ui): pin the agent settings submit and the prompt raw json tab
Agent Settings only saves because Save Changes carries an explicit
type="submit" now that the button is a Base UI button, so a test drives the
edit and asserts the patch call fires. The prompt info tabs are keyed by slug
now, which also makes Raw JSON render for prompts with no template, so a
second test renders that case and asserts the serialized response is visible.
The prior commit ran ruff format on the whole file to type the
_batch_cost_result helper, reflowing hundreds of unrelated pre-existing
lines that were never ruff-format-clean to begin with (confirmed at the
PR's base commit, before any of these changes). CI's ruff-format gate
only checks litellm/**/*.py, not tests/, so this reformatting served no
CI purpose and only bloated the diff. Restores everything except the
intended TYPE_CHECKING import and _batch_cost_result annotations.
- ruff format litellm/batches/batch_utils.py
- reconcile the upstream output_file_id=None guard (merged in from
litellm_internal_staging) with BatchCostUsageResult, and count that
batch's error_file_id failures instead of always reporting 0
- fix test_handle_completed_batch_no_output_file_is_zero's tuple
unpacking, which predated the BatchCostUsageResult refactor
- commit the batch_successful_requests/batch_failed_requests fixture
fix to test_spend_management_endpoints.py that was left uncommitted
Both surfaces wrote zero bytes for the whole time-to-first-token, so an
intermediary with an idle read timeout drops a healthy connection before the
first token. They reached neither keepalive engine, which is what #37322 left
open.
The streaming assistants run spends that wait inside the awaited call that
produces its response, since create_response buffers the first chunk, so it
takes the same open_sse_before_first_byte seam the native routes use. The A2A
route only contacts the upstream agent once its body iterator is first pulled,
so nothing is awaited before the response exists and the gap has to be filled
from inside the stream instead; wrap_sse_stream_with_keepalive_pings already
does that and now takes the filler as a parameter, so A2A gets an SSE comment
its JSON-RPC clients discard rather than Anthropic's ping event.
Off until an operator sets litellm_settings.sse_keepalive_ping_interval_seconds.
* fix(otel): bound and shut down credential-scoped tracer providers
Each credential-scoped TracerProvider owns a BatchSpanProcessor worker thread that
only stops on shutdown, and the v1 cache holding them was an unbounded, unsynchronized
dict that never shut anything down. Every distinct team/key credential set therefore
added a thread for the life of the process, and concurrent first-requests for the same
credential set orphaned duplicate providers outright.
Make the cache a lock-guarded bounded LRU that shuts down whatever it drops, matching
the v2 TenantTracerCache. Providers wrapping a caller-supplied SpanExporter instance
share that exporter with the logger's own provider, so they are dropped without
shutdown; those use SimpleSpanProcessor and own no thread.
* fix(otel): reclaim dropped providers on a dedicated executor
Sustained credential churn queues one blocking shutdown per eviction, so using the
shared logging executor let an unreachable tenant endpoint stall unrelated logging
work behind the OTLP retry budget. Give provider shutdown its own bounded pool; its
threads spawn lazily, so a proxy that never evicts still pays nothing.
* fix(otel): decide provider shutdown from the victim, not the evicting request
Both dynamic entry points share one provider cache, so it can hold providers of
mixed exporter ownership. Reading the ownership flag from the evicting request
therefore stopped a shared caller-supplied exporter in one direction, silencing
telemetry process-wide, and leaked a BatchSpanProcessor thread in the other.
Cache ownership alongside the provider so the drop decision reads the victim's
own flag.
* fix(otel): honor the widened header mapping type instead of dict only
Widening the header parameter to Mapping left the isinstance check on dict, so a
non-dict Mapping silently returned no headers at all, which for the OTLP path means
an unauthenticated exporter and no traces with nothing raised. The dict branch also
returned the caller's own object, and dropping the defensive copy at the call site
let that alias reach a long-lived exporter. Match on Mapping and copy.
* fix(otel): do not give a provider we may never stop an interpreter-exit hook
Every TracerProvider registers an atexit hook by default, and that hook holds a strong
reference. Providers wrapping a caller-supplied exporter are dropped without shutdown,
so they stayed pinned for the life of the process and then stopped the shared exporter
at exit. Tie shutdown_on_exit to ownership: those providers use SimpleSpanProcessor and
buffer nothing, so they lose no flush, while providers that own their exporter keep the
hook and their exit flush.
Also stop the victim the eviction test leaves behind, and trim the added comments.
Key and team router_settings set enable_tag_filtering on the request kwargs,
and get_deployments_for_tag already treats that as authoritative, but
_select_pre_routing_strategy only consulted the router-wide flag, so tagged
auto-router markers still captured untagged requests from keys that enabled
filtering. The e2e auto-router module now enables tag filtering through
key-level router_settings instead of flipping /config/update module-wide,
which was denying concurrently running tagged requests from other suites on
the shared per-build CI proxy.
* refactor(ui): move the model info view and pass-through endpoint forms off tremor
The pass-through settings form's Save button relied on tremor's implicit
submit, so it now carries an explicit type="submit" because the Base UI
button defaults to type="button". The include-subpath switch inside the
antd Form.Item is wired through onCheckedChange plus form.setFieldsValue,
since the Base UI switch does not read the onChange antd injects. Both
tab strips keep every panel mounted so the edit forms survive a tab
switch, pinned by a new mount-contract test on the model info view. Also
prunes the six tremor no-restricted-imports suppressions these files no
longer need.
* test(ui): pin the model info overview panel to its own dom node across tab switches
* fix(ui): keep the line tab strip on the model info and pass-through views
Both tab strips were bare tremor TabLists, which defaulted to the line
variant, so the straight rename turned them into filled segmented pills.
They now use the same full-width line strip the agent, guardrail and
prompt info views ship.
The antd form reset on [form, actualSchema, tool], so it reseeded whenever
the schema changed and not only when a different tool was picked. Keying the
migrated form's remount on tool.name alone narrowed that: a same-named tool
whose schema changed would have kept its old indexed values and submitted
them under the new schema's keys, and any field the new schema added would
never get its default. The key now covers the schema content as well.
Reachability, so nobody reads more into this than is there: selectedTool is a
state snapshot set on click, so this is not reachable through the current
parent. It is a latent divergence rather than a live bug, and it is fixed
because the contract for this migration is zero functional change.
The schema default helpers move into the pure module beside the other
argument logic. They arrived carrying the original's explicit any and built
their result by mutating it; they are now typed with unknown, built by
spread, and covered directly by unit tests rather than only through a render.
Also takes the last five hardcoded neutrals onto tokens. The earlier pass
scanned the gray family only and did not see the slate ones in the tool name
chip, which had no dark variant. Counting colour utilities that have no
dark: counterpart reads 0 for this file now, against 64 before the migration.
The Base UI combobox only renders a selected value that is present in its
item list, so the port synthesizes an item for the current value when the
search results no longer contain it. That synthetic item carried an empty
user, and selecting it ran the same handler as a real result, wiping both
the email and the user id before submit.
The synthetic item now carries no user at all and the select handler
ignores it, so reselecting the value already in the field leaves both
identity fields alone. antd needed none of this: its Select renders a
value that is absent from its options.
The team create and edit forms send a different set of keys depending on
which collapsible sections the user opened, because a closed section is
unmounted and its values never reach the request. Nothing covered that,
so a form rewrite could change the request body without failing a test.
Pins the exact key set the create form sends with every section closed,
the keys Additional Settings adds once opened, and that a value typed
then re-hidden is dropped while a reopened one is restored. Does the same
for the team member and search tool sections on the edit form, asserting
absence at the wire level rather than just comparing values.
Also hardens two option queries in the member modal suite onto the option
role, and lifts the duplicated mock seeding in the team info suite into
one function both blocks call.
* test(ui): raise vitest test and hook timeouts for CI headroom
The UI unit suite runs about 3x slower on the CI runner than locally, which
put the slowest cases right on the 30s per-test limit. TeamInfo's pass
through routes case takes ~8s locally and has been failing on staging at the
timeout across consecutive runs even though it passes reliably when run
directly.
Raise testTimeout to 60s and set hookTimeout to 30s so the current slow cases
have headroom. This is a stopgap while the suite gets split into proper tiers,
not a fix for the underlying per-test cost.
* test(ui): query agent form panels with findByRole like the rest of the file
The panel helper was the only synchronous query in add_agent_form's
integration test; every other lookup already retries via findBy. On the CI
runner the second case has been failing with "Unable to find an accessible
element with the role button and name /Cost Configuration/" against a modal
whose body had not rendered.
Make the helper retry like its siblings and await it at each call site.
The tool test panel drove its argument fields through an antd Form, so the
call payload was whatever rc-field-form happened to have mounted. It now runs
on react-hook-form with shadcn controls, and the payload itself lives in
toolCallArguments.ts as a pure function of the schema fields plus the entered
values.
Fields bind by index rather than by name, because an MCP tool's JSON schema
can name a property anything: a key containing a dot would be one flat key to
antd but a nested path to react-hook-form. Binding to args.0, args.1 and
zipping back to the real keys at submit time keeps the emitted arguments
identical whatever the server calls its properties.
Coercion, the blank filter, the required and JSON rules, and the params
wrapper for nested-object schemas all keep their previous behaviour, and the
neutral colours in the panel move onto tokens so it reads correctly in dark
mode.
* refactor(ui): move the admin, SSO, SCIM, alerting and fallback forms off tremor
Swaps the tremor Button, Card, Callout, Grid, Divider, Text, Title, TextInput, Table parts, Badge, Icon and Switch in these nine files for the shadcn layer and lucide icons, keeping antd in place. The SCIM create-token button keeps an explicit type="submit"; the two SCIM copy buttons sit outside the antd form so they stay plain buttons, and the alerting form's Enterprise Feature upsell button is a link wrapper rather than a save action, so it deliberately stays type="button" while the form keeps its own Update Settings submit. The teal login callout on the admin panel maps to the info Alert variant since no teal variant exists. Prunes the nine tremor no-restricted-imports suppressions these files no longer need.
* fix(ui): keep the alerting settings name column left aligned
tremor's TableCell hardcoded text-left, so the align="center" attribute never took effect. The shadcn cell has no text-align of its own, so translating that attribute into text-center would have centered the field name and its description for the first time.
* fix(ui): restore the CloudZero key reveal toggle and the SCIM divider gap
tremor's TextInput drew its own show/hide button whenever the type was
password and the field was not disabled, so the straight pass-through to
the plain shadcn Input silently deleted that affordance from the CloudZero
API key field. Rebuilds it with the InputGroup reveal pattern that
email_settings.tsx already uses, behind a small local control so the antd
Form.Item keeps its id, value and onChange wiring and the field still
matches its shadcn sibling in the same form.
Also puts the SCIM separator back on my-6: tremor's Divider was
"w-full mx-auto my-6", and the conversion shipped my-4, tightening that
gap by 8px on each side. The disabled SCIM token field stays a bare Input
because tremor suppressed its toggle when disabled too.