Commit graph

4746 commits

Author SHA1 Message Date
ryan-crabbe-berri
2b2ae4ca49
refactor(ui): migrate MCP, callback, guardrail, SSO, and search tool logos to the shared Logo component (#34169)
Some checks failed
LiteLLM Rust / rustfmt, clippy, test (push) Has been cancelled
* refactor(ui): migrate MCP, callback, guardrail, SSO, and search tool logos to the shared Logo component

Third step of the logo consolidation. Every remaining rogue logo
pattern now renders through Logo: MCP well-known grid and backend
mcp_info.logo_url sites (which previously skipped resolveLogoSrc and
broke under non-root mounts), callback maps in callback_info_helpers
plus the backend-provided variant in settings.tsx, the guardrail map
with the garden dataset now deriving logos from guardrailLogoMap
instead of duplicating them, the SSO map deduped from two verbatim
copies into SSOSettings/constants.ts, search tools' filename guessing
replaced with an explicit static-import map, and the two straggler
sites in EntityUsage and model_info_view.

Static-map path strings become bundled static imports throughout;
backend-provided URLs stay runtime strings resolved via Logo src mode.
MCPLogoSelector still stores stable /ui/assets/logos paths so existing
DB rows keep matching. okta's logo remains an external hotlink pending
a vendored local asset. promptguard.svg drops a mismatched intrinsic
dimension attribute for the Turbopack import parser.

* fix(ui): make resolveLogoSrc idempotent for values already carrying the server root path

Stored mcp_info.logo_url values from sub-path deployments could bake in
the deployment root because the old bare img sites did no resolution.
Prefixing those again produced /litellm/litellm/... and a fallback
avatar. Skip prefixing when the value already starts with the current
normalized root segment; paths whose first segment merely begins with
the root text still get prefixed.
2026-07-21 22:22:54 +00:00
yuneng-jiang
72d458e416
feat(ui): add react-hook-form + zod form infrastructure (#34170)
* feat(ui): add react-hook-form + zod form infrastructure

Introduce the shared form layer the dashboard's antd forms will migrate onto,
with no user-visible change yet.

- pin react-hook-form, @hookform/resolvers, and zod (kept on 3.25.76 and
  imported via the zod/v4 entrypoint so openai's optional zod ^3 peer still
  resolves and npm ci stays clean)
- vendor the base-vega Field family into components/shared/form as forwardRef
  components on the repo's cva.config, since base-vega ships no form primitive
  and its field source imports class-variance-authority and is React 19 style
- add a FormField bridge that binds a react-hook-form Controller to the Field
  layer and wires label, description, and error ids into aria attributes
- add pickDirty, which narrows a submitted body to the top-level keys the user
  actually touched so a partial update stops re-sending untouched fields

pickDirty reads dirtiness at the top level because react-hook-form tracks it
per leaf, so an edited array arrives as [true, false] and a cleared list as an
empty array that still carries its default-length dirty markers; the falsy
clear tokens (null, [], {}, 0, false) all survive.

Tests cover the Field primitives, the FormField aria wiring against a live
zod resolver, and pickDirty both as a unit and driven through a real
react-hook-form instance.

* test(ui): lock pickDirty behavior on a pure field-array reorder

react-hook-form compares each array element to its default positionally by
value, so useFieldArray move/swap and a reordered scalar array all mark the
moved indices dirty and pickDirty sends the whole array; a swap of two equal
elements is a value-level no-op and is correctly omitted. Covers the reorder
case a review flagged as untested.
2026-07-21 15:17:58 -07:00
yuneng-jiang
bc9534085b
feat(ui): add controlled row selection to the shared DataTable (#34167) 2026-07-21 22:08:08 +00:00
Yassin Kortam
c2ae52709c
feat(proxy): make DB config-reload interval configurable via config.yaml and UI (#34130)
The add_deployment and get_credentials background jobs that keep a multi-pod
deployment in sync with config-in-DB objects (models, credentials, guardrails,
general settings, etc.) polled the database on a hardcoded 30s interval, with
no way to trade convergence latency against DB load.

Expose it as the general_setting proxy_config_reload_interval_seconds (env
PROXY_CONFIG_RELOAD_INTERVAL_SECONDS parsed via get_env_int, default 30),
threaded like the existing proxy_batch_polling_interval knob, and surface it on
the admin general-settings page so it is reachable from the dashboard and
persists to the DB for all pods. Non-positive values are rejected at the UI
(gt=0) and fall back to 30s with a warning on the env/config/DB paths.
2026-07-21 22:03:22 +00:00
ryan-crabbe-berri
e1afc64baa
refactor(ui): migrate inline provider logo lookups to the shared Logo component (#34141)
* fix(ui): bundle provider logos as static imports and unify fallback in Logo component

providerLogoMap values are now content-hashed bundle URLs emitted by
static imports instead of /ui/assets/logos/ path strings, so any
deployment that serves the app JS also serves the logos: dev server,
proxy /ui mount, server_root_path sub-paths, and the split-chart nginx
image where the old route 404d in production. A missing file is now a
build error instead of a silent runtime 404.

resolveLogoSrc passes /_next/ URLs through untouched so bundled values
never get double-prefixed with the server root path. The new Logo
molecule owns resolution and the letter-avatar fallback and warns with
the failing URL on load error; ProviderLogo delegates to it. The three
bare img sites in the agents wizard render through Logo, fixing their
broken-image bug.

Dashscope now uses qwen.png, RunwayML the on-disk runway.png, and the
GradientAI entry is removed (no plausible asset exists). soniox.svg and
ai21.svg drop a single mismatched intrinsic dimension attribute that
Turbopack's import-time image parser rejects. Dead logoSrc lookup in
AddModelForm deleted. Vitest resolves image imports to Next's
StaticImageData shape via a config plugin so tests exercise the same
/_next/ URLs as production.

* fix(ui): retry logo load when src changes after an error

Track which src errored instead of a boolean so a Logo instance whose
source changes in place (agents modal title) attempts the new URL
rather than staying on the letter-avatar until remount.

* refactor(ui): migrate inline provider logo lookups to the shared Logo component

Patterns B, C, and E from the logo consolidation: every inline
providerLogoMap lookup feeding a bare img with a hand-rolled DOM
fallback now renders through Logo (credential modal, vector store
create/info views, cost tracking margin and discount forms and tables).

getProviderDisplayInfo, handleImageError, and ProviderDisplayInfo are
deleted; getProviderLogoAndName is a strict superset of the exact-match
helper. The vector store logo map no longer duplicates provider logo
paths: shared entries reference providerLogoMap and the three
vector-store-only logos become static imports. The map itself stays
because milvus and s3_vectors have no Providers enum equivalent.

Sites that rendered nothing for an unmapped provider now render the
letter avatar. Representative tests per pattern assert the rendered img
src against providerLogoMap so a wrong provider-to-enum mapping fails,
plus letter-avatar fallbacks for unmapped providers.

* fix(ui): resolve vector store slugs through the vector store logo map

The vector store info provider badge fed backend slugs like pg_vector,
milvus, and s3_vectors to getProviderLogoAndName, which only knows LLM
providers, so those stores showed a letter avatar and a raw slug. The
pre-existing inline lookup had the same wrong-domain bug via
provider_map. Reinstate getVectorStoreProviderLogoAndName resolving
through vectorStoreProviderMap first with a fallback to the LLM
resolver, so vector-store-only providers get their own logo and display
name for the first time.
2026-07-21 14:28:05 -07:00
ryan-crabbe-berri
2e8403a073
fix(ui): bundle provider logos as static imports and unify fallback in Logo component (#34125)
* fix(ui): bundle provider logos as static imports and unify fallback in Logo component

providerLogoMap values are now content-hashed bundle URLs emitted by
static imports instead of /ui/assets/logos/ path strings, so any
deployment that serves the app JS also serves the logos: dev server,
proxy /ui mount, server_root_path sub-paths, and the split-chart nginx
image where the old route 404d in production. A missing file is now a
build error instead of a silent runtime 404.

resolveLogoSrc passes /_next/ URLs through untouched so bundled values
never get double-prefixed with the server root path. The new Logo
molecule owns resolution and the letter-avatar fallback and warns with
the failing URL on load error; ProviderLogo delegates to it. The three
bare img sites in the agents wizard render through Logo, fixing their
broken-image bug.

Dashscope now uses qwen.png, RunwayML the on-disk runway.png, and the
GradientAI entry is removed (no plausible asset exists). soniox.svg and
ai21.svg drop a single mismatched intrinsic dimension attribute that
Turbopack's import-time image parser rejects. Dead logoSrc lookup in
AddModelForm deleted. Vitest resolves image imports to Next's
StaticImageData shape via a config plugin so tests exercise the same
/_next/ URLs as production.

* fix(ui): retry logo load when src changes after an error

Track which src errored instead of a boolean so a Logo instance whose
source changes in place (agents modal title) attempts the new URL
rather than staying on the letter-avatar until remount.
2026-07-21 14:27:36 -07:00
ryan-crabbe-berri
1cc70f84c0
fix(ui): distinguish response cache from provider prompt caching (#34138)
* fix(ui): distinguish response cache from provider prompt caching

The log detail drawer labeled LiteLLM's response cache result as
"Cache Hit" and rendered a red "false" tag next to provider prompt
cache token counts, which read as prompt caching being broken. The
row is now labeled "Response Cache" with an explanatory tooltip,
shows a neutral "Miss" tag instead of a red one, and the prompt
cache token rows are prefixed with "Prompt Cache" and get their own
tooltips. Cost breakdown line items get the same prefix.

The Caching dashboard only reports response cache analytics but never
said so; it is renamed to "Response Cache" in the sidebar, gains a
scope description pointing to the Usage page and Logs for prompt
caching, and the ambiguous "Cached Tokens" stat card is renamed to
"Cached Completion Tokens".

* test(ui): cover renamed Response Cache sidebar item in e2e

The sidebar navigation spec now clicks the renamed "Response Cache"
item and asserts it routes to /ui/caching, and the menu label fixture
maps the new label while keeping "Caching" as a legacy alias.
Verified by running sidebar.spec.ts through run_e2e.sh (full harness:
built UI served by the proxy, seeded postgres); both tests pass.

* feat(ui): link cache tooltips and dashboard description to docs

The Response Cache tooltip links to the proxy caching docs and the
two prompt cache token tooltips link to the prompt caching docs, so
users can jump straight to the explanation of whichever mechanism
they are looking at. The Response Cache dashboard description links
both docs pages the same way.
2026-07-21 14:26:26 -07:00
ryan-crabbe-berri
b47fe730a4
fix(ui): stop cloning body-carrying requests into stream uploads in fetchClient middleware (#34122)
* fix(ui): stop cloning body-carrying requests into stream uploads in fetchClient middleware

The openapi-fetch middleware rebuilt every outgoing request with
new Request(url, request), which converts a string JSON body into a
ReadableStream with duplex=half. Chromium only allows streaming uploads
over HTTP/2 or HTTP/3, so against any HTTP/1.1 hop (uvicorn serves
HTTP/1.1 only) the fetch dies at the network layer with
net::ERR_ALPN_NEGOTIATION_FAILED, surfaced as "Failed to fetch".

GET callers were unaffected (null body); the first body-carrying caller
arrived with the MCP BYOK credential modal, breaking that flow on plain
http deployments in the v1.94.0 RCs.

The middleware now mutates headers on the original request when no
runtime base is registered, and when rebasing onto a runtime base it
rebuilds the request with the body materialized as bytes via
arrayBuffer(), which fetch sends with Content-Length instead of a
streaming upload

* Update ui/litellm-dashboard/src/lib/http/api.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-07-21 14:26:02 -07:00
ryan-crabbe-berri
f9300d4312
fix(e2e): pin harness Python and surface proxy boot crash output (#34157)
run_e2e.sh let uv resolve any system Python; on machines where that is
3.14 the locked uvloop 0.21.0 fails to import (BaseDefaultEventLoopPolicy
was removed from asyncio.events) and the proxy dies at boot, which the
harness reported only as a misleading 180s health timeout. Pin the
interpreter to 3.13 (overridable via UV_PYTHON) and redirect proxy output
to a log file whose tail is printed when the proxy exits early or never
becomes healthy.
2026-07-21 21:17:43 +00:00
ryan-crabbe-berri
d2819baf0a
feat(ui): add block/unblock key action to key info page (#34116)
Adds a Block Key / Unblock Key action to the key info page, wired to the
existing /key/block and /key/unblock endpoints which previously had no UI.
The Reset Spend and Delete Key buttons move together with it into a new
overflow dropdown next to Regenerate Key, and a red Blocked tag shows next
to the key alias while the key is blocked.
2026-07-21 13:41:10 -07:00
ryan-crabbe-berri
ee0028a841
feat(ui): surface key budget_reset_at in key info and keys table (#34113)
* feat(budgets): add configurable budget_reset_time of day

Budgets reset at midnight in the configured timezone with no way to control
the time of day, so a drained daily budget surfaces as an overnight incident.
Add a litellm_settings.budget_reset_time option (e.g. "12:00") that shifts
day/week/month resets to a configurable wall-clock time in the existing
timezone, so the end of the budget window lands during business hours.

The reset time is parsed once into an immutable BudgetResetSettings and
injected into the reset job (constructor) and computation, rather than read
from a module-level global at call time. A malformed value fails fast at
startup. Sub-day durations ignore the offset. Unset preserves midnight resets.

* feat(ui): surface key budget_reset_at in key info and keys table
2026-07-21 13:35:14 -07:00
ryan-crabbe-berri
01d624e860
fix(ui): add tooltip to the Active key status badge (#34109) 2026-07-21 12:01:17 -07:00
yuneng-jiang
212a9213c4
refactor(ui): migrate agents table onto the shared DataTable (#34089)
* refactor(ui): migrate agents table onto the shared DataTable

Replace the hand-rolled tremor table inside AgentsPanel with the shared
DataTable, splitting the surface into a data-owning panel, a thin
AgentsTable consumer, and a getAgentsTableColumns definition composed
from the shared cell library.

Row delete moves from an inline icon button into the per-row overflow
menu, and the health-check toggle moves into the table toolbar since it
controls which rows the server returns. The loading skeleton is now
initial-load-only, so refetches keep the current rows on screen.

Drops the last @tremor/react import from AgentsPanel, so its
grandfathered eslint suppressions are pruned from the baseline.

* fix(ui): keep agents ordering and token changes correct in the migrated table

Sorting by created_at went through a raw accessor, and TanStack places
undefined ahead of real values, so an agent with no created_at jumped to
the top of the newest-first list. The pre-migration sort coerced a
missing date to epoch 0 and sorted it last; restore that by sorting on a
derived timestamp.

Reload the list when the access token changes rather than leaving the
previous token's rows on screen: show the skeleton for the new token,
drop the rows if that load fails, and ignore a superseded response so a
slow earlier request cannot overwrite newer rows. Refetches triggered by
delete or the health-check toggle still keep their rows.

Tests also reset the networking mocks between cases so an unconsumed
mockResolvedValueOnce queue cannot leak into the next test.
2026-07-21 10:28:49 -07:00
yuneng-jiang
4647f85958
Merge pull request #34078 from BerriAI/litellm_/elated-thompson-4a0c84
refactor(ui): migrate access groups table to shared DataTable
2026-07-21 10:28:21 -07:00
yuneng-jiang
5beb0a735d
Merge pull request #34079 from BerriAI/litellm_/wizardly-bardeen-a47430
refactor(ui): migrate memory table onto shared DataTable
2026-07-21 10:28:10 -07:00
yuneng-jiang
0b4851dd81
Merge pull request #34081 from BerriAI/litellm_/gallant-kapitsa-a809e0
refactor(ui): migrate organizations table onto shared DataTable
2026-07-21 10:27:59 -07:00
yuneng-jiang
925beda06d
Merge pull request #34080 from BerriAI/litellm_/brave-bell-58da35
refactor(ui): migrate audit logs table onto shared DataTable
2026-07-21 10:27:48 -07:00
ryan-crabbe-berri
e20d3d4ecc
fix(ui): serve /ui/assets from the nginx image instead of SPA fallback (#34066) 2026-07-21 09:22:02 -07:00
Yuneng Jiang
ff8d8797dd
test(ui): pin memory table page-size behavior on the last page
Changing rows-per-page while on the last page recomputes the page
index from the top visible row, so the table lands on the new last
page instead of an out-of-range one. Pin that, since it depends on
the parent holding the full PaginationState rather than just the
page index.
2026-07-20 23:26:27 -07:00
Yuneng Jiang
879a287ba9
refactor(ui): migrate organizations table onto shared DataTable
The organizations admin table was a hand-rolled tremor/antd table in a single
snake_case file. This moves it onto the shared DataTable and cell library the other
migrated tables use, splitting it into a data-owning OrganizationsPanel, a thin
OrganizationsTable consumer, and a getOrganizationsTableColumns module

The models column no longer uses a per-row accordion whose expand state lived in the
parent; it renders the shared ModelsCell with truncation and a "+N more" tooltip,
matching every other table with a models column. Row actions (Edit, Delete) move into
a per-row overflow menu gated to proxy admins, while the detail view, create modal,
and delete modal stay in the panel. The server-side org id / org alias search stays
wired to the useOrganizations hook, and the table gains an initial-load skeleton plus
a search-aware empty state. The dead sort_by / sort_order filter fields, the misnamed
"Info" column that only ever showed a member count, and an unused refresh affordance
are dropped; the default created_at descending sort is preserved
2026-07-20 22:24:28 -07:00
Yuneng Jiang
e3b453fd0a
refactor(ui): migrate audit logs table onto shared DataTable
Move the Audit Logs table off the hand-rolled antd Table/Pagination onto the
shared DataTable and cell library, matching the other migrated admin tables
(Teams, Virtual Keys, Guardrails)

The single audit_logs.tsx is split into three PascalCase files: AuditLogsPanel
owns the data (server useQuery, pagination and filter state, the row-detail
drawer, and the enterprise preview gate), AuditLogsTable is a thin DataTable
consumer, and AuditLogsTableColumns exposes getAuditLogsTableColumns. The
AuditLogEntry type moves out of the request-logs columns.tsx into the audit
columns file, and AuditLogDrawer stays in the parent unchanged

Server pagination is wired through paginationMode="server" with the shared
footer replacing the standalone antd Pagination, keeping keepPreviousData
semantics so page flips keep rows visible and only the initial load shows the
skeleton. The six filters (Object ID, Changed By, Team ID, Key Hash, Action,
Table) move into a DataTableFilterDrawer plus toolbar with active-filter chips,
each resetting the page to the first. The Object ID cell is the clickable
identity cell that opens the drawer; there is no whole-row navigation, no
selection, and no per-row actions since the table is read-only

The enterprise query is now also gated on premiumUser so the preview path no
longer fires a doomed request for non-premium users
2026-07-20 22:23:54 -07:00
Yuneng Jiang
3a55dda7ec
refactor(ui): migrate memory table onto shared DataTable
Move the admin dashboard Memory table off the hand-rolled antd
<Table> onto the shared DataTable and cell library, matching the
pattern already used by Teams, Virtual Keys, and Guardrails.

MemoryView keeps the data (server useQuery, mutations) and owns the
detail drawer, edit modal, and delete modal; it now renders a thin
MemoryTable consumer plus a getMemoryTableColumns columns file. The
server pagination moves the full PaginationState up to the parent so
the shared footer's rows-per-page selector works, the key-prefix
search runs through the shared toolbar and resets the page on change,
and per-row view/edit/delete collapse into a single overflow menu.
Sorting stays off since the backend returns updated_at DESC.

The old page-reset effect is gone (the page now resets inside the
search handler), so its react-hooks/set-state-in-effect suppression
is pruned. The detail drawer moves into its own MemoryDetailDrawer
component to keep the parent under the complexity budget.
2026-07-20 22:22:28 -07:00
Yuneng Jiang
484cc12ab6
fix(ui): ignore stale available-teams fetch on unmount or token change 2026-07-20 20:35:51 -07:00
Yuneng Jiang
99f215df68
refactor(ui): migrate available teams table onto shared DataTable 2026-07-20 20:08:42 -07:00
yucheng-berri
9ad8698aab
feat: add deepkeep as custom guardrail (#33844)
* adding deepkeep as custom guardrail

* adding deepkeep as a custom guardrail

* adding deepkeep as a custom guardrail (hooks)

* adding litellm/proxy/_experimental/out/ to .gitignore

* adding deepkeep as custom guardrail in litellm

* removing sentinel_fortress

* comparing schema.prisma files

* fix(deepkeep): address greptile review comments

- extra_headers: fix type annotation (list -> Dict[str, str]) and actually
  merge them into _build_request_headers() so user-configured headers
  reach the DeepKeep API
- user_api_key_hash: only fall back to user_api_key_token when no
  explicit hash is already set, avoiding silent overwrite
- apply_guardrail: preserve tool_calls and structured_messages in the
  return value so downstream callers don't lose that content

Adds tests for all four fixes.

* fix(deepkeep): address greptile review comments

- extra_headers: fix type annotation (list -> Dict[str, str]) and actually
  merge them into _build_request_headers() so user-configured headers
  reach the DeepKeep API
- user_api_key_hash: only fall back to user_api_key_token when no
  explicit hash is already set, avoiding silent overwrite
- apply_guardrail: preserve tool_calls and structured_messages in the
  return value so downstream callers don't lose that content

Adds tests for all four fixes.

* fix: add missing __init__.py and allowlist entries for upstream merge

- tests/test_litellm/proxy/client/__init__.py: fixes pytest collection
  collision with tests/test_litellm/models/test_models.py (same basename)
- tests/test_litellm/models/__init__.py: same fix
- backend/routes/allowlist.py: add /config_overrides/ and /v1/unified_access_group
  prefixes for new routes added by upstream

* fix(ui/tests): resolve frontend-lint failures in new test files

- useLogDetails.test.ts: add Wrapper.displayName, replace 'null as any'
  with null, type resolveCall promise resolver properly
- usePaginatedDailyActivity.test.ts: remove unused waitFor import,
  add Wrapper.displayName, change Record<string,any> to Record<string,unknown>
- UsageViewSelect.adminFiltering.test.tsx: replace all props:any with
  explicit SelectProps/BadgeProps/SelectOption types, replace (X as any).displayName
  with direct X.displayName assignment

no-explicit-any count: 2034 (budget: 2040). Prettier check: clean.

* fix(ui): sync proxy/_experimental/out/ exactly to upstream

245 stale JS chunk files from earlier merges were left in the out/
directory but had been deleted in upstream. The Docker image in CI is
built by copying this directory verbatim, so the stale artifacts caused
the SERVER_ROOT_PATH redirect E2E to fail.

Synced by: git checkout upstream/litellm_internal_staging -- out/ (adds
new files) + git rm on every file present in HEAD but absent from
upstream.

* Update litellm/proxy/guardrails/guardrail_hooks/deepkeep/deepkeep.py

Co-authored-by: veria-ai[bot] <224490171+veria-ai[bot]@users.noreply.github.com>

* fix(makefile): fall back to upstream/litellm_internal_staging for strict-budget gate

origin/litellm_internal_staging exists on BerriAI's CI but not on forks
that use a different remote name (e.g. Azure DevOps as origin).  Fall
back to upstream/litellm_internal_staging when the origin ref is absent.

* linter reformat

* fix(deepkeep): apply guardrail tool/tool_call redactions from API response

When DeepKeep returns GUARDRAIL_INTERVENED with redacted tools or
tool_calls, the previous code ignored those redactions and forwarded
the original (potentially sensitive) values to the model — a guardrail
bypass for content embedded in tool schemas or function arguments.

Fix: prefer response_json["tools"] / response_json["tool_calls"] when
present, falling back to the originals only when the guardrail did not
return replacements — consistent with the existing pattern for texts and
images.

Refactor _build_return_inputs() into a private static helper to keep
apply_guardrail() under the PLR0915 statement limit (50).

Adds test_apply_guardrail_applies_tool_redactions_from_response to
assert that redacted tool payloads from the API response are used.

* Update litellm/proxy/guardrails/guardrail_hooks/deepkeep/deepkeep.py

Co-authored-by: veria-ai[bot] <224490171+veria-ai[bot]@users.noreply.github.com>

* fix(lint): move base-ref fallback into ruff_strict_gate.py; revert Makefile

The previous Makefile fix had a shell bug: 'git rev-parse --verify'
writes the resolved SHA to stdout, so the $$(...) substitution captured
both the SHA and the echo output, handing '--base <sha>\norigin/...' as
two tokens to the Python script, causing exit code 1 in CI.

Fix: revert Makefile to its original single-line invocation and add
_resolve_base() to ruff_strict_gate.py. The function checks whether the
requested ref resolves; if not, it tries the 'upstream/' equivalent
before falling back to the original ref (letting git emit a clear error).

Behaviour in BerriAI CI: origin/litellm_internal_staging resolves → used
as before, no change.
Behaviour on forks with a different 'origin': falls back to
upstream/litellm_internal_staging transparently.

* fix(lint): fix UP006/UP045/F401 in changed files; add depth guard to check_any_discipline

- Replace Dict/List/Optional/Tuple typing imports with built-in equivalents
  (UP006, UP045) across files touched in this PR diff, then clean up
  the now-unused typing imports (F401).
- Add _MAX_CONTAINS_ANY_DEPTH guard to check_any_discipline.contains_any()
  to prevent RecursionError on deeply-nested mypy types.

* fix(lint): resolve all three CI lint job failures

1. lint (ruff_strict_gate) — UP006/UP045/F401 violations introduced on
   changed lines. Fixed Dict/List/Optional/Tuple → built-in equivalents
   across every file in the PR diff; cleaned up now-unused typing imports.

2. any-discipline — RecursionError in check_any_discipline.contains_any()
   on deeply-nested mypy types. Upstream fixed this by converting to an
   iterative stack-based algorithm (merged). Also added deepkeep.py to
   any-discipline-budget.json via 'make lint-any-budget-update' so the
   new file's Any count is baselined instead of failing against the
   zero-baseline default.

3. basedpyright reportMissingParameterType — **kwargs in DeepKeepGuardrail
   __init__ lacked a type annotation. Added **kwargs: Any.

* Update litellm/deepkeep_tilt_config.yaml

Co-authored-by: veria-ai[bot] <224490171+veria-ai[bot]@users.noreply.github.com>

* fix(lint): black reformat after merge

* fix(deepkeep): honour empty-list replacements in _build_return_inputs

When DeepKeep returns GUARDRAIL_INTERVENED with an intentional empty
replacement (e.g. texts:[], tool_calls:[]) the previous truthiness check
treated [] as absent and forwarded the original content downstream —
a guardrail bypass for any case where the firewall wants to fully clear
a field.

Fix: replace all response_json.get(field) truthiness checks with
'is not None' comparisons so that an empty list is respected as a
deliberate replacement. Applies to texts, images, tools, tool_calls,
and the original-input fallback guards.

Adds test_apply_guardrail_honours_empty_list_replacements.

* fix(test): replace live httpbin.org call with mocked transport in test_pass_through_with_httpbin_redirect

Root cause of OOM: the test made a real HTTP request to https://httpbin.org
inside a pytest-xdist worker. Under memory pressure the worker's httpx client
and redirect-following logic allocated enough virtual memory to trip the OOM
killer (confirmed by ulimit -v 16GB reproducing the crash with 'node down: Not
properly terminated' on this exact test).

Fix: replace the real network call with a custom httpx.AsyncBaseTransport that
returns a pre-built 302 -> 200 response sequence in-memory. The test now runs
hermetically with no network dependency and no excess memory allocation.

ulimit -v 16GB: 24,284 passed (0 crashes) after this fix.

* fix: merge upstream/litellm_internal_staging (197 commits), resolve conflicts

7 conflicts resolved:
- 6 Python files: upstream added new code with old-style typing (Optional,
  Dict, List) on lines where we had ruff-fixed modern syntax (str | None,
  dict, list). Took upstream's version then re-ran ruff UP006/UP045/F401
  --fix to keep both the new content and ruff compliance.
- test_openapi_compliance.py: upstream replaced 'role' with 'steps' in
  output_fields and updated the spec comment. Took upstream's version.

Also: added _resolve_base() fallback to type_check_gate.py and removed
the hard 'git fetch origin litellm_internal_staging' from the Makefile's
lint-basedpyright target (same pattern as ruff_strict_gate.py fix).

* fix: merge upstream (41 commits), resolve .gitignore conflict, fix BLE001

- .gitignore: upstream removed package.json/out/ ignore entries; took theirs
- deepkeep.py: added '# noqa: BLE001' on catch-all Exception handler
  (BLE001 rule newly enforced in ruff-strict-budget)
- type_check_gate.py: added _resolve_base() fallback for basedpyright gate
- Makefile: removed hard 'git fetch origin' from lint-basedpyright target

* fix: merge upstream (57 commits), resolve conflicts

- Makefile: upstream added lint-fetch-base target; made it tolerant of
  missing origin/litellm_internal_staging (git fetch || true)
- test_websearch_chat_completion.py: took upstream's new assertions and
  skipif marker
- anthropic_cache_control_hook.py: upstream added new code using List/Dict/Tuple
  which were undefined after our earlier UP006 cleanup; replaced with
  built-in list/dict/tuple

* fix(coverage): revert ruff UP006/UP045 changes on upstream files

The previous ruff fixes (Dict→dict, Optional→X|None) on 7 upstream files
added ~500 changed lines of pure type-annotation no-ops to our PR diff.
codecov/patch penalised these uncovered lines, dropping patch coverage
to 51.35% (target 61.83%).

Fix: revert these files to exactly match upstream/litellm_internal_staging.
The ruff_strict_gate still passes because the violations exist equally in
both the base and HEAD (total == base_count → no breach).

* fix: merge upstream (130 commits), resolve Makefile + base_email conflicts

- Makefile: upstream changed lint deps to $(LINT_DEP_INSTALL)/$(LINT_DEP_BASE);
  kept our --base removal (handled by _resolve_base in Python scripts)
- base_email.py: took upstream's dedup cache addition
- deepkeep.py: ruff format after merge

* chore: remove lint/format-only changes and non-feature files

Revert all lint-infra and black/ruff-reformat-only changes back to
upstream/litellm_internal_staging so the PR diff shows only the DeepKeep
guardrail feature:
- Makefile, scripts/ruff_strict_gate.py, scripts/type_check_gate.py
  (lint-gate infra)
- credential_migration.py + enterprise/* + assorted test files
  (black-reformat / xdist test-isolation drift)
- backend/routes/allowlist.py (merge glue)
Remove non-feature local artifacts: build-and-push.sh,
deepkeep_tilt_config.yaml, stray __init__.py collision shims, and
unrelated UI test files.

* fix(lint): add reason to BLE001 noqa to satisfy type-discipline gate (LIT003)

The type-discipline budget ratcheted LIT003's ceiling to 292 as upstream
fixed reasonless suppressions, so our '# noqa: BLE001' (code but no
reason) tipped the total to 293 and failed CI. Add a reason per the
required '# noqa: CODE  # <reason>' shape.

* fix(deepkeep): apply structured_messages redactions returned by the guardrail API

_build_return_inputs dropped any structured_messages the DeepKeep API returned and
always forwarded the original input, so redactions on that field never took effect.
Check the response first, same as texts/images/tools/tool_calls

* chore(ui): drop redundant preserve prop from the guardrail form

preserve defaults to true in rc-field-form (isMergedPreserve falls back to true when
unset), so the explicit prop changed nothing and only widened this PR's blast radius
to every guardrail provider in the shared form

* fix(deepkeep): stop extra_headers list from crashing the guardrail call and name the real firewall id config key

litellm_params.extra_headers is a list of header names to forward, so passing it
straight into dict.update raised ValueError and, under fail_closed, took the request
down with it. Only merge mapping values and warn otherwise

The docstring example and the missing-secret error both said firewall_id, but
initialize_guardrail only reads deepkeep_firewall_id, so anyone following them
had their value silently ignored

* refactor(proxy): drop normalize_callback change; split to its own PR (#33905)

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

---------

Co-authored-by: Yaniv Israel <yaniv@deepkeep.ai>
Co-authored-by: DK-yaniv <164404355+DK-yaniv@users.noreply.github.com>
Co-authored-by: veria-ai[bot] <224490171+veria-ai[bot]@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-20 19:27:40 -07:00
yuneng-jiang
731efafbca
Merge pull request #34053 from BerriAI/litellm_/migrate-simple-table-631c9b
refactor(ui): migrate credentials table onto shared DataTable
2026-07-20 18:18:28 -07:00
yuneng-jiang
ea045300bc
Merge pull request #34056 from BerriAI/litellm_/osv-dependency-vulnerabilities-662a89
build(deps-dev): bump js-yaml to 4.3.0 and brace-expansion to 5.0.7
2026-07-20 18:13:44 -07:00
devin-ai-integration[bot]
9d6d6c4fe0
fix(agents): allow optional securityScheme fields so /public/agent_hub does not 500 (#33897)
Co-authored-by: shivam <shivam@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-20 18:13:09 -07:00
Yuneng Jiang
f2ca0a149b
build(deps-dev): bump js-yaml to 4.3.0 and brace-expansion to 5.0.7
Both are dev-only build and lint tooling in the dashboard, not part of the
browser bundle. The bumps pull in upstream maintenance releases that address
inefficient handling of certain inputs

js-yaml is force-pinned through the overrides block because
@redocly/openapi-core exact-pins an older copy; a plain lockfile change would
not hold since the tree re-spawns a nested stale version on re-resolution, so
the override moves from 4.2.0 to 4.3.0. brace-expansion is added to overrides
at 5.0.7 so npm install does not leave the previously resolved 5.0.6 in place.
Both resolve to a single deduped copy after the change
2026-07-20 17:46:20 -07:00
Yuneng Jiang
368bfe19bf
fix(ui): surface add/update credential failures with an error toast
The add and update handlers had no try/catch (carried over from the legacy
panel), so a failed credentialCreateCall / credentialUpdateCall became an
unhandled rejection: no error notification and the modal left open with no
feedback. Bring them in line with the co-located delete handler by catching
and calling NotificationsManager.error, keeping the modal open on failure so
the user can retry. Add panel tests for the success (modal closes, refetch,
success toast) and failure (error toast, modal stays open) paths.
2026-07-20 17:37:24 -07:00
Yuneng Jiang
c06c16b0bf
refactor(ui): migrate credentials table onto shared DataTable
Move the Credentials panel off its hand-rolled tremor table onto the shared
DataTable and cell library, matching the SimpleTable design and the sibling
Vector Stores / Guardrails tables. Split the panel into a modal-owning parent
(CredentialsPanel), a thin client-mode DataTable consumer (CredentialsTable),
and a CredentialsTableColumns factory.

Credential Name and Provider render as shared cells (IdentityCell + provider
logo via getProviderLogoAndName); the per-row edit/delete icons become a right
-aligned overflow menu (Edit, Copy credential name, Delete). Admin-viewer read
parity is preserved: viewers still see the list but get no actions column.
Detail/edit and delete modals stay in the parent, so the public prop stays
uploadProps only.

Drops the now-unused tremor import (pruning its eslint suppression) and the
dead antd Form handle.
2026-07-20 17:09:55 -07:00
tin-berri
43e4af73f0
Merge pull request #33631 from BerriAI/litellm_lit4517_messages_mcp_gateway
feat(mcp): support MCP servers on the Anthropic /v1/messages API
2026-07-20 16:22:03 -07:00
yuneng-jiang
9ac6cc7bee
Merge pull request #33885 from BerriAI/litellm_/global-guardrails-display-b172b4
fix(ui): hide guardrail group headers when only one group has entries
2026-07-20 15:30:38 -07:00
devin-ai-integration[bot]
34561482ed
feat(ui): add configuration tabs to the Cost Optimization page (#33899)
* feat(ui): add configuration tabs to Cost Optimization page

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

* feat(ui): reuse AutoRouter v2 and Router Settings prompt-caching panel in Cost Optimization; clarify Headroom compression

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

* feat(ui): add experimental dashboard banner with feedback discussion link to Cost Optimization

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

* feat(ui): add savings methodology note and per-key/team compression enterprise callout to Cost Optimization

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

* test(ui): assert active tab state in Cost Optimization tab-switch test

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

---------

Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-20 07:30:49 -07:00
devin-ai-integration[bot]
cc45d18e9c
feat(complexity-router): add return_raw_model_name toggle for response model field (#33875)
* feat(complexity-router): optionally return raw model name

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

* test(proxy): restore asyncio import

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

* chore(tests): preserve staging asyncio import

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

* test(proxy): drop unused local asyncio import

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

* feat(dashboard): add complexity router raw model toggle

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

* refactor(complexity-router): move metadata key constant to constants.py

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

* style(proxy-tests): preserve module spacing

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

---------

Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-18 19:24:56 -07:00
tin-berri
3f3295b33f
feat(spend): track prompt compression saved tokens in daily spend aggregates (#33810)
* feat(spend): track prompt compression saved tokens in daily spend aggregates

Native compression interception now records tokens_before/after/saved into the
request litellm_metadata so savings land in the SpendLog metadata JSON under a
typed compression_savings key. A single normalizer
(extract_compression_saved_tokens) sums that key with Headroom guardrail
tokens_saved; the two writers are disjoint and run at different stages, so
summing never double-counts. The spend-log redactor now preserves purely
numeric compression stats inside guardrail_response so Headroom savings
survive the store_prompts_in_spend_logs=false default. compression_saved_tokens
is threaded through BaseDailySpendTransaction, queue aggregation, the daily
upsert blocks, a new BigInt column on all six daily spend tables, and the
daily activity read path (SpendMetrics, DailySpendMetadata, raw-SQL rollups)

* fix(spend): normalize legacy guardrail shapes and float token stats in compression savings reader

* feat(spend): aggregate compression and prompt caching dollar savings in daily rollups

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

* test(spend): update daily spend aggregation fixtures for savings columns

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

* feat(ui): add Cost Optimization dashboard page

New left-nav Cost Optimization page under Observability that surfaces money saved by prompt compression and prompt caching. It reads the daily activity rollup (userDailyActivityCall / get_daily_activity) and never scans SpendLogs, so it stays fast at 1M+ rows.

Renders a Total saved card, per-driver Compression and Prompt caching cards, a savings-over-time area chart, and a savings-by-driver donut, all aggregated in memory from the per-day metrics.compression_savings_spend and metrics.prompt_caching_savings_spend fields.

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

---------

Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-18 17:47:54 -07:00
Yuneng Jiang
2e492f5cb7
fix(ui): hide guardrail group headers when only one group has entries
The team settings guardrails dropdown always rendered the Global and
Other headers, so a proxy with no global guardrails showed an empty
Global heading above the list.
2026-07-18 16:27:59 -07:00
devin-ai-integration[bot]
d495da4ce4
feat(chat-ui): add personal Logs view scoped to the current user (#33829)
* feat(chat-ui): add personal Logs view scoped to the current user

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

* fix(chat-ui): show request payload from proxy_server_request in logs detail

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

* fix(chat-ui): address logs panel review feedback (stable detail key, error state)

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

---------

Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-18 14:58:12 -07:00
yuneng-jiang
377d54e694
refactor(ui): migrate policy attachments table onto shared DataTable (#33827)
* refactor(ui): migrate policy attachments table onto shared DataTable

* refactor(ui): pass a specific success message to the attachment copy action
2026-07-18 11:37:39 -07:00
Yassin Kortam
e18966625d
feat(mcp): add ID-JAG (identity assertion authorization grant) support for MCP egress (#31516)
* feat(mcp): add ID-JAG egress auth as a v2 outbound-credentials arm

Adds the oauth2_id_jag MCP egress auth mode (draft-ietf-oauth-identity-assertion-authz-grant,
shipped by Okta as "AI agent token exchange") as a first-class arm of the v2
outbound_credentials resolver rather than a standalone v1 handler.

ID-JAG is a two-leg flow: an RFC 8693 token exchange swaps the caller's id_token for an
ID-JAG assertion at the IdP org authorization server, then an RFC 7523 jwt-bearer grant
presents that assertion to the MCP's resource authorization server for the access token
used to call the upstream. The gateway authenticates to both endpoints with a private-key
JWT client_assertion, falling back to client_secret when no key is configured.

The mode is modeled as IdJagConfig in the AuthConfig discriminated union, with client auth
as a ClientAuth tagged union (private_key_jwt or client_secret) so required fields are
enforced at construction and illegal states are unrepresentable. A new token_endpoint
collaborator performs the authenticated OAuth token-endpoint call and caches the result
with per-key single-flight; the resolver's _id_jag arm runs the two legs and returns an
httpx.Auth or a typed CredError. A missing caller identity token fails closed
(precondition_required), so an ID-JAG server never falls back to a static credential. The
v1->v2 adapter maps oauth2_id_jag servers onto IdJagConfig and the existing live v2 path
resolves them, so no standalone handler, has_id_jag_config flag, or resolve_mcp_auth
precedence branch is needed.

The ID-JAG client_private_key is encrypted at rest alongside client_secret.

* fix(mcp): sort token_endpoint imports to satisfy the I001 budget gate

* fix(mcp): give token_endpoint pyright suppressions reasons for the LIT004 budget

The freshly-merged base ratcheted the LIT004 ceiling down, so the six
unexplained pyright suppressions in token_endpoint.py went over budget.
Annotate each with why the boundary is untyped (litellm http handler and
InMemoryCache are untyped; response.json() is validated by
_TokenEndpointResponse in fetch) so the gate counts them as explained.

* fix(mcp): enforce ID-JAG exchange over caller auth overrides and redact token endpoint from client errors

For oauth2_id_jag servers the v2 resolver mints the upstream assertion from the caller's identity token; a caller-supplied x-mcp-auth / x-mcp-<alias>-authorization override or a conflicting injected Authorization must not disable that exchange and forward an arbitrary bearer, so IdJagConfig now joins authorization_code and token_exchange as a resolver-owned mode that keeps the v2 spec and ignores the override.

The token endpoint error branches previously returned the configured endpoint URL in the client-visible 503 detail. The endpoint now stays in server-side logs and clients get a generic token-exchange failure.

* fix(mcp): bind the ID-JAG token cache to the exchange config and map token endpoint network errors to typed CredErrors

* fix(mcp): fail closed when an oauth2_id_jag server is half-configured instead of deferring to v1 static credentials

* fix(mcp): evict the cached ID-JAG bearer on an upstream 401 so the retry re-exchanges

* fix(mcp): map an unsignable client assertion to a typed misconfigured error instead of an unhandled 500

* fix(mcp): redact credential fields from the server-registry debug dump
2026-07-18 11:36:25 -07:00
ryan-crabbe-berri
6f4f4f69df
refactor(ui): consolidate Add/Edit credential modals into one CredentialModal (#32572)
* refactor(ui): consolidate Add/Edit credential modals into one CredentialModal

AddCredentialModal and EditCredentialModal were ~90% identical: the same
provider select, ProviderSpecificFields, and submit/filter logic, differing
only in title, button text, edit-mode prefill, and the disabled credential
name. Replace both with a single CredentialModal driven by a mode: 'add' |
'edit' prop, and point the two call sites in credentials.tsx at it.

Removes ~120 lines of duplication and drops the no-explicit-any and
no-restricted-imports baselines. The two per-file tests merge into one
CredentialModal.test.tsx covering both modes (add: editable empty name;
edit: prefilled, disabled name; provider fields render).

* refactor(ui): derive credential name disabled state from mode, not data

The disabled flag on the credential name field was tied to whether
existingCredential?.credential_name is truthy, an artifact of the old
EditCredentialModal. Drive it from the isEdit flag like the rest of the
component so mode='add' with a stray existingCredential can't disable the
field and mode='edit' with an empty name can't leave it editable. Behavior
is unchanged for real call sites; adds a regression test for the edit-with-
empty-name case.

* refactor(ui): prefill credential form declaratively instead of via useEffect

The edit-mode form was seeded with an imperative form.setFieldsValue inside
a useEffect that also set React state (setSelectedProvider), an antd anti-
pattern carried over from the old EditCredentialModal. Both call sites mount
the modal fresh with existingCredential already present (conditional && plus
destroyOnHidden), so there is no 'prop arrives after mount' case to handle.

Replace it with antd's declarative initialValues on the Form and a lazy
useState initializer for the provider. Removes the effect, its
react-hooks/set-state-in-effect suppression and exhaustive-deps warning, and
one any cast; behavior is unchanged (edit now shows the real provider on
first paint instead of flashing the default). Existing tests cover prefill
and the disabled name field.
2026-07-18 18:24:03 +00:00
tin-berri
3ba5266ab3
Merge pull request #33581 from BerriAI/litellm_lit4478_anthropic_auto_cache_ui
feat(ui): configure Anthropic automatic prompt caching from the Admin UI
2026-07-17 23:15:58 -07:00
yucheng-berri
f759c75466
feat: add Straiker guardrail integration (#33781)
* feat: add Straiker guardrail integration

Implements LLM security guardrails via Straiker with prompt and response inspection, multi-mode execution (pre_call, post_call), and configurable blocking or redaction of flagged content across providers, streaming, images, and tool calls.

* fix(guardrails): harden straiker source attribution and error-path consistency

Use the operator-configured source for Straiker application attribution instead of a caller-supplied agent_id metadata value, so a caller cannot spoof which application a detection is attributed to. Make _fail reuse _block so a post_call error raises ModifyResponseException like a deliberate post_call block rather than GuardrailRaisedException, and type the blocking helper as NoReturn so the type checker enforces that execution never falls through the BLOCKED branch. Serialize the webhook payload once and send it as raw content to avoid re-serializing on the size check and on every retry.

* fix(guardrails): read straiker config and metadata from all supported shapes

Handle a dict optional_params in _get_config_value so nested guardrail
settings loaded from YAML or the DB (timeout, unreachable_fallback, and
the rest) are applied instead of silently falling back to defaults;
previously only attribute-style access was supported. Build the webhook
metadata bag from the merged metadata so client tags stored under
litellm_metadata on routes like /v1/messages reach Straiker the same way
identity and application fields already do, and widen the internal-key
skip prefix to user_api so proxy-injected budget values are not
forwarded.

* fix(guardrails): fail safe on straiker interventions without redactions

Block instead of passing content through when Straiker returns
GUARDRAIL_INTERVENED without replacement texts, so a positive
intervention verdict can never silently forward the original flagged
content. Fix the streamed-request detection to read the request body
from proxy_server_request.body, where the proxy stores it, instead of a
top-level body key that is never populated; the previous fallback was
dead, so a streamed response whose stream flag was not lifted to the top
level would have been redacted rather than blocked while buffering
replayed the original chunks.

* revert(guardrails): restore straiker caller agent_id application attribution

Restore the original behavior where a request-scoped agent_id in metadata
sets the Straiker application source, falling back to the configured
source. This is the integration's intended per-application attribution;
litellm already resolves a key-owned agent_id ahead of any caller-supplied
value, so a configured key cannot be spoofed.

* revert(guardrails): restore straiker webhook metadata scoping

Restore the original behavior where the Straiker webhook metadata bag is
built from request-scoped metadata only. Forwarding litellm_metadata was
a scope change to what the integration sends to Straiker; keep the
author's intended scoping.

* fix(guardrails): keep proxy key material out of straiker webhook metadata

Widen the internal-key skip prefix from user_api_key_ to user_api so the
proxy-injected user_api_key hash and user_api_end_user_max_budget are not
copied into the Straiker webhook metadata bag. The narrower prefix missed
the bare user_api_key name, leaking the hashed key to the vendor. Keeps
the request-scoped metadata source unchanged.

---------

Co-authored-by: cs-mehta <chandra@straiker.ai>
2026-07-18 03:31:29 +00:00
ryan-crabbe-berri
577dd3b707
fix(ui): stop credential edit from persisting the masked api key (#33797)
Editing an existing LLM credential and changing only the api_base also
overwrote the stored api_key with its masked display value (e.g. sk****IA).
The edit form pre-fills fields from the credential the backend returns, whose
secrets come back masked, and the update handler sent every field straight
back; the endpoint then encrypted and stored the asterisks over the real key.

Run credential_values through stripMaskedSecrets before the PATCH so masked
placeholders are never sent, mirroring the guard the model edit form already
uses. The isMaskedSecret / stripMaskedSecrets helpers move out of
model_info_view into a shared utils module so both call sites share one
implementation.

Add a Playwright e2e that seeds a credential, edits only the api base in the
LLM Credentials tab, and asserts the outgoing PATCH no longer carries the
masked api_key while the new base persists.
2026-07-17 18:25:24 -07:00
yuneng-jiang
b94311481e
fix(ui): migrate tag deletion to shared DeleteResourceModal (#33795)
The tag delete action moved into a Base UI dropdown menu when the tags
table was migrated onto the shared DataTable. That menu is modal by
default and holds a pointer-events lock on the page while it opens and
closes, which left the hand-rolled inline confirmation modal unclickable,
so deleting a tag stopped working

Replace the inline modal with the shared DeleteResourceModal, which
renders through an antd Modal portal that manages its own pointer-events
and z-index, matching every other table's delete flow. Add a deleting
loading state so the confirm button reflects progress and cannot be
double-clicked

Cover the wiring with a regression test that drives the delete flow
through the shared modal and asserts tagDeleteCall runs with the tag name
2026-07-17 17:33:36 -07:00
Tin Chi Lo
4e5f488452 feat(ui): tighten the Prompt Caching descriptions
The toggle and ttl descriptions were a wall of text, with a panel intro that
mostly repeated the toggle description. Drop the intro and cut both descriptions
to one or two lines, keeping a one-clause note that the cache is shared across
callers on the same upstream credentials.
2026-07-17 12:16:09 -07:00
Tin Chi Lo
73cbbdd51d feat(ui): move Anthropic prompt caching to its own Router Settings tab
Rather than mixing the flag and its ttl into the generic General settings table
(which also surfaced the confusing Not Set / In Config / In DB provenance badges),
give prompt caching a dedicated tab with a purpose-built toggle and ttl dropdown.

Each registry field gains an optional tab, surfaced as ConfigList.field_tab, so
the General tab renders the ungrouped fields and the caching fields render on
their own tab. The update, persist and reset endpoints are unchanged.
2026-07-17 11:38:24 -07:00
Tin Chi Lo
9f7f53a82a refactor(ui): extract the General Settings value editor into a component
The value cell was a ternary chain over field_type; adding Select made it a fourth
level and tripped no-nested-ternary. Early returns read better than a deeper chain
and let the suppression baseline ratchet down.
2026-07-17 10:56:48 -07:00
Tin Chi Lo
1291962850 feat(ui): configure Anthropic automatic prompt caching from the Admin UI
Register enable_anthropic_prompt_caching and anthropic_prompt_caching_ttl on the
General Settings table so caching can be turned on without hand-writing config.

The registry could not express either field: validation was hardcoded to a float in
(0, 1], reset set every field to None (not a bool for a boolean flag), and the listing
reported any non-None value as 'In Config', which a False default would always trip.
Validation now dispatches on the declared type and reset restores each field's own
default. ConfigList carries field_options so the table can render a Select for enums
instead of no editor at all.
2026-07-17 10:56:48 -07:00
tin-berri
a7d01cb1ac
Merge pull request #33573 from BerriAI/litellm_lit4478_anthropic_auto_cache
feat(anthropic): add enable_anthropic_prompt_caching for automatic cache_control injection
2026-07-17 10:48:32 -07:00