Commit Graph
502 Commits
Author SHA1 Message Date
Yuneng Jiang 9b019aaa6b Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix-config-update-targeted-upserts 2026-04-29 18:26:19 -07:00
Yuneng Jiang db5cdfc440 fix(proxy): /config/update litellm_settings merge — request wins
Flip the litellm_settings dict merge from {**incoming, **existing} to
{**existing, **incoming} so the caller's value for any pre-existing key
is what gets persisted. The previous direction silently no-op'd a
request like {"litellm_settings": {"drop_params": false}} when the DB
already held drop_params: true — the endpoint returned 200 OK but the
stored value never changed. router_settings (immediately below) had
been doing the right thing all along; this brings the two sections into
alignment.

success_callback semantics are unchanged: it is still always normalized
to lowercase, and still unioned with any existing list (callbacks are
additive — a caller sends the new entry, not the full set).

Adds a regression test (drop_params: True in DB, request flips to
False, expect persisted False with other keys preserved).
2026-04-29 17:28:04 -07:00
4a7af1ff68 feat(proxy): durable agent workflow run tracking via /v1/workflows/runs (#26793)
* feat(schema): add workflow run tracking tables (LiteLLM_WorkflowRun, LiteLLM_WorkflowEvent, LiteLLM_WorkflowMessage)

* feat(proxy): add /v1/workflows/runs endpoints for durable agent workflow tracking

* feat(proxy): register workflow management router in proxy_server

* docs(workflows): add README for workflow run tracking API

* test(workflows): add unit tests for /v1/workflows/runs endpoints

* fix(workflows): atomic event+status update via tx(), run_id 404 guard, sequence retry on collision

* test(workflows): add tx mock, 404 on unknown run_id, retry-on-collision tests

* fix(workflows): constrain status to Literal enum, rename total→count in list responses

* add tenant isolation and bounded limits to workflow endpoints

* add created_by column and index to LiteLLM_WorkflowRun

* add ownership and bounded-limit tests for workflow endpoints

* Fix workflow run ownership for null owners

* guard prisma import in workflow_management_endpoints

* sync schema.prisma copies with workflow run models

* black: format workflow_management_endpoints.py

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-04-29 17:12:18 -07:00
Yuneng Jiang 1fd38eb5a5 fix(proxy): /config/update normalize existing success_callback before dedup
When a litellm_settings row already holds mixed-case names (e.g.
["Langfuse"]) — written by another code path or by hand — the
union-on-update path was running set([...]) over the raw existing list
plus the lowercase-normalized incoming list, so "Langfuse" and
"langfuse" survived as duplicates. delete_callback uses a lowercase
lookup, leaving the mixed-case entry unreachable.

Normalize the existing list with normalize_callback_names before the
union so the merged list converges to lowercase. Adds a regression test
covering the case where the DB starts with ["Langfuse", "SQS"] and the
caller submits ["langfuse"].
2026-04-29 16:21:51 -07:00
Yuneng JiangandClaude Opus 4.7 b6e4ccf876 fix(proxy): /config/update normalize success_callback on first write
Previously the normalize_callback_names call only ran when the existing
litellm_settings DB row already had a success_callback key. On the very
first write (no row yet, or row missing the key), incoming mixed-case
values like ["SQS", "sQs"] persisted as-is. delete_callback (lowercase
lookup) then could not find them, and a follow-up /config/update would
union normalized incoming with mixed-case stored entries, producing
duplicates.

Always normalize incoming success_callback before merging, and dedupe
both the standalone first-write case and the union-with-existing case.

Adds test_success_callback_normalized_on_first_write covering the
no-existing-row path; the existing union test still passes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 23:42:43 -07:00
Yuneng Jiang abbe5d7f85 fix(proxy): /config/update writes only sent sections, drop store_model_in_db gate
The endpoint loaded the full merged YAML+DB config and re-saved every
top-level section to LiteLLM_Config rows via save_config(), so a UI toggle
of one field persisted unrelated YAML state to DB as a side effect. It
also rejected every request when store_model_in_db was False — including
the request that would flip the flag to True (chicken-and-egg).

Replace save_config with targeted per-section upserts: read the existing
litellm_config row, merge in the request, upsert just that row. Sections
the caller did not send are not touched. Drop the blanket
store_model_in_db guard — the endpoint already requires prisma_client,
and the startup-side override at proxy_server.py:6491 picks up
general_settings.store_model_in_db=True from the DB on next restart.
2026-04-27 14:59:33 -07:00
yuneng-jiangandGitHub 1dee006423 Merge pull request #26520 from BerriAI/litellm_feat-team-my-user-tab
[Feat] Add "My User" tab to team info page
2026-04-25 15:05:05 -07:00
Ryan Crabbe 79762e4f5c fix(team): address review feedback on My User tab
- Fall back to email match when looking up the caller in
  members_with_roles — email-onboarded members may have user_id=None on the
  stored entry, which caused a false 404 for valid members. (P1)
- Replace 3 raw Prisma queries with get_team_object / get_team_membership /
  get_user_object so the endpoint reuses the cache + retry layer the rest of
  the proxy uses. (P2)
- Allow internal_user role to reach /team/{team_id}/members/me by adding the
  route to LiteLLMRoutes.self_managed_routes (the handler already enforces
  member-of-team access).
- Return null from the UI fetch on 404 instead of throwing, so a proxy admin
  who isn't a team member sees the existing empty state rather than an error
  string in the always-visible tab. (P2)
- Move the fetch out of networking.tsx into a colocated React Query hook
  (useMyTeamMember) next to MyUserTab; TeamInfo now passes only teamId.
- Tooltip + empty-state copy on Model Scope: drop "(all team models)"
  parenthetical and the redundant tooltip line.
- Tests: build real LiteLLM_TeamMembership / LiteLLM_BudgetTableFull
  fixtures (with created_at) so the Pydantic Union resolves to the Full
  variant; add an assertion that budget_reset_at survives end-to-end; add a
  test for the email-only member match path.
2026-04-25 14:28:27 -07:00
yuneng-jiangandGitHub 4a11362695 Merge pull request #26522 from BerriAI/litellm_yj_apr25
[Infra] Merge dev branch
2026-04-25 14:18:15 -07:00
michelligabrieleandGitHub ae925baaa1 fix(model_management): refresh in-memory router after POST /model/update (#26427) 2026-04-25 14:10:23 -07:00
michelligabrieleandGitHub db8ef44323 fix(key_management): enforce upperbound_key_generate_params on /key/regenerate (#26340) 2026-04-25 13:49:00 -07:00
Ryan Crabbe 9d71ad4796 [Feat] Add "My User" tab to team info page
Adds a new "My User" tab on the team detail page (between Overview and
Virtual Keys) so non-admin team members can see their own spend, budget,
budget reset date, rate limits, model scope, and team role.

Backend
- New `GET /team/{team_id}/members/me` endpoint that resolves the caller
  from the API key and returns only their own LiteLLM_TeamMembership row
  plus minimal team context (alias, role, email). Returns 404 if the
  caller is not a member of the team. Avoids exposing other members'
  data, which would happen if we filtered `/team/info` client-side.
- New `TeamMemberInfoResponse` Pydantic model.

Frontend
- New `MyUserTab` component (antd) — read-only summary cards.
- New `teamMemberMeCall` helper in networking.tsx.
- Tab is visible to all team members (including non-admins).
2026-04-25 13:42:51 -07:00
Yuneng Jiang 91f6661b37 [Fix] Align MCP OAuth proxy endpoints with per-server access policy
Bring `/server/oauth/{server_id}/authorize`, `/token`, and `/register`
in line with `fetch_mcp_server`: the helper that resolves the server now
also applies the per-caller access policy. Admin-view callers are
unrestricted; non-admins must have the server in their allowed-servers
set; servers resolved from the admin-only `/server/oauth/session`
temporary cache reject non-admins.
2026-04-25 12:39:44 -07:00
Ryan Crabbe 7eab549190 test: tighten prepare_key_update_data mock and apply black
- test_prepare_key_update_data: replace bare MagicMock with
  MagicMock(spec=LiteLLM_VerificationToken) and explicitly set
  existing_key_row.metadata = {}, so reserved-field reads return real
  values instead of MagicMock-returning-MagicMock. Fixes a regression
  surfaced by the new reserved-metadata preservation logic.
- test_key_management_endpoints.py: black-format-only changes from
  recent edits.
2026-04-25 09:08:55 -07:00
Ryan Crabbe ea5c762b00 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix-preserve-reserved-metadata 2026-04-25 08:46:17 -07:00
milan-berriandGitHub 2001d91b27 fix(mcp): share temporary MCP OAuth sessions across instances via Redis (#26162) (#26318)
Temporary MCP OAuth sessions were kept in process-local memory, so on
multi-instance/LB proxy deployments a session created on instance A could
not be found when the follow-up /server/oauth/{server_id}/... request
landed on instance B.

Persist temporary session records to Redis (encrypted with the existing
proxy encryption helpers) as a best-effort L2 cache alongside the current
in-memory L1. Convert get_cached_temporary_mcp_server to async and await
it from the authorize/token/register OAuth endpoints.

Made-with: Cursor
2026-04-23 16:21:27 -07:00
Michael Riad ZakyandMichael Riad Zaky 0bd49ecb8b Fix bug that bypasses per-team member budget limit 2026-04-22 10:41:13 -07:00
Yuneng Jiang 9deefc0f76 fix: align MCP broker endpoint access controls with existing auth patterns 2026-04-20 16:52:59 -07:00
Ryan Crabbe 208d583a33 [Fix] handle metadata=null on service-account keys
Addresses Greptile P1: `metadata: null` on a key with a reserved field
was crashing inside prepare_metadata_fields because cast(dict, None) is
a runtime no-op, so the loop hit `reserved_field in None` and returned
500 instead of 400.

Extend the rejection condition to cover `casted_metadata is None`, so
attempts to clear an immutable field via `metadata: null` return 400
consistently with the overwrite path. Non-service-account keys still
fall through to the existing "clear all metadata" behavior.
2026-04-18 11:52:54 -07:00
Ryan Crabbe 01acbb8d3d [Fix] reject explicit null when clearing reserved metadata field
Addresses Greptile review feedback:
- Clarify LiteLLM_Reserved_Metadata_Fields comment to describe both
  preserve-on-omit and reject-on-change behaviors.
- Treat explicit null as a change attempt so callers trying to clear
  service_account_id get a 400 instead of a silent no-op.
2026-04-18 11:06:22 -07:00
Ryan Crabbe 80d48a41e4 [Fix] preserve service_account_id in metadata on /key/update
/key/update and /user/update wholesale-replaced the metadata JSON column
whenever a caller passed a `metadata` field, silently dropping
service_account_id. The pre-call check in litellm_pre_call_utils.py then
stopped treating the key as a service account and bypassed
service_account_settings.enforced_params.

Add LiteLLM_Reserved_Metadata_Fields and have prepare_metadata_fields
preserve these keys from the existing row when the caller omits them.
Reject attempts to change an already-set value (400) since rebinding
service_account_id would break spend attribution.
2026-04-17 23:21:20 -07:00
Yuneng Jiang 11c3270cdc Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_yj_apr17
# Conflicts:
#	litellm/__init__.py
2026-04-17 17:36:40 -07:00
Yuneng Jiang ee2cf0e6e8 fix: address three CI failures from recent security PR merges
- url_utils.py: narrow sockaddr[0] from str|int to str via a helper with a
  fail-closed isinstance check. Fixes the two mypy errors introduced by
  the SSRF hardening without masking unexpected stdlib behavior.

- key_management_endpoints.py: restore the documented team member_permissions
  path for /key/update. The cross-key admin check added to close the
  cross-org rewrite attack was over-broad: it rejected non-admin team
  members even when can_team_member_execute_key_management_endpoint had
  already validated their team membership and /key/update grant. Now skip
  the admin check when the key has a team_id and the change is non-budget
  (membership + permission already enforced above). Budget/spend changes
  still require team/org admin. The cross-org attack remains blocked:
  an outside org admin fails the earlier team membership check.

- test_logging_redaction_e2e_test.py: rename and rewrite two parametrized
  tests to assert that request-body turn_off_message_logging has no effect.
  Reflects the intentional removal of turn_off_message_logging from
  _supported_callback_params so the caller cannot override admin logging
  policy via the request body.

- test_key_management_endpoints.py: add two tests covering the restored
  team member permission path — one positive (non-budget update succeeds
  for a team member with /key/update grant), one negative (max_budget
  change still rejected without admin role).
2026-04-17 15:11:45 -07:00
Ishaan Jaffer e8461b5b97 style: run black formatter on files from main merge 2026-04-17 13:02:59 -07:00
Ishaan Jaffer f31d4faa87 Merge origin/main into litellm_ishaan_april6 2026-04-17 12:36:51 -07:00
user c7c3df2b02 fix(proxy): extend /key/update admin check to non-budget fields
Audit-B #2. _check_key_admin_access was gated on max_budget/spend
changes only, which meant a non-admin caller could blanket-rewrite
any OTHER field on any key (key_alias, models, tpm_limit, rpm_limit,
metadata, tags, allowed_routes, guardrails, blocked, duration,
permissions, auto_rotate, access_group_ids, object_permission, …)
as long as they avoided budget/spend. Example attack:

  POST /key/update {
    key: sk-victim-in-org-B,
    models: [],
    blocked: true,
    organization_id: org-A,
  }

The caller is org-admin of org-A, which satisfies the route gate;
the handler then wipes models and blocks the victim's key.

Policy after this fix:
- PROXY_ADMIN: always allowed.
- Key OWNER (matching user_id): allowed for non-budget fields;
  budget/spend changes still require team/org admin.
- Everyone else: must pass _check_key_admin_access (PROXY_ADMIN /
  key-owner / team-admin / org-admin of the key).

Regression test confirms a non-owner INTERNAL_USER cannot rewrite
key_alias/blocked on someone else's key; existing test covers the
owner-can-update-alias case; existing test covers
internal-user-cannot-modify-max-budget.
2026-04-17 00:41:00 +00:00
user 662d05531d fix(proxy): close three more org-boundary escape paths
Continuation of Veria E3NpkuAd / Audit-B hardening. All three are the
same anti-pattern PR #25904 already addressed for _user_is_org_admin
and /user/delete: route-level gate trusts a caller-supplied scope
field, handler operates on a different scope.

1. /user/update no longer silently creates a user when the target
   email doesn't exist. Pre-fix, an org admin could supply a fresh
   email + caller-chosen budget/models/metadata; the INSERT path
   created the user with no org attachment, bypassing /user/new's
   org/team authorization. Now require PROXY_ADMIN for the create
   branch; return 404 otherwise. Also fixes /user/bulk_update because
   it dispatches through the same _update_single_user_helper.

2. /team/bulk_member_add with all_users=true restricted to PROXY_ADMIN.
   The flag pulls every user in the database into the target team,
   ignoring org scope — any team admin could use it to capture every
   user across every org into their team.

3. /team/update now verifies destination-org admin rights. When the
   request carries an organization_id that differs from the team's
   current org, an org admin of the caller's current org could
   previously relocate the team into any other org (draining their
   resources, or capturing a team they once administered). Require
   PROXY_ADMIN or org-admin of the DESTINATION org for the relocation.

Regression tests for #1 and #3; #2 covered by the existing bulk_add
suite after the gate addition.
2026-04-17 00:36:28 +00:00
user 8c0668f105 perf: batch target membership lookup in delete_user to avoid N+1
Greptile P1 on the /user/delete fix. Per CLAUDE.md 'No N+1 queries',
move the find_many inside the per-user loop to a single batched
fetch with {'user_id': {'in': data.user_ids}} before the loop, then
distribute to a per-user set in memory.
2026-04-17 00:17:57 +00:00
user 467166fdd7 fix(proxy): enforce per-target org authorization on /user/delete
Veria admin-queue finding E3NpkuAd, Audit-B #1. The route-level gate
accepts this call when the caller is PROXY_ADMIN or ORG_ADMIN of any
org named in request_data["organization_id"]/["organizations"]. The
handler processes data.user_ids without cross-checking whether those
users belong to the caller's administered orgs, so an org-admin of
org-A could delete users in org-B via:
  {"user_ids": ["victim_in_org_B"], "organization_id": "org-A"}

Add per-target authorization: org-admins may only delete users whose
entire org membership is within their admin scope; targets with any
org outside scope (or no org at all) require PROXY_ADMIN.

Regression test confirms an ORG_ADMIN call fails with 403 and no
cascade delete_many runs.
2026-04-17 00:11:00 +00:00
user 91bfbe6efe fix(proxy): enforce organization boundaries in admin operations
Validate org admin role against all requested organizations instead
of returning on first match. Scope team list queries to the caller's
permitted organizations when filtering by user_id.
2026-04-16 21:06:56 +00:00
Sameer KankuteandGitHub 1a9a31e4a2 Merge pull request #25665 from BerriAI/litellm_oss_staging_04_13_2026_p1
litellm oss staging 04/13/2026
2026-04-14 23:50:08 +05:30
1d45cfd1fc fix(proxy) - #25506 Team members added before team_member_budget is configured have no budget enforcement (#25557)
* fix #25506

* address greptile review feedback

* [Test] UI - Models: Add E2E tests for Add Model flow

Add E2E tests covering:
- Test connection with bad credentials shows failure modal
- Adding a specific model and verifying it appears in All Models table
- Adding a wildcard route and verifying it appears in All Models table
- Verifying model dropdown shows provider-specific models (existing test updated)

Added data-testid attributes to UI components to support stable test selectors.

Tests verified passing 3/3 consecutive runs with zero flakiness.

* address greptile review feedback (greploop iteration 1)

Add cleanup helper to delete models created during tests, preventing
stale data accumulation across repeated test runs.

* fix CI: replace data-testid selectors with text/role-based selectors

The data-testid attributes added to React components are not present
in the CI-built UI output. Switch to using getByRole and getByText
selectors which work with the rendered DOM regardless of build cache.

* remove unnecessary cleanup helper

The database is freshly seeded on every test run via seed.sql,
so per-test cleanup is not needed.

---------

Co-authored-by: Yuneng Jiang <yuneng@berri.ai>
Co-authored-by: Krrish Dholakia <krrish+github@berri.ai>
2026-04-14 23:37:49 +05:30
Sameer KankuteandGitHub ee40da58a2 Merge branch 'main' into litellm_oss_staging_04_11_2026 2026-04-14 20:54:12 +05:30
Sameer Kankute f6e526c5be Fix bulk update tests 2026-04-14 20:46:21 +05:30
yuneng-jiangandGitHub a306092d47 Merge pull request #25463 from BerriAI/litellm_oss_staging_04_09_2026
Litellm oss staging 04 09 2026
2026-04-13 17:25:53 -07:00
f54e4e664b fix(proxy): use _hash_token_if_needed for cache invalidation in bulk update and key rotation (#25552)
Two code paths in key_management_endpoints.py call hash_token()
unconditionally when invalidating the user_api_key_cache after a key
update.  When the caller passes a pre-hashed token ID (not an sk-
prefixed key), hash_token() double-hashes it, producing a cache key
that does not match the actual cached entry.  Cache invalidation
silently fails.

This is compounded by update_cache() which writes the stale cached key
object back with a fresh 60s TTL after every successful request,
preventing natural TTL expiry.  The stale entry (with outdated fields
like max_budget=None) persists indefinitely under load.

PR #24969 fixed this in update_key_fn but missed two other call sites:
- _process_single_key_update (bulk update path)
- _execute_virtual_key_regeneration (key rotation path)

Fix: replace hash_token() with _hash_token_if_needed() in both
locations, matching the pattern already used elsewhere in the file.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 19:36:40 -07:00
Yuneng Jiang 218daca867 [Fix] Address Greptile review: POST /organization/info auth bypass, inline imports, team access denial tests
- Add _verify_org_access to deprecated POST /organization/info endpoint
- Move get_user_object to module-level import in organization_endpoints.py
- Add tests for _verify_team_access 403 denial path
2026-04-11 12:40:55 -07:00
Ishaan Jaffer d22a07a9ba Merge remote-tracking branch 'origin/main' into ci-fix-april6-fixes 2026-04-11 12:04:14 -07:00
Yuneng Jiang 09ffc87734 fix: align org and team endpoint permission checks with existing patterns
Brings organization info and team management endpoints in line with the
access-control patterns used elsewhere in the proxy.
2026-04-10 22:17:05 -07:00
Yuneng Jiang 9a0487553d Merge remote-tracking branch 'origin' into litellm_oss_staging_04_09_2026 2026-04-10 16:41:27 -07:00
jayden d910a95661 fix(proxy): improve input validation on management endpoints 2026-04-09 14:14:53 -07:00
a6c30b30bf build: migrate packaging, CI, and Docker from Poetry to uv (#25007)
* build: migrate packaging metadata to uv

* ci: move automation and local tooling to uv

* docker: migrate image builds and runtime setup to uv

* docs: update install and deployment guidance for uv

* chore: align auxiliary scripts and tests with uv

* test: harden test_litellm isolation

* fix: keep release and health check images self-contained

* build: pin uv tooling and health check deps

* test: isolate bedrock image request formatting from suite state

* test: cover sandbox executor requirements flow

* ci: fix circleci no-op command steps

* ci: fix circleci publish workflow parsing

* fix: stabilize remaining uv migration CI checks

* ci: increase matrix test timeout headroom

* fix: restore published docker and license coverage

* fix: restore proxy runtime build parity

* fix: restore proxy extras parity and venv migrations

* ci: persist uv path across circleci steps

* fix: keep psycopg binary in default test env

* docker: preserve prisma cache across stages

* test: run local proxy checks through uv python

* build: restore runtime deps moved into ci

* build: refresh uv lock after upstream merge

* fix: restore module import in test_check_migration after merge

The conflict resolution imported only the function but the test body
references check_migration as a module throughout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: revert dependency promotions, remove nodejs-wheel-binaries, fix Docker layer caching

- Move google-generativeai, Pillow, tenacity back to ci group (they are
  lazily imported and bloat the base SDK install needlessly)
- Remove nodejs-wheel-binaries from extra_proxy and proxy-dev (redundant
  in Docker where system Node.js is already installed via apk)
- Remove all nodejs-wheel node replacement and venv npm patching blocks
  from Dockerfiles since the wheel is no longer installed
- Add --no-default-groups to CodSpeed benchmark workflow so the benchmark
  environment matches the old minimal pip install footprint
- Apply standard uv two-phase Docker pattern: copy metadata first, install
  deps (cached layer), then copy source and install project
- Replace CircleCI enterprise no-op with proper uv sync command

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate uv.lock after removing nodejs-wheel-binaries

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): use cache/restore instead of cache to prevent cache poisoning

The old workflow used actions/cache/restore (read-only). The uv migration
changed it to actions/cache (read-write), which zizmor flags as a cache
poisoning risk. Restore the safer read-only variant.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): disable setup-uv built-in cache to silence cache-poisoning alert

The setup-uv action enables caching by default, which zizmor flags as a
cache poisoning risk. Disable it since we already use a read-only
cache/restore step.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): disable setup-uv cache in publish workflow

Silences zizmor cache-poisoning alert. Publishing workflow runs
infrequently on protected branches so caching adds no real benefit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(test): remove duplicate verbose_logger mock in test_check_migration

The logger was patched twice — first via mocker.patch() then via
mocker.patch.object(autospec=True). The second call fails because
autospec cannot inspect an already-mocked attribute. Remove the
redundant first patch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): free disk space before Docker build in test-server-root-path

The Dockerfile.non_root build ran out of disk on the CI runner. Remove
Android SDK, .NET, Boost, and GHC toolchains (~12GB) to free space.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 11:46:23 -07:00
Ryan Crabbe fdd2672e93 feat: add POST /team/permissions_bulk_update endpoint
Adds a new endpoint to bulk-update team_member_permissions across
teams. Supports apply_to_all_teams (with cursor-based pagination)
or a specific list of team_ids. Merges new permissions into each
team's existing set rather than overwriting.

Also fixes test isolation bug in test_get_prompt_info_by_base_id
where leaked prisma_client state from other tests caused a
TypeError on await.
2026-04-06 17:45:35 -07:00
Ishaan Jaffer 9e0fb6bd17 fix(tests): set default_team_member_models=None on team mocks to match new field 2026-04-06 17:25:45 -07:00
Yuneng JiangandClaude Opus 4.6 566a04126f test: add unit tests for _get_team_deployments filtering logic
Tests cover: matching deployments, wrong team_id filtering, string-encoded
model_info, empty results, invalid model_info, and mixed deployment filtering.
Also updates MockPrismaClient.find_many to support the new startswith query.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 14:34:39 -07:00
ryan-crabbe-berriandGitHub 0331fb5a8f Merge pull request #25027 from BerriAI/litellm_add-access-group-to-model
feat(teams): resolve access group resources in team endpoints
2026-04-03 17:22:47 -07:00
Ryan Crabbe 93369bf60d perf(teams): batch-fetch access groups in single DB query
Replace per-ID _resolve_access_group_resources loop with a single
find_many call that deduplicates IDs across all teams. Removes the
N+1 query pattern on cold cache for the team list endpoint.
2026-04-03 17:13:56 -07:00
Ryan Crabbe 38f6c9491d fix(tests): correct mock targets in TestResolveAccessGroupResources
Three tests were patching the non-existent `get_access_object` instead
of `_get_access_object` (the lazy-import wrapper), causing AttributeError.
Also added missing `prisma_client` mock so tests get past the early-exit
guard and actually exercise the resolution logic.
2026-04-03 16:16:55 -07:00
+1
ishaan-berriGitHubmichelligabrieleSameer KankuteredhelixSynergyTalha AnwarClaude Opus 4.6madhu19991Srikanth @adobe <devarakondasrikanth@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
c6aa3ea452 Litellm ishaan april1 try2 (#25110)
* Litellm ishaan april1 (#25103)

* fix(proxy): enforce upperbound key params on key/update and add custom_key_update hook

The /key/update endpoint did not enforce upperbound_key_generate_params,
allowing users to bypass configured limits (tpm_limit, rpm_limit,
max_budget, duration, budget_duration) by updating an existing key
instead of generating a new one.

Extract the upperbound enforcement logic from _common_key_generation_helper()
into a standalone _enforce_upperbound_key_params() function and call it from
both the generate and update paths. For updates, None values are skipped
(not filled with defaults) since they mean "don't change this field".

Also adds a custom_key_update config option and user_custom_key_update global,
mirroring the existing custom_key_generate pattern, so custom key validation
logic can fire during key updates as well.

* fix(proxy): invoke custom_key_update hook in bulk update path

The user_custom_key_update hook was only called in update_key_fn
(single key update) but not in _process_single_key_update (bulk
update path), allowing custom validation to be bypassed via the
/key/update/bulk endpoint. Mirror the hook invocation in both paths.

* fix(proxy): pass UpdateKeyRequest to hook in bulk path, not BulkUpdateKeyRequestItem

Move the custom_key_update hook invocation to after UpdateKeyRequest
is constructed so the hook receives the same type in both single and
bulk update paths. Previously the bulk path passed
BulkUpdateKeyRequestItem (5 fields only), which would cause
AttributeError for hooks accessing fields like tpm_limit or models.

* fix(bedrock): promote cache usage to message_delta for Claude Code (#24850)

Ensure Bedrock/Anthropic-compatible streaming exposes cache usage where Claude Code reads it by promoting message_stop usage onto message_delta and preserving usage fields in fake-streamed message_delta events.

Made-with: Cursor

* fix(search): Support self-hosted Firecrawl response format in search transform (#24866)

The `transform_search_response` method only handled Firecrawl Cloud (v2)
response format where `data` is a dict with `web`/`news` keys. Self-hosted
Firecrawl (v1) returns `data` as a flat list of result objects, causing an
`AttributeError: 'list' object has no attribute 'get'`.

Detect the response format by checking if `data` is a list (self-hosted)
or dict (cloud) and handle both cases.

Cloud format:  {"data": {"web": [...], "news": [...]}}
Self-hosted:   {"success": true, "data": [{"url": "...", "title": "...", ...}]}

Co-authored-by: Synergy <synergyoclaw@gmail.com>

* feat: add environment and user tracking to prompt management (#24855)

* feat: add environment and user tracking to prompt management

- Add environment (development/staging/production) and created_by columns to LiteLLM_PromptTable
- Update unique constraint to [prompt_id, version, environment]
- All CRUD endpoints support environment filtering and user tracking
- Redesigned prompt detail page with environment tabs and version history
- UI: environment filter on list page, environment selector in editor
- 8 new tests for environment and user tracking

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: Black formatting and add environments to PromptInfoResponse TypeScript type

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address Greptile review findings

- P1: delete_prompt scopes in-memory cleanup to environment when provided
- P2: dotprompt_content parsed directly regardless of environment flag
- P2: use distinct for environments query
- P2: fix double-fetch on initial mount in prompt_info.tsx
- fix: remove unsupported select kwarg from find_many

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address remaining Greptile review comments

- Remove unused useCallback import (index.tsx)
- Remove unused ENV_COLORS variable (prompt_info.tsx)
- P1: in-memory fallback in get_prompt_versions now respects environment filter
- P1: reset selectedEnv when promptId changes to avoid stale state
- Cyclic imports are pre-existing pattern, not introduced by this PR

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: scope patch_prompt to environment using primary key

- Add environment query param to patch_prompt endpoint
- Look up target row by composite key (prompt_id + version + environment)
- Update by primary key (id) to target exactly one row
- Fixes Greptile finding: patch with multiple environments no longer ambiguous

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use actual start_time for failed request spend logs (#24906)

async_post_call_failure_hook set both start_time and end_time to
datetime.now(), making all failed requests show duration=0. Use the
actual start_time from litellm_logging_obj instead, so spend logs
reflect the real request duration on timeout and other failures.

Fixes #24888

* feat(bedrock): add nova canvas image edit support (#24869)

* feat(bedrock): add nova canvas image edit support

* fix(bedrock): support PathLike inputs for nova image edit

* chore: sync schema.prisma copies from root

* fix(mypy): correct type-ignore code for delta_usage arg-type

* fix(mypy): cast status_code to str, suppress intentional str yield

* fix(lint): extract _create_content_block_chunks to fix PLR0915

* fix(lint): extract helpers to fix PLR0915 in prompt endpoints

---------

Co-authored-by: michelligabriele <gabriele.michelli@icloud.com>
Co-authored-by: Sameer Kankute <sameer@berri.ai>
Co-authored-by: redhelix <amin.lalji@gmail.com>
Co-authored-by: Synergy <synergyoclaw@gmail.com>
Co-authored-by: Talha Anwar <37379131+talhaanwarch@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: madhu19991 <madhu@thunkai.com>
Co-authored-by: Srikanth @adobe <devarakondasrikanth@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(test): update model armor streaming test to handle string or int error code

---------

Co-authored-by: michelligabriele <gabriele.michelli@icloud.com>
Co-authored-by: Sameer Kankute <sameer@berri.ai>
Co-authored-by: redhelix <amin.lalji@gmail.com>
Co-authored-by: Synergy <synergyoclaw@gmail.com>
Co-authored-by: Talha Anwar <37379131+talhaanwarch@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: madhu19991 <madhu@thunkai.com>
Co-authored-by: Srikanth @adobe <devarakondasrikanth@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-03 14:57:44 -07:00
Ryan Crabbe bbe708b093 perf(teams): single-pass access group resolution + asyncio.gather in list endpoint
- Fetch each access group object once and extract all 3 resource fields
  in a single pass instead of 3 separate calls (3N → N lookups)
- Use asyncio.gather to resolve access groups across teams concurrently
  in list_team_v2 instead of sequential awaits
- Add 5 unit tests for _resolve_access_group_resources
2026-04-02 14:52:32 -07:00