Commit graph

8121 commits

Author SHA1 Message Date
Tin Chi Lo
f3f89d6177 fix(proxy): raise dashboard session budget default to $1 and make it configurable in config and Admin UI
Every dashboard login mints a 24h session key whose max_budget comes from
litellm.max_ui_session_budget, and all dashboard LLM traffic (playground,
auto router per-tier Test Connection probes) spends against and is gated
by that one key. The $0.25 default locked sessions out mid-testing with
"Budget has been exceeded ... Max budget: 0.25" and the setting appeared
in no docs, no UI, and no error text, so it read as a hardcoded cap.

Raise the default to $1. Give the setting an explicit typed arm in the
config loader (float coercion for env-var strings, null disables the
cap). Surface it on the Admin UI General settings tab through the
existing litellm_settings bridge as a new Dollar field type (positive
USD, unbounded above; the existing Float type is validated to (0, 1] for
fractions), with a spec-level default so clearing the field restores $1
instead of silently removing the cap, and enroll it in
LITELLM_SETTINGS_SAFE_DB_OVERRIDES so UI edits propagate to peer workers.
2026-07-21 20:11:49 -07:00
Yassin Kortam
1aba849af2
fix(ui): surface env-var-sourced theme and logging-callback settings (#34156)
The UI theme and logging-callback read endpoints reported only stored
config while the features resolve their values from the process
environment, so a gateway configured purely through env vars showed
blank settings pages even though branding rendered and callbacks fired.

/get/ui_theme_settings read only litellm_settings.ui_theme_config;
logo_url and favicon_url now fall back to UI_LOGO_PATH and
LITELLM_FAVICON_URL when the stored config leaves them blank.

process_callback (the logging-callbacks block of /get/config/callbacks)
reported every callback env var as unset unless it lived in the config
environment_variables overlay; it now falls back to os.getenv, matching
the slack block. Secret values stay redacted for non-admins via the
existing callback role gate.

Stored values keep winning over the environment, so the UI-driven flow
is unchanged.

Resolves LIT-4667
2026-07-21 18:36:31 -07:00
Yassin Kortam
82d3116be9
fix(ui): reflect REDIS_* env cache config and stop the UI overwriting the stored password (#34160)
The Cache Settings page read only the database row, so a response cache
pointed at Redis purely through REDIS_* env vars showed a blank page
while the cache worked. It also masked credentials on read with a
partial-reveal string and re-persisted whatever the form submitted, so
an admin who edited an unrelated field and pressed Save wrote the mask
string over the real Redis password, breaking auth.

GET /cache/settings now overlays the same REDIS_* kwargs the runtime
resolves from when the stored config leaves a field unset, and redacts
credentials with a fixed marker. POST /cache/settings restores the
stored secret behind any credential echoed back as the marker or omitted,
and drops an env-sourced marker rather than persisting it; the response
no longer echoes plaintext credentials. The connection test resolves a
redacted credential back to the stored value the same way. The dashboard
never prefills a credential and drops the marker from the save payload,
mirroring the Coordination Redis tab.

Resolves LIT-4315
2026-07-21 18:36:26 -07:00
yucheng-berri
065faf6e69
chore(proxy): clean up request parameter validation and provider destination handling (#34189) 2026-07-22 00:57:58 +00:00
Yassin Kortam
1ea7db2111
fix(rust): route agentic-completion-hook /messages requests to Python for all stream modes (#34126) 2026-07-22 00:36:47 +00:00
ryan-crabbe-berri
e17f3b6e1a
fix(proxy): populate user_email on UserAPIKeyAuth for JWT auth (#34174)
Some checks are pending
CodSpeed Benchmarks / benchmarks (push) Waiting to run
GitHub Actions Security Analysis / zizmor (push) Waiting to run
JWT auth built UserAPIKeyAuth without user_email even though the resolved
user row and the JWT email claim were both available, so the user_email
label on Prometheus metrics and user_api_key_user_email in
StandardLogging/SpendLogs metadata were always None for JWT traffic.

Plumb user_email through JWTAuthBuilderResult: auth_builder returns the
user row email when set, falling back to the user_email_jwt_field claim
(covers the scope-based proxy-admin path where no user row is loaded).
The JWT branch now stamps it on the proxy-admin return, the standard
valid_token, and the auto-registered virtual key object.

Resolves LIT-4238
2026-07-21 16:11:47 -07:00
Mateo Wang
9d714b0b29
Merge pull request #34172 from BerriAI/litellm_scrub_customer_name
chore(tests): replace a customer name and domain with neutral placeholders
2026-07-21 15:22:35 -07:00
tin-berri
869ef0cbfb
Merge pull request #34072 from BerriAI/litellm_mcp_ema_assertion_store
feat(mcp): store the enterprise IdP identity assertion at SSO login for EMA egress
2026-07-21 15:10:35 -07:00
mateo-berri
fa025fc474 chore(tests): replace a customer name and domain with neutral placeholders 2026-07-21 15:09:15 -07: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
Tin Chi Lo
ddae6eac6b fix(mcp): judge EMA retention only against the config and DB authorities, never the registry snapshot 2026-07-21 14:54:37 -07:00
Tin Chi Lo
cfa074d970 fix(mcp): make the EMA retention gate authoritative across pods and tighten the assertion store 2026-07-21 14:54:37 -07:00
Tin Chi Lo
83e147b553 feat(mcp): store the enterprise IdP identity assertion at SSO login for EMA egress 2026-07-21 14:54:37 -07:00
Yassin Kortam
48fdaaa5cd
fix(router): honor request-level num_retries over global litellm_settings.num_retries (#34124)
The async @client wrapper stamped the global litellm.num_retries onto the raised
exception via setattr(e, "num_retries", ...), even on router calls where the
request-level num_retries had already been popped and resolved. async_function_with_retries
then adopted that stamped global value, overwriting the request-level num_retries it had
correctly resolved. So a per-request num_retries (request body or x-litellm-num-retries
header) was silently ignored whenever litellm_settings.num_retries was set.

Only stamp num_retries on the exception when the call itself carried one (an explicit
request value or a deployment's litellm_params.num_retries), never the global fallback.
The router already resolves the global via self.num_retries, so leaving the exception
unset preserves the request-level value and lets the per-deployment path set it when present.

Resolves LIT-4516
2026-07-21 21:32:34 +00:00
Yassin Kortam
e9ac84dc8b
fix(proxy): stop save_config from snapshotting environment_variables into the DB (#34119)
save_config wrote the entire merged config to the DB config table on every call. Because get_config() resolves os.environ/ placeholders to plaintext and merges the environment_variables section, any endpoint that does get_config() then mutates one section then calls save_config() (/add/allowed_ip, delete_callback, model and cost-tracking settings, and others) accidentally persisted an environment_variables row holding all YAML/OS-sourced env vars. Once that row existed the DB overlay shadowed YAML and container env on every subsequent startup, so config/env changes were silently ignored

save_config now pops environment_variables from the DB write unless the caller passes include_env_vars=True. The dedicated /config/update path already writes env vars per-section via _upsert_section, so no current caller needs to opt in

Resolves LIT-2009
2026-07-21 21:23:28 +00:00
Yassin Kortam
fcaf673418
fix(router): stop per-deployment num_retries from double-counting as provider max_retries (#34129)
* fix(router): stop per-deployment num_retries from double-counting as provider max_retries

A model group with one deployment and num_retries set in the deployment's
litellm_params sent (1 + num_retries) ** 2 requests upstream instead of
1 + num_retries. The deployment's num_retries reached litellm.completion, which
copied it onto max_retries and set it on the provider client, so the provider SDK
retried num_retries times inside each of the Router's 1 + num_retries attempts.

The Router is the sole retry owner for routed calls, so completion() now forces the
provider-SDK max_retries to 0 whenever the call originates from the Router/proxy
(detected via model_group in the request metadata) and only keeps the num_retries
to max_retries alias for direct, non-routed litellm calls (the instructor use case).
This also stops a request- or deployment-level max_retries from nesting on top of
the Router's retries.

Resolves LIT-4385

* fix(router): make router-origin check robust and close test clients

Address review: detect the router marker in both metadata and litellm_metadata
independently (a non-empty metadata without model_group no longer hides a
model_group in litellm_metadata), and close the injected async clients in the
test fixture.
2026-07-21 21:13:32 +00:00
tin-berri
914079a68c
Merge pull request #34065 from BerriAI/litellm_lit4629_issuer_discovery
fix(mcp): let an admin-pinned issuer drive OAuth discovery for url-less servers
2026-07-21 14:06:55 -07:00
ryan-crabbe-berri
76c9eca25d
refactor(auth): derive temp budget increase without mutating the token (#34121)
* refactor(auth): derive temp budget bump without mutation, tz-aware auth datetimes

_update_key_budget_with_temp_budget_increase mutated max_budget in place, so correctness depended on every resolution path handing it a fresh copy of the cached token; one future re-cache of a live token would compound the bump per request. Return a model_copy instead so no caller can leak an increased budget into shared state.

Also fixes the three remaining DTZ005 naive datetime.now() calls in user_api_key_auth.py (auth span start, builder start_time, service-log end_time; all consumers convert to epoch or subtract same-pair datetimes) and ratchets the DTZ005 strict budget 244 -> 241.

* test: pin non-mutation of the temp budget helper input

Adversarial mutation-testing showed reverting the helper to in-place mutation still passed every test: the cache's copy-on-read layer masks the mutation in the integration test and the direct unit test only inspected the return value. Assert the input object is left untouched and the result is a distinct object so the purity guarantee itself is load-bearing.
2026-07-21 21:02:40 +00:00
Tin Chi Lo
6959d9de69 test(mcp): pin the token and register wall messages for url-less servers 2026-07-21 13:53:14 -07:00
Tin Chi Lo
930676a9bf fix(mcp): let an admin-pinned issuer drive OAuth discovery for url-less servers 2026-07-21 13:53:14 -07:00
tin-berri
1d36290551
Merge pull request #34063 from BerriAI/litellm_lit4629_openapi_oauth_egress
fix(mcp): attach resolved OAuth credentials to OpenAPI spec_path tool calls
2026-07-21 13:51:17 -07:00
Mateo Wang
a7e4c83013
Merge pull request #34154 from BerriAI/litellm_a2a_protocol_version_semver
fix(a2a): accept semver protocolVersion values like 0.3.0 in agent cards
2026-07-21 13:37:05 -07:00
ryan-crabbe-berri
efa997dfe0
feat(budgets): add configurable budget_reset_time of day (#31007)
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.
2026-07-21 13:35:01 -07:00
mateo-berri
2310211531 fix(a2a): reject malformed protocolVersion suffixes while keeping semver prereleases 2026-07-21 13:24:58 -07:00
mateo-berri
062e58fb1d fix(a2a): accept semver protocolVersion values like 0.3.0 in agent cards 2026-07-21 13:03:47 -07:00
yuneng-jiang
fcd236097e
fix(interactions): add queued to the Interaction status enum (#34135)
Google added a queued value to Interaction.status in the live Interactions
OpenAPI spec, so the compliance canary test_status_enum_values started
failing on every open PR. The exact-match assertion is deliberate; it is
how we find out the spec moved, so this adds the new value rather than
loosening the check, and mirrors it into the generated Status enums so
InteractionStatus stays truthful.
2026-07-21 12:36:21 -07:00
yucheng-berri
049c6836d2
fix(model_armor): sanitize error details by default (#33908)
* fix(model_armor): sanitize error details by default

Generated with AI

Co-Authored-By: Claude Code

* fix(model_armor): sanitize handler-raised HTTP errors and redact scanned content in guardrail logging

The async HTTP handler raises MaskedHTTPStatusError on any non-2xx via
raise_for_status, so the non-200 branch in make_model_armor_request never ran
against a live API and the raw upstream body reached callers and logs. Catch
the raised error and build the sanitized detail from the response status

Replace the empty-dict guardrail logging payload with field-level redaction of
the keys that echo scanned content (text, sanitizedText, findings) so guardrail
traces keep filter states and block reasons while scanned content stays out

Restore the upstream status code in the sanitized error detail, read guardrail
metadata from the same key the hooks write, and keep guardrail_status within
its typed literal values

* fix(model_armor): bound redactor recursion depth and allowlist it in the recursion detector

_redact_scanned_content walks provider JSON bounded by _REDACT_MAX_DEPTH=20 and
fails closed by returning the redaction sentinel at the cap

* fix(model_armor): honor fail_on_error for upstream API failures

API failures now raise a dedicated ModelArmorAPIError so hooks can tell them
apart from content-block HTTPExceptions; fail_on_error=False lets the request
proceed on a Model Armor outage again while fail-closed configs get the same
sanitized 400 as before

Also addresses review notes: sanitize_error_detail constructor annotation
matches the nullable config field, redaction is owned by the metadata write
sites so _process_response no longer re-applies it, and the request and
response debug log branches move into helpers

* test(model_armor): cover fail_on_error routing on during-call, post-call, streaming, and file-scan paths

* chore: remove accidentally committed pytest cache files

* fix(model_armor): keep sanitize_error_detail coerced across in-memory config reloads

update_in_memory_litellm_params assigns raw LitellmParams fields, so a hot
reloaded config carrying an explicit null would silently disable sanitization;
re-apply the only-explicit-False-opts-out coercion after the update

* fix(model_armor): redact matched malicious URIs and reuse the shared recursion depth constant

maliciousUriMatchedItems echoes the caller-supplied URL including path and
query, so it joins the scanned-content key set; the redactor depth cap now
comes from DEFAULT_MAX_RECURSE_DEPTH in litellm constants instead of a local
literal

* fix(model_armor): keep API failures out of the intervention trace status

Fail-closed upstream failures re-raise ModelArmorAPIError instead of
converting to HTTPException(400), so the shared guardrail logging keeps
recording them as guardrail_failed_to_respond while content blocks stay
guardrail_intervened. Callers see the same 500 shape as before this PR,
with the sanitized message

* chore(model_armor): drop explanatory comment per repository comment policy

---------

Co-authored-by: eugene-yao-zocdoc <eugene.yao@zocdoc.com>
2026-07-21 10:28:24 -07:00
mateo-berri
e411d637b3 feat(gemini): day-0 pricing for gemini-3.6-flash and gemini-3.5-flash-lite 2026-07-21 08:50:40 -07:00
Mateo Wang
7e66f00fca
Merge pull request #34068 from BerriAI/litellm_codex_auto_drop_params 2026-07-20 23:45:29 -04:00
mateo-berri
fadec17a0f Merge origin/litellm_internal_staging into litellm_mantle_codex_additional_tools (resolve overlap with #33228 hoist) 2026-07-20 20:30:34 -07:00
Mateo Wang
07a355e867
Merge pull request #33228 from lyb0307/litellm_bedrock_mantle_codex_additional_tools
fix(bedrock_mantle): hoist Codex additional_tools input items to top-level tools
2026-07-20 23:14:47 -04:00
tin-berri
c9b50120ae
Merge pull request #34059 from BerriAI/litellm_lit4629_google_registry
fix(mcp): add Google Sheets, Drive, Calendar, and Docs to the OpenAPI registry
2026-07-20 20:08:21 -07:00
Mateo Wang
464537a12d
Merge pull request #34047 from BerriAI/litellm_shared_key_capability_flags
fix(router): propagate capability flags to shared backend cost map key
2026-07-20 23:05:58 -04:00
mateo-berri
d2b573c37d feat(proxy): auto-enable drop_params for Codex user agents 2026-07-20 19:58:47 -07:00
mateo-berri
354f3971a9 fix(bedrock_mantle): gate unsupported service_tier on drop_params for the Responses API 2026-07-20 19:49:42 -07:00
Tin Chi Lo
040aa9d896 fix(mcp): never promote caller oauth2 headers to the resolved credential on the v1 arm 2026-07-20 19:33:46 -07:00
mateo-berri
8745f355a4 fix(bedrock_mantle): log additional_tools hoist at debug level 2026-07-20 19:33:31 -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
bingbing
8307e3ee32 fix(bedrock_mantle): hoist Codex additional_tools input items to top-level tools 2026-07-20 19:27:33 -07:00
Tin Chi Lo
48572c9516 fix(mcp): attach resolved OAuth credentials to OpenAPI spec_path tool calls 2026-07-20 19:20:34 -07:00
Tin Chi Lo
39cdfbdd28 test(mcp): pin all four Google registry entries in the shape test 2026-07-20 19:18:42 -07:00
Tin Chi Lo
3334ee9b13 fix(mcp): add Google Sheets, Drive, Calendar, and Docs to the OpenAPI registry 2026-07-20 19:02:00 -07:00
Tin Chi Lo
20dd0e6a39 fix(mcp): return the DCR client's own redirect_uris to stop the /callback self-redirect loop
The MCP OAuth DCR relay's client-facing /register response handed back LiteLLM's own
<base>/callback as the client's redirect_uris on every arm except the true bridge relay.
A spec-compliant OAuth 2.1 DCR client (e.g. Open WebUI over Streamable HTTP) adopts that
value for its subsequent /authorize calls, so /callback redirects to itself; the second
hit carries the client's opaque state, fails to decrypt, and surfaces as the LIT-4197
"oauth_state ... Incorrect padding" error, making pass-through MCP OAuth unusable against
real DCR-capable upstreams.

The short-circuit arm keeps registering the gateway callback upstream (unchanged) and now
echoes the client's own redirect_uris back to the client across all register arms. Since
the client then authorizes with its own separate-origin redirect, the /authorize rejection
hint now points operators to MCP_TRUSTED_REDIRECT_ORIGINS, the mechanism a legitimate
cross-origin OAuth client needs (auto-trusting a DCR-registered redirect would reintroduce
the VERIA-57 open-redirect vector, since dynamic registration is unauthenticated).
2026-07-20 18:56:47 -07:00
tin-berri
2ea09a9969
Merge pull request #34026 from BerriAI/litellm_lit4608_autoroute_fixed_port_key
fix(cli): stable port and persisted master key for lite autoroute up
2026-07-20 18:54:17 -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
devin-ai-integration[bot]
089de50d20
fix(auth): apply temp_budget_increase for cache-hit keys (#33841)
temp_budget_increase was only applied on the DB-fetch path of _user_api_key_auth_builder, so a key served from the auth cache reverted to its original max_budget and was wrongly blocked with BudgetExceededError once spend crossed the original budget while staying under the effective budget.

Move _update_key_budget_with_temp_budget_increase out of the DB-only branch so it runs for every resolved token regardless of source. The cache stores the original budget and each cache hit returns a fresh model_copy(), so this never double-applies.

Fixes #25760

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:12:15 -07:00
Mateo Wang
1e741094fe
Merge pull request #33807 from BerriAI/litellm_vertex_azure_midsys
fix(vertex,azure): model-aware mid-conversation system for Claude /v1/messages
2026-07-20 20:48:47 -04:00
ryan-crabbe-berri
46a80e1ef5
fix(auth): set budget_reset_at when JWT upsert seeds a budget_duration (#34050)
The JWT first-login upsert in get_user_object creates the user row by
merging default_internal_user_params straight into table.create, so a
configured budget_duration landed with budget_reset_at NULL. The reset
sweep now heals such rows (PR #33623), but until the next sweep the row
shows a null reset time and its first window starts at the sweep instead
of one full duration after creation. Compute budget_reset_at at creation
like every other write path (/user/new, UI SSO, /key/generate, /team/new)
already does
2026-07-20 17:23:44 -07:00
Yassin Kortam
2c99b7804e
perf(core): fast-path SafeAttributeModel.__delattr__ for declared fields (#33993)
Continues #29762. Response models (Message, Choices, Usage) delete unset
optional fields in __init__ so model_dump matches the OpenAI spec. Each delete
routed through pydantic's BaseModel.__delattr__, whose per-call
ModelMetaclass.__getattr__ lookup and _check_frozen dominate construction. When
the target is a declared field already present in __dict__ on a non-frozen
model, delete it with object.__delattr__ directly; that is exactly what pydantic
2.13 does for that case, minus the metaclass getattr and the frozen check. It
falls back to the previous super().__delattr__ path for extras, private
attributes, cached properties and missing names, so behavior is unchanged.

Co-authored-by: Jay Gowdy <jgowdy@godaddy.com>
2026-07-21 00:06:56 +00:00
Joseph Yeung
ffe56cf5a2
fix(budget): reset users/teams with NULL budget_reset_at (#33623)
Internal users seeded from default_internal_user_params (SSO/JWT first-login
upsert, or /user/new without an explicit budget_reset_at) get budget_duration
set but budget_reset_at = NULL. The ResetBudgetJob user/team queries filter on
{"budget_reset_at": {"lt": now}}, which never matches NULL, so these rows are
never reset: their spend accumulates for the lifetime of the row and silently
exceeds max_budget with no periodic reset.

The budget-table query already handles this by OR-ing in a
{budget_reset_at IS NULL AND budget_duration IS NOT NULL} branch. Apply the
same pattern to the user and team reset queries in PrismaClient.get_data.

Adds a regression test asserting both the user and team reset queries select
NULL-budget_reset_at rows that have a budget_duration.

Co-authored-by: yuneng-jiang <yuneng@berri.ai>
Co-authored-by: Mateo Wang <277851410+mateo-berri@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 16:55:38 -07:00