A pass that raises (a missing Slack webhook, say) now waits the daily interval instead of logging the
same exception every 30 seconds, and the Admin UI alerting settings list the new alert type so it can be
toggled like the others
Regenerates the guardrails and policy_engine fragments of the lazy OpenAPI
snapshot for the usage-unit fields, regenerates schema.d.ts from it, and
exports DailyGuardrailUsageUnitsRepository next to its sibling repositories
Both loading tests mock searchToolQueryCall as a promise that resolves on a
timer, assert the loading affordance, then return with that promise still in
flight. When the worker outlives the file's jsdom environment, the component's
setIsLoading(false) runs against a torn-down window, and React reports
"ReferenceError: window is not defined" as an unhandled rejection. Vitest counts
that as an error, so ui-unit-tests fails the job while reporting every one of
its 7351 tests as passed.
Awaiting the settled state keeps both assertions and leaves nothing pending at
teardown.
A bare toHaveBeenCalled() passes no matter what the caller passed, so the CSV
export could serialize the wrong rows, write the wrong content type, and name
the file wrong while its test stayed green.
Strengthens the load-bearing cases in three files onto the arguments that carry
the behaviour: the rows handed to the CSV serializer, the blob content type, the
anchor that gets attached and cleaned up, and the specific message each
validation failure shows the user. Two of the discount and margin tests
previously asserted the same bare call for different validation failures, so
neither could tell the two apart.
Each rewrite was proven by mutating the source it covers and confirming the test
goes red where the bare assertion stayed green.
The User Usage view handed EntityUsage a static entityList holding only
the first /user/list page, so its filter could only find the 50 most
recently created users and anyone beyond that page, including users
with spend in the selected period, was unreachable
Add a self-contained UserDropdown that owns useInfiniteUsers (server-side
search plus load-more, mirroring TeamDropdown) and use it both as the
User Usage filterSlot and for the Global Usage user filter. Resolve a
selected user that is outside the loaded page by id so its label
survives view round-trips. Drop the now dead single-select branch from
UsageExportHeader
Enables 14 rules at error with zero violations, so the vacuous-assertion class
the previous commit fixed cannot come back. No budget file and no suppressions
baseline: a rule is on only if it is already at zero.
prefer-to-have-value stays off. It matches any attribute whose name contains
"value", so it rewrites toHaveAttribute("aria-valuenow", n) into toHaveValue(n),
and jest-dom's toHaveValue supports only form controls, which breaks every
role="meter" element the dashboard renders.
Records the enabled set, the rules left off with their measured counts, and the
seven ways these plugins' autofixers produce broken output.
One test could not fail: it asserted on the Promise returned by an unawaited
findAllByText, and a pending Promise is always truthy, so the organization
detail view had no working coverage at all.
Rewrites 316 assertions across 128 files onto matchers that describe what the
user perceives, and awaits the queries that were being compared as Promises.
Most of this was mechanical, but the fixers behind these rules are not
trustworthy, so every site they damaged was repaired by hand. The quiet one
worth naming: prefer-to-have-text-content wraps strings in new RegExp() without
escaping, turning toContain("100K+ requests") into a pattern meaning "100
followed by one-or-more K". That compiles, lints clean, and keeps passing while
no longer asserting what it claims.
The pin tests picked the pinned model by position in the option list. antd
virtualizes that list and jsdom reports every row as zero height, so only the
first window ever renders, and adding the Lite preset to the bundled JSON pushed
the pin out of it. Both cases have been red on litellm_internal_staging since
that preset landed.
Typing the model name filters the list to the pin, so the click no longer
depends on where the pin sits among the presets.
The auto-router tests moved onto accessible queries here while staging added a
"Lite" preset and a default-model pin, so the option-label expectations take
staging's list read through this branch's visibleOptions helper.
Staging's new pin tests reached for antd's internal classes, which the lint rule
this branch enables rejects. The edit-modal cases now read the rendered
selection through one selectedValueIn helper, and the clear-affordance click in
ComplexityRouterConfig keeps a reasoned suppression since antd marks that icon
aria-hidden.
The key overview card and the Virtual Keys spend meter fell back to the parent team's max_budget as the denominator while the numerator stayed the key's own spend, so a $0.50 key on a $1,200 team read as "$0.50 of $1,200 (Team)" and drew a meter against a limit that governs the whole team's aggregate spend, not this key. Both surfaces now show Unlimited for a budgetless key and, when the parent team or organization does carry a budget, a hover hint listing those inherited caps so the reader knows what still gates the key
Callers can opt into the provider's raw operation response on /v1/ocr with the x-req-format: native header (or req_format in the body) while page-based cost tracking keeps reading usage_info off the normalized response.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* feat(ui): link key info header to its user, creator, team, and organization
The key info page showed the owning user and creator as plain text and never surfaced the team or organization at all, so walking from a key to its parent entities meant copying ids into other pages. The header now renders User and Created By as links to the user detail page, and gains a far-right column with Team and Organization links (alias when known, id otherwise). Client-side navigation logic shared by BadgeLink and IdentityCell moves into a reusable EntityLink so all entity links behave the same
* test(ui): mock next/navigation in VirtualKeysTable test
KeyInfoView now renders EntityLink, which calls useRouter, so the table test that opens the key detail needs the app router mocked
* Add default model pin to complexity router UI
A complexity router's default model was only ever derived from the tiers, so
operators had no way to point the fallback at a model that is not first in the
Simple or Medium tier. Adds a Default Model select that records an explicit pin.
The pin is stored in complexity_router_config.default_model, which the backend
already reads, and mirrored onto complexity_router_default_model on save. Both
paths resolve through one helper that mirrors init_complexity_router_deployment:
a pin wins, otherwise MEDIUM or SIMPLE. Recording the pin in the config keeps it
distinguishable from a derived value, so a pin that happens to match the tiers
survives a round trip instead of being read back as tier tracking.
The edit modal only requires one non-empty tier, so a router with models in
COMPLEX alone can reach save with nothing the backend would pick. That now
blocks with an inline message rather than saving a router that raises at init.
* fix(UI): probe the pinned default model in the auto router connection test
The connection test built its targets from the tiers and the embedding model
only, so a Default Model pin outside every tier was never reached and a green
result could hide an unreachable default. model_info_view had already hand
rolled the dedupe and append locally, so the rule moved into
buildAutoRouterTestTargets and both call sites now share it.
* fix(ui): mirror backend precedence when resolving a complexity router default
The edit modal only recognized a pin stored in complexity_router_config.default_model,
so a router whose default lived solely in litellm_params.complexity_router_default_model
lost it on the next save. That field cannot be trusted outright either: before this PR
every save wrote a tier-derived value into it, so treating any value as a pin would
freeze legacy routers away from their tiers. Hydration now takes the config marker as
authoritative and falls back to litellm_params only when it diverges from what the tiers
alone derive, which is only reachable through an external API or config write.
Test Connection had the mirror-image bug: it fell back to complexity_router_config.default_model,
a UI-only marker init_complexity_router_deployment never reads, so it could probe a model
the router would never call. It now follows router.py exactly: litellm_params, else pure
tier-derivation.
Also reword a tooltip that hardcoded the Default Model select's position on the page, and
document the dual write and the create-vs-edit validation asymmetry.
Resolves conflicts from the upstream merge and addresses the Veria-AI
review comment on this PR: batch rows could bypass a project's
per-model ITPM/OTPM quota when the batch's file-bound/routing model
had no quota configured. Charges each row's own model against its own
project quota instead of only the routing model's, and fixes rate
limit error messages to attribute the correct model via a new
descriptor_value field on RateLimitStatus/AtomicCounterMeta. Also
re-syncs the ruff-strict, type-discipline, and basedpyright budgets
against the correct (non-stale) merge base.
Co-authored-by: Cursor <cursoragent@cursor.com>
The bulk invite button was rendered from inside CreateUserButton, so the two
actions were locked together and only the bulk one had been migrated, leaving
the users page with an antd primary button sitting next to a shadcn one.
Move BulkCreateUsersButton up to the users page toolbar so each action stands
on its own, and migrate CreateUserButton's buttons to the shared shadcn Button
so both triggers render identically. The teams prop only ever fed the bulk
button, so it goes away from CreateUserButton and its other call site.
The Global Control Plane (formerly documented as the HA Control Plane) is
documented as an Enterprise feature, but `worker_registry` carried no premium
check, so any OSS install could run one. Gate it at config load, matching the
`enforced_params` precedent, and fail startup rather than ignoring the registry
silently: a silently dropped registry degrades a control plane into an ordinary
proxy with no signal to the operator.
Also declare `worker_registry` and `general_settings.control_plane_url`, both
load bearing today and neither previously declared, so they appear in the
generated config schema.
Dashboard tests located controls through antd's own class names
(.ant-form-item, .ant-select-selector, .ant-select-item-option). Those
break when a page moves to shadcn without any behaviour changing, and
they miss regressions a user would notice.
Replace them with role, label, title and accessible icon-name queries
where antd exposes one, and add local/no-antd-class-selectors to keep
them out. The rule is enabled as an error at zero violations, so there is
no budget file and no suppressions baseline. It found eight more sites a
'.ant-' search missed, written as bare class names.
Eleven couplings remain and carry an inline suppression naming why:
antd puts role="option" only on a hidden mirror list, so the visible
options have no role, no aria-disabled and a tooltip in title; Skeleton
and the modal mask expose nothing at all; and one assertion's whole
purpose is that no antd modal renders.
7231 tests passed before, 7233 pass after: one conflated ModelSelector
case became three focused ones.
antd Form.Item injects an id into its child so the rendered label can point
at the control. BudgetDurationDropdown destructured its props without
forwarding that id, so the label resolved to nothing and assistive tech
announced the field unnamed. Forward it to SelectTrigger.
Create Key offered two options labelled "Never resets" in the Reset Budget
dropdown. BudgetDurationDropdown renders its unset item using the caller's
placeholder, and create_key_button passed placeholder="Never resets" alongside
showNeverResets, so the omit option and the explicit-null option looked
identical while behaving differently. An omitted budget_duration picks up
default_key_generate_params and the linked budget tier's schedule, whereas the
"none" sentinel is converted to an explicit null and truly never resets. The
unset item now reads "Not set", matching getBudgetDurationLabel, and the
create-key test mock passes the placeholder through so a future collision fails
the suite
The rest is migration cleanup found during manual QA. The models and endpoints
tab strip hides its scrollbar and fades at the right edge using a vendored copy
of the shadcn scroll-fade utility, keeping the CLI package out of the build.
globals.css neutralises the @tailwindcss/forms resting-state rules for
combobox-chip-input, which lets twelve call sites drop the same copy-pasted
className workaround. Guardrails moves to the line tab variant and stops
clipping its textarea focus ring, the log drawer JSON tree takes the app
background, the audit log empty state centres, the caching page selects no
longer stretch to the row height, the usage page team filter shares its row
with the Export button through a new filterSlot prop, and the cost optimization
and vector store tab strips drop their leftover full-width divider