* feat(guardrails): add LLM_AS_A_JUDGE to SupportedGuardrailIntegrations
* feat(types): add EvalVerdict, StandardLoggingEvalInformation; wire eval_information into SpendLogsMetadata
* feat(guardrails): add self-contained llm_as_a_judge guardrail hook
* fix(a2a): filter agent-only litellm_params from acompletion kwargs; pass agent_id into body
* feat(ui): add LLMJudgeFields criteria builder component
* feat(ui): wire LLM-as-a-Judge into add guardrail form
* feat(ui): update EvalViewer — title 'LLM Judge Results', weighted score column, summary row
* fix(ui): wire EvalViewer into LogDetailContent to show LLM judge results on logs page
* fix(guardrails-ui): route llm_as_a_judge to criteria builder step; rename to LiteLLM LLM as a Judge; add litellm logo
* fix(guardrail-viewer): stack lifecycle + eval details vertically to avoid badge overflow in narrow drawer
* fix(guardrail-create): surface config validation errors on create instead of silently orphaning guardrail in DB
* fix(guardrail-registry): hardcode llm_as_a_judge in initializer registry so it loads regardless of package install path
* fix(llm-as-a-judge): fix P1 code quality issues - validate weights/on_failure, guard pre_call, handle multimodal, move imports to module level, fix spurious finally logging
* fix(guardrail_endpoints): use correct PK field in rollback delete and log rollback failure
* fix(llm_as_a_judge): support Pydantic object in _get_litellm_param fallback chain
* fix(LLMJudgeFields): replace @tremor/react Button with antd Button
* fix(llm_as_a_judge): remove dead registry dicts, fix KeyError in prompt builder, set correct status on judge failure
* test(llm_as_a_judge): add unit tests for guardrail hook
* fix(llm_as_a_judge): remove @log_guardrail_information decorator to fix duplicate guardrail_information entries
The decorator and the manual finally block both called add_standard_logging_guardrail_information_to_request_data, producing two entries per request. The decorator also misclassified HTTPException(422) blocks as guardrail_failed_to_respond (it checks for 400). The finally block correctly tracks status throughout, so removing the decorator is sufficient.
* fix(test_gcs_pub_sub): ignore metadata.eval_information in comparison
* fix(test_spend_management): ignore metadata.eval_information in payload comparison
* fix(types/guardrails): add input_type and messages to ApplyGuardrailRequest
* fix(guardrail_endpoints): pass input_type and messages through apply_guardrail endpoint
* fix(guardrail_endpoints): auto-detect post_call guardrails and use input_type=response
* fix(a2a_endpoints): merge agent litellm_params guardrails into data before post_call hooks
* fix(llm_as_a_judge): use float sum with tolerance for weight validation
* fix(guardrail_registry): split long import line for black formatting
* fix(llm_as_a_judge): guard guardrail_name Optional for mypy
* fix(llm_as_a_judge): set guardrail_status=guardrail_intervened when score fails, regardless of on_failure mode
* fix(a2a_endpoints): use try/finally so deferred spend log fires even when guardrail blocks with 422
* fix(litellm_logging): declare _defer_async_logging and _enqueue_deferred_logging on Logging class for mypy
* fix(logging_worker): restore queue.join() in flush() to wait for in-flight callbacks
Previously, the "Store Prompts in Spend Logs" and "Maximum Spend Logs
Retention Period" settings were surfaced via a gear-icon modal on the
Logs page. The gear was visible to every authenticated user even though
the backend endpoints (/config/update, /config/list) require PROXY_ADMIN
— so non-admins could open the modal but the request would 403 on load
and save, giving a confusing UX.
Move the controls into a new "Logging Settings" tab under Admin Settings,
which is already gated to admins at the sidebar. Remove the gear button
and the onOpenSettings prop chain (ConfigInfoMessage → LogDetailContent →
LogDetailsDrawer). ConfigInfoMessage now points users to
"Admin Settings → Logging Settings" inline.
Members tab column reads this field; dropping it from the type in the
previous revert broke the type check without affecting the reverted
render logic.
Relative labels ("today", "in 2 days", "on May 12, 2026") mixed three
shapes in one column, breaking scannability. Always render MMM D, YYYY
for consistency and easier at-a-glance comparison across members.
The useEffect that re-fetches logs on sort/page/time changes:
useEffect(() => {
if (hasBackendFilters && accessToken) {
performSearch(filters, currentPage);
}
}, [sortBy, sortOrder, currentPage, startTime, endTime, isCustomDate]);
intentionally omits `filters` and `hasBackendFilters` from its dep array
to avoid double-fetches when a filter is applied. The side-effect is a
stale-closure bug: the effect captures `filters` and `hasBackendFilters`
from the render where its deps last changed, not from the render where
the user selected, e.g., a Key Alias.
Reproduce: set Key Alias → results appear correctly → change page or
sort → the effect fires with the OLD `filters` snapshot (no key_alias)
→ API request is sent without the filter → table shows unfiltered data.
Fix: store the latest `filters` and `hasBackendFilters` in refs that are
kept in sync on every render. The sort/page/time effect reads from the
refs instead of the closure so it always uses the current filter state
without altering the dep array.
Co-authored-by: Bytechoreographer <Bytechoreographer@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4 (1M context) <noreply@anthropic.com>
When backend filters (e.g. Key Alias) are active on the Request Logs
page, the manual Fetch button called logs.refetch() which re-runs the
main TanStack Query. That query does not carry backend-only filter
params such as key_alias, so the button had two problems:
1. It fired a redundant API request without the active filters.
2. It did not refresh the filtered result set — backendFilteredLogs
stayed frozen at the last debounce-triggered fetch.
Fix: expose refetchWithFilters() from useLogFilterLogic and route the
Fetch button through it when hasBackendFilters is true. This cancels
any in-flight debounce and calls performSearch with the current filter
state, keeping all active filters intact.
Co-authored-by: Bytechoreographer <Bytechoreographer@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4 (1M context) <noreply@anthropic.com>
Adds back the per-cycle spend column that was replaced by Total Spend in
331e3f22. Current Cycle Spend reads membership.spend (zeroed on
budget_reset_at) — this is the value enforced against the member's
budget, so admins need it to see whether a member is approaching their
cap for the active window. Total Spend remains for lifetime analytics.
Adds a formatBudgetReset helper (dayjs-based, with validity guard) that
renders the next reset as "today" / "in N days" / "on MMM D, YYYY". The
team budget card now shows the team's reset timestamp and the member-
default reset (when a shared team_member_budget is configured), and the
Members tab gains a Budget Reset column per member.
Premium fields like policies are echoed at the top level of the
/key/update response, not necessarily mirrored into metadata. Read
metadata first then fall back to the top-level property so an
intentional clear is preserved in either shape.
The /key/update response echoes top-level defaults like policies:[] into
client state. On a subsequent edit, the form resends policies:[], which
the backend treats as "user is setting policies" and blocks with a 403
enterprise check regardless of value.
Drop premium metadata fields from the update payload when the current
form value and the previously persisted value are both empty. Genuine
clears (non-empty -> empty) still pass through so premium users can
clear policies as intended.
Set extra_headers explicitly in initialValues instead of relying on
a useEffect setFieldValue call that races with Antd form initialization.
Also avoid sending empty array on submit so the backend's exclude_none
doesn't overwrite stored values.
The edit page was calling POST /mcp-rest/test/tools/list (the temp-session
endpoint that requires inline credentials) on mount. Since fetchTools
deliberately omits credentials from the request body, any server with
auth_type api_key/bearer_token/basic/authorization would 422.
Switch to GET /mcp-rest/tools/list?server_id=... which looks up stored
credentials on the backend — no inline creds needed for saved servers.
Editing a model in the Admin UI (e.g. to change credential) unconditionally
included input_cost_per_token: 0 and output_cost_per_token: 0 in the PATCH
payload, overriding built-in pricing from model_prices_and_context_window.json.
Guard cost fields with form.isFieldTouched so they are only sent when the
user explicitly modifies them. Intentional $0 cost (for budget bypass) still
works because the guard checks touched + non-null, not non-zero.
Unit Tests: Proxy DB Operations / proxy-db (auth-checks, tests/proxy_unit_tests/test_auth_checks.py tests/proxy_unit_tests/test_user_api_key_auth.py, 20, 8) (push) Waiting to run
Unit Tests: Proxy DB Operations / proxy-db (remaining, tests/proxy_unit_tests --ignore=tests/proxy_unit_tests/test_key_generate_prisma.py --ignore=tests/proxy_unit_tests/test_auth_checks.py --ignore=tests/proxy_unit_tests/test_user_api_key_auth.py, 30, 8) (push) Waiting to run
page_utils.test.ts enforces that every menuGroups entry has a matching
description and vice versa. The left nav uses 'skills' but page_metadata.ts
still had 'claude-code-plugins', causing two test failures.
The antd mocks in RouterSettingsForm.test.tsx and index.test.tsx
replaced the entire antd module with only Select, so the Switch and
Button used by nested components failed to render. Use importOriginal
to preserve the rest of antd and override only Select.
Also fix the TagFilteringToggle click assertion — antd's Switch fires
onChange with (checked, event), so toHaveBeenCalledWith(true) was
always going to miss. Assert the checked arg directly instead of
coupling to antd's call signature.
Deduplicates base64UrlEncode, generateCodeVerifier, and
generateCodeChallenge which were copy-pasted across useMcpOAuthFlow
and useUserMcpOAuthFlow hooks.
Show an info banner on the Deleted Keys and Deleted Teams pages for
non-premium users indicating that deleted-record auditing is graduating
from beta into the Enterprise audit & compliance suite.