From 43054a239059cbc695a0f0215aedfa15615750cc Mon Sep 17 00:00:00 2001 From: Harshit Jain Date: Thu, 26 Feb 2026 19:03:49 +0530 Subject: [PATCH 01/60] fix: langfuse trace leak key on model params --- litellm/integrations/langfuse/langfuse.py | 31 +++++++++++++++++------ 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/litellm/integrations/langfuse/langfuse.py b/litellm/integrations/langfuse/langfuse.py index 7bf97665fd2..e2db8be0450 100644 --- a/litellm/integrations/langfuse/langfuse.py +++ b/litellm/integrations/langfuse/langfuse.py @@ -25,6 +25,7 @@ from litellm.litellm_core_utils.core_helpers import ( reconstruct_model_name, filter_exceptions_from_params, ) +from litellm.litellm_core_utils.model_param_helper import ModelParamHelper from litellm.litellm_core_utils.redact_messages import redact_user_api_key_info from litellm.integrations.langfuse.langfuse_mock_client import ( create_mock_langfuse_client, @@ -123,7 +124,7 @@ class LangFuseLogger: self.langfuse_flush_interval = LangFuseLogger._get_langfuse_flush_interval( flush_interval ) - + if should_use_langfuse_mock(): self.langfuse_client = create_mock_langfuse_client() self.is_mock_mode = True @@ -291,8 +292,6 @@ class LangFuseLogger: functions = optional_params.pop("functions", None) tools = optional_params.pop("tools", None) - # Remove secret_fields to prevent leaking sensitive data (e.g., authorization headers) - optional_params.pop("secret_fields", None) if functions is not None: prompt["functions"] = functions if tools is not None: @@ -505,13 +504,18 @@ class LangFuseLogger: kwargs.get("model", ""), custom_llm_provider, metadata ) + # Use whitelisted model parameters to prevent leaking secrets + sanitized_model_params = ModelParamHelper.get_standard_logging_model_parameters( + optional_params + ) + trace.generation( CreateGeneration( name=metadata.get("generation_name", "litellm-completion"), startTime=start_time, endTime=end_time, model=model_name, - modelParameters=optional_params, + modelParameters=sanitized_model_params, prompt=input, completion=output, usage={ @@ -607,9 +611,7 @@ class LangFuseLogger: # Use standard_logging_object.trace_id if available (when trace_id from metadata is None) # This allows standard trace_id to be used when provided in standard_logging_object if trace_id is None and standard_logging_object is not None: - trace_id = cast( - Optional[str], standard_logging_object.get("trace_id") - ) + trace_id = cast(Optional[str], standard_logging_object.get("trace_id")) # Fallback to litellm_call_id if no trace_id found if trace_id is None: trace_id = litellm_call_id @@ -833,13 +835,26 @@ class LangFuseLogger: kwargs.get("model", ""), custom_llm_provider, metadata ) + # Use whitelisted model_parameters from StandardLoggingPayload + # to prevent leaking secrets (api_key, auth headers, etc.) + if standard_logging_object is not None: + sanitized_model_params = standard_logging_object.get( + "model_parameters", optional_params + ) + else: + sanitized_model_params = ( + ModelParamHelper.get_standard_logging_model_parameters( + optional_params + ) + ) + generation_params = { "name": generation_name, "id": clean_metadata.pop("generation_id", generation_id), "start_time": start_time, "end_time": end_time, "model": model_name, - "model_parameters": optional_params, + "model_parameters": sanitized_model_params, "input": input if not mask_input else "redacted-by-litellm", "output": output if not mask_output else "redacted-by-litellm", "usage": usage, From 20bf3aa8070a4dc150bb8edaddb6bd3306b83a53 Mon Sep 17 00:00:00 2001 From: Harshit28j Date: Tue, 10 Mar 2026 17:16:46 +0530 Subject: [PATCH 02/60] fix: pop sensitive keys from langfuse --- litellm/litellm_core_utils/litellm_logging.py | 50 +++++++++---------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index e450b233c7e..73a8b92c1bb 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -1653,9 +1653,7 @@ class Logging(LiteLLMLoggingBaseClass): self.model_call_details[ "standard_logging_object" - ] = self._build_standard_logging_payload( - logging_result, start_time, end_time - ) + ] = self._build_standard_logging_payload(logging_result, start_time, end_time) if ( standard_logging_payload := self.model_call_details.get( @@ -2518,9 +2516,7 @@ class Logging(LiteLLMLoggingBaseClass): ## STANDARDIZED LOGGING PAYLOAD self.model_call_details[ "standard_logging_object" - ] = self._build_standard_logging_payload( - result, start_time, end_time - ) + ] = self._build_standard_logging_payload(result, start_time, end_time) # print standard logging payload if ( @@ -4195,8 +4191,7 @@ def _maybe_auto_initialize_arize_phoenix(_in_memory_loggers: list) -> None: litellm.logging_callback_manager.add_litellm_callback(phoenix_logger) verbose_logger.info( - "Auto-initialized Arize Phoenix logger alongside otel " - "(endpoint=%s)", + "Auto-initialized Arize Phoenix logger alongside otel " "(endpoint=%s)", arize_phoenix_config.endpoint, ) except Exception as e: @@ -4755,9 +4750,11 @@ class StandardLoggingPayloadSetup: ).model_dump() if isinstance(_raw, dict): if ResponseAPILoggingUtils._is_response_api_usage(_raw): - return ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage( - _raw - ).model_dump() + return ( + ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage( + _raw + ).model_dump() + ) return _raw if isinstance(_raw, Usage): return _raw.model_dump() @@ -5482,21 +5479,23 @@ def scrub_sensitive_keys_in_metadata(litellm_params: Optional[dict]): litellm_params["_langfuse_masking_function"] = masking_fn litellm_params["metadata"] = metadata - ## check user_api_key_metadata for sensitive logging keys - cleaned_user_api_key_metadata = {} - if "user_api_key_metadata" in metadata and isinstance( - metadata["user_api_key_metadata"], dict - ): - for k, v in metadata["user_api_key_metadata"].items(): - if k == "logging": # prevent logging user logging keys - cleaned_user_api_key_metadata[ - k - ] = "scrubbed_by_litellm_for_sensitive_keys" - else: - cleaned_user_api_key_metadata[k] = v + ## remove sensitive logging/callback keys from metadata dicts + ## these contain credentials (langfuse_secret_key, langfuse_public_key, etc.) + _sensitive_keys = {"logging", "callback_settings"} - metadata["user_api_key_metadata"] = cleaned_user_api_key_metadata - litellm_params["metadata"] = metadata + for metadata_field in ( + "user_api_key_metadata", + "user_api_key_auth_metadata", + "user_api_key_team_metadata", + ): + if metadata_field in metadata and isinstance(metadata[metadata_field], dict): + for sensitive_key in _sensitive_keys: + metadata[metadata_field].pop(sensitive_key, None) + + ## remove user_api_key_auth entirely - contains full auth object with nested credentials + metadata.pop("user_api_key_auth", None) + + litellm_params["metadata"] = metadata return litellm_params @@ -5603,4 +5602,3 @@ def create_dummy_standard_logging_payload() -> StandardLoggingPayload: model_parameters={"stream": True}, hidden_params=hidden_params, ) - From feee689e878f58b2a10e00346a8c3d53440c3e64 Mon Sep 17 00:00:00 2001 From: joereyna Date: Thu, 12 Mar 2026 12:34:17 -0700 Subject: [PATCH 03/60] fix: set oauth2_flow when building MCPServer in _execute_with_mcp_client --- litellm/proxy/_experimental/mcp_server/rest_endpoints.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py index 307caa2fbc8..298fe363425 100644 --- a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py +++ b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py @@ -906,9 +906,11 @@ if MCP_AVAILABLE: client_id, client_secret, scopes = _extract_credentials(request) _oauth2_flow: Optional[Literal["client_credentials", "authorization_code"]] = ( - "client_credentials" - if client_id and client_secret and request.token_url - else None + request.oauth2_flow or ( + "client_credentials" + if client_id and client_secret and request.token_url + else None + ) ) server_model = MCPServer( From 377b79afae88aee52574c41334f8cd1a8b2d9be3 Mon Sep 17 00:00:00 2001 From: joereyna Date: Thu, 12 Mar 2026 18:21:19 -0700 Subject: [PATCH 04/60] fix: add oauth2_flow to NewMCPServerRequest and guard auto-detect with token_url --- litellm/proxy/_types.py | 1 + 1 file changed, 1 insertion(+) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index b7ac4212cbd..f4c3a03fb6f 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -1123,6 +1123,7 @@ class NewMCPServerRequest(LiteLLMPydanticObjectBase): authorization_url: Optional[str] = None token_url: Optional[str] = None registration_url: Optional[str] = None + oauth2_flow: Optional[str] = None allow_all_keys: bool = False available_on_public_internet: bool = True is_byok: bool = False From cd7b25842b10c5e38a0db820fe9e1a5e07cc7aba Mon Sep 17 00:00:00 2001 From: joereyna Date: Fri, 13 Mar 2026 19:46:02 -0700 Subject: [PATCH 05/60] fix: narrow oauth2_flow type to Literal in NewMCPServerRequest --- litellm/proxy/_types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index f4c3a03fb6f..dfc7c3f353c 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -1123,7 +1123,7 @@ class NewMCPServerRequest(LiteLLMPydanticObjectBase): authorization_url: Optional[str] = None token_url: Optional[str] = None registration_url: Optional[str] = None - oauth2_flow: Optional[str] = None + oauth2_flow: Optional[Literal["client_credentials", "authorization_code"]] = None allow_all_keys: bool = False available_on_public_internet: bool = True is_byok: bool = False From 6970e1000c6fc874e9351e06eba79e3a6054e642 Mon Sep 17 00:00:00 2001 From: Ryan Crabbe Date: Sat, 14 Mar 2026 15:28:37 -0700 Subject: [PATCH 06/60] fix: align DefaultInternalUserParams Pydantic default with runtime fallback The Pydantic default for user_role was INTERNAL_USER, but all runtime provisioning paths (SSO, SCIM, JWT) fall back to INTERNAL_USER_VIEW_ONLY when no settings are saved. This caused the UI to show "Internal User" on fresh instances while new users actually got "Internal Viewer". --- litellm/proxy/_types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index b7ac4212cbd..8bc77d300c6 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -4262,7 +4262,7 @@ class DefaultInternalUserParams(LiteLLMPydanticObjectBase): LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY, ] ] = Field( - default=LitellmUserRoles.INTERNAL_USER, + default=LitellmUserRoles.INTERNAL_USER_VIEW_ONLY, description="Default role assigned to new users created", ) max_budget: Optional[float] = Field( From c60f1dd590c23fe5b79699f9f3c4176a541c6b19 Mon Sep 17 00:00:00 2001 From: Ryan Crabbe Date: Sat, 14 Mar 2026 15:36:26 -0700 Subject: [PATCH 07/60] test: add regression test for fresh-instance default role sync Asserts that GET /get/internal_user_settings returns INTERNAL_USER_VIEW_ONLY on a fresh DB with no saved settings, matching the runtime fallback in SSO/SCIM/JWT provisioning. --- .../test_proxy_setting_endpoints.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py b/tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py index 7378b14cdf5..15ec901ab5e 100644 --- a/tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py +++ b/tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py @@ -111,6 +111,43 @@ class TestProxySettingEndpoints: assert "user_role" in data["field_schema"]["properties"] assert "description" in data["field_schema"]["properties"]["user_role"] + def test_get_internal_user_settings_fresh_db_defaults_to_viewer( + self, mock_auth, monkeypatch + ): + """ + On a fresh DB with no saved settings, the GET endpoint should return + INTERNAL_USER_VIEW_ONLY as the default role — matching the runtime + fallback in SSO/SCIM/JWT provisioning paths. + """ + # Simulate fresh DB: no default_internal_user_params in config + empty_config = { + "litellm_settings": {}, + "general_settings": {}, + "environment_variables": {}, + } + + from litellm.proxy.proxy_server import proxy_config + + monkeypatch.setattr( + proxy_config, "get_config", lambda: empty_config # sync, wrapped by endpoint + ) + + import asyncio + + async def mock_get_config(): + return empty_config + + monkeypatch.setattr(proxy_config, "get_config", mock_get_config) + + response = client.get("/get/internal_user_settings") + assert response.status_code == 200 + + values = response.json()["values"] + assert values["user_role"] == LitellmUserRoles.INTERNAL_USER_VIEW_ONLY, ( + f"Fresh DB should default to INTERNAL_USER_VIEW_ONLY, got {values['user_role']}. " + "The Pydantic default must match the runtime fallback." + ) + def test_update_internal_user_settings( self, mock_proxy_config, mock_auth, monkeypatch ): From c95783e6411cd3fc4ef23ae45a0e2c4c41dd2c7d Mon Sep 17 00:00:00 2001 From: ryan-crabbe <128659760+ryan-crabbe@users.noreply.github.com> Date: Sat, 14 Mar 2026 15:42:17 -0700 Subject: [PATCH 08/60] Update tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- .../proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py b/tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py index 15ec901ab5e..bd9968ae936 100644 --- a/tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py +++ b/tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py @@ -128,12 +128,6 @@ class TestProxySettingEndpoints: from litellm.proxy.proxy_server import proxy_config - monkeypatch.setattr( - proxy_config, "get_config", lambda: empty_config # sync, wrapped by endpoint - ) - - import asyncio - async def mock_get_config(): return empty_config From 5d33cc66a0ba3327193f85d96b990ee27476b7fb Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 16 Mar 2026 12:53:20 -0700 Subject: [PATCH 09/60] Add unit tests for 5 previously untested UI dashboard files Tests added for: UiLoadingSpinner, HashicorpVaultEmptyPlaceholder, PageVisibilitySettings, errorUtils, and mcpToolCrudClassification. Co-Authored-By: Claude Opus 4.6 --- .../HashicorpVaultEmptyPlaceholder.test.tsx | 29 +++++++ .../PageVisibilitySettings.test.tsx | 77 +++++++++++++++++++ .../components/ui/ui-loading-spinner.test.tsx | 22 ++++++ .../src/utils/errorUtils.test.ts | 40 ++++++++++ .../utils/mcpToolCrudClassification.test.ts | 63 +++++++++++++++ 5 files changed, 231 insertions(+) create mode 100644 ui/litellm-dashboard/src/components/Settings/AdminSettings/HashicorpVault/HashicorpVaultEmptyPlaceholder.test.tsx create mode 100644 ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/PageVisibilitySettings.test.tsx create mode 100644 ui/litellm-dashboard/src/components/ui/ui-loading-spinner.test.tsx create mode 100644 ui/litellm-dashboard/src/utils/errorUtils.test.ts create mode 100644 ui/litellm-dashboard/src/utils/mcpToolCrudClassification.test.ts diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/HashicorpVault/HashicorpVaultEmptyPlaceholder.test.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/HashicorpVault/HashicorpVaultEmptyPlaceholder.test.tsx new file mode 100644 index 00000000000..4214d5fda7c --- /dev/null +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/HashicorpVault/HashicorpVaultEmptyPlaceholder.test.tsx @@ -0,0 +1,29 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import HashicorpVaultEmptyPlaceholder from "./HashicorpVaultEmptyPlaceholder"; + +describe("HashicorpVaultEmptyPlaceholder", () => { + it("should render the empty state message and configure button", () => { + render(); + expect(screen.getByText("No Vault Configuration Found")).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /configure vault/i })).toBeInTheDocument(); + }); + + it("should call onAdd when the configure button is clicked", async () => { + const onAdd = vi.fn(); + const user = userEvent.setup(); + render(); + + await user.click(screen.getByRole("button", { name: /configure vault/i })); + + expect(onAdd).toHaveBeenCalledOnce(); + }); + + it("should display the description text about Vault purpose", () => { + render(); + expect( + screen.getByText(/Configure Hashicorp Vault to securely manage provider API keys/), + ).toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/PageVisibilitySettings.test.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/PageVisibilitySettings.test.tsx new file mode 100644 index 00000000000..798572b2037 --- /dev/null +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/PageVisibilitySettings.test.tsx @@ -0,0 +1,77 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import PageVisibilitySettings from "./PageVisibilitySettings"; + +vi.mock("@/components/page_utils", () => ({ + getAvailablePages: () => [ + { page: "usage", label: "Usage", description: "View usage stats", group: "Analytics" }, + { page: "models", label: "Models", description: "Manage models", group: "Analytics" }, + { page: "keys", label: "API Keys", description: "Manage API keys", group: "Access" }, + ], +})); + +describe("PageVisibilitySettings", () => { + it("should render the not-set tag when enabledPagesInternalUsers is null", () => { + render( + , + ); + expect(screen.getByText("Not set (all pages visible)")).toBeInTheDocument(); + }); + + it("should show the selected page count tag when pages are configured", () => { + render( + , + ); + expect(screen.getByText("2 pages selected")).toBeInTheDocument(); + }); + + it("should show singular 'page' when exactly one page is selected", () => { + render( + , + ); + expect(screen.getByText("1 page selected")).toBeInTheDocument(); + }); + + it("should call onUpdate with null when reset button is clicked", async () => { + const onUpdate = vi.fn(); + const user = userEvent.setup(); + render( + , + ); + + // Expand the collapse panel first to reveal the reset button + await user.click(screen.getByRole("button", { name: /configure page visibility/i })); + await user.click(await screen.findByRole("button", { name: /reset to default/i })); + + expect(onUpdate).toHaveBeenCalledWith({ enabled_ui_pages_internal_users: null }); + }); + + it("should display the property description when provided", () => { + render( + , + ); + expect(screen.getByText("Controls which pages are visible")).toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/ui/ui-loading-spinner.test.tsx b/ui/litellm-dashboard/src/components/ui/ui-loading-spinner.test.tsx new file mode 100644 index 00000000000..73b2de54c4c --- /dev/null +++ b/ui/litellm-dashboard/src/components/ui/ui-loading-spinner.test.tsx @@ -0,0 +1,22 @@ +import { describe, it, expect } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { UiLoadingSpinner } from "./ui-loading-spinner"; + +describe("UiLoadingSpinner", () => { + it("should render an SVG element", () => { + render(); + expect(screen.getByTestId("spinner")).toBeInTheDocument(); + }); + + it("should apply custom className alongside default classes", () => { + render(); + const svg = screen.getByTestId("spinner"); + expect(svg).toHaveClass("text-red-500"); + expect(svg).toHaveClass("animate-spin"); + }); + + it("should spread additional SVG props onto the element", () => { + render(); + expect(screen.getByLabelText("Loading")).toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/utils/errorUtils.test.ts b/ui/litellm-dashboard/src/utils/errorUtils.test.ts new file mode 100644 index 00000000000..ccd65717f40 --- /dev/null +++ b/ui/litellm-dashboard/src/utils/errorUtils.test.ts @@ -0,0 +1,40 @@ +import { describe, it, expect } from "vitest"; +import { extractErrorMessage } from "./errorUtils"; + +describe("extractErrorMessage", () => { + it("should return the message from an Error instance", () => { + expect(extractErrorMessage(new Error("Something broke"))).toBe("Something broke"); + }); + + it("should return detail when it is a string", () => { + expect(extractErrorMessage({ detail: "Not found" })).toBe("Not found"); + }); + + it("should join msg fields from a FastAPI 422 detail array", () => { + const err = { + detail: [ + { msg: "field required", loc: ["body", "name"], type: "value_error" }, + { msg: "invalid type", loc: ["body", "age"], type: "type_error" }, + ], + }; + expect(extractErrorMessage(err)).toBe("field required; invalid type"); + }); + + it("should extract error from nested detail object", () => { + expect(extractErrorMessage({ detail: { error: "bad request" } })).toBe("bad request"); + }); + + it("should fall back to message property on plain objects", () => { + expect(extractErrorMessage({ message: "fallback msg" })).toBe("fallback msg"); + }); + + it("should JSON.stringify unknown object shapes", () => { + expect(extractErrorMessage({ foo: "bar" })).toBe('{"foo":"bar"}'); + }); + + it("should stringify primitive non-object values", () => { + expect(extractErrorMessage(42)).toBe("42"); + expect(extractErrorMessage(null)).toBe("null"); + expect(extractErrorMessage(undefined)).toBe("undefined"); + }); +}); diff --git a/ui/litellm-dashboard/src/utils/mcpToolCrudClassification.test.ts b/ui/litellm-dashboard/src/utils/mcpToolCrudClassification.test.ts new file mode 100644 index 00000000000..0ae8ae70bec --- /dev/null +++ b/ui/litellm-dashboard/src/utils/mcpToolCrudClassification.test.ts @@ -0,0 +1,63 @@ +import { describe, it, expect } from "vitest"; +import { classifyToolOp, groupToolsByCrud } from "./mcpToolCrudClassification"; + +describe("classifyToolOp", () => { + it("should classify read operations by name", () => { + expect(classifyToolOp("get-users")).toBe("read"); + expect(classifyToolOp("list-items")).toBe("read"); + expect(classifyToolOp("search documents")).toBe("read"); + }); + + it("should classify delete operations by name", () => { + expect(classifyToolOp("delete-user")).toBe("delete"); + expect(classifyToolOp("remove-item")).toBe("delete"); + expect(classifyToolOp("purge-cache")).toBe("delete"); + }); + + it("should classify create operations by name", () => { + expect(classifyToolOp("create-user")).toBe("create"); + expect(classifyToolOp("add-item")).toBe("create"); + expect(classifyToolOp("upload-file")).toBe("create"); + }); + + it("should classify update operations by name", () => { + expect(classifyToolOp("update-settings")).toBe("update"); + expect(classifyToolOp("edit-profile")).toBe("update"); + expect(classifyToolOp("rename-file")).toBe("update"); + }); + + it("should prioritize read over delete for names like get-removed-entries", () => { + expect(classifyToolOp("get-removed-entries")).toBe("read"); + expect(classifyToolOp("list-deleted-items")).toBe("read"); + }); + + it("should fall back to description when name is unrecognised", () => { + expect(classifyToolOp("mytool", "This will delete the record")).toBe("delete"); + expect(classifyToolOp("mytool", "fetch data from the API")).toBe("read"); + }); + + it("should return unknown when neither name nor description match", () => { + expect(classifyToolOp("my_tool")).toBe("unknown"); + expect(classifyToolOp("my_tool", "does something")).toBe("unknown"); + }); +}); + +describe("groupToolsByCrud", () => { + it("should group tools into their CRUD categories", () => { + const tools = [ + { name: "get-user", description: "" }, + { name: "create-item", description: "" }, + { name: "delete-record", description: "" }, + { name: "update-settings", description: "" }, + { name: "mysteryop", description: "" }, + ]; + + const groups = groupToolsByCrud(tools); + + expect(groups.read).toHaveLength(1); + expect(groups.create).toHaveLength(1); + expect(groups.delete).toHaveLength(1); + expect(groups.update).toHaveLength(1); + expect(groups.unknown).toHaveLength(1); + }); +}); From 0c1739390bd5b20928a0830462bd137bed1af126 Mon Sep 17 00:00:00 2001 From: joereyna Date: Mon, 16 Mar 2026 13:17:14 -0700 Subject: [PATCH 10/60] fix: remove skip decorators from m2m tests now that oauth2_flow is set --- .../proxy/_experimental/mcp_server/test_rest_endpoints.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_rest_endpoints.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_rest_endpoints.py index 3a01fe19edb..3acbe5465f2 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_rest_endpoints.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_rest_endpoints.py @@ -158,7 +158,6 @@ class TestExecuteWithMcpClient: @pytest.mark.asyncio - @pytest.mark.skip(reason="PR #23187 changed has_client_credentials to require explicit oauth2_flow opt-in, but NewMCPServerRequest and _execute_with_mcp_client were not updated - needs fix") async def test_m2m_credentials_forwarded_to_server_model(self, monkeypatch): """M2M OAuth credentials (client_id, client_secret) from the nested ``credentials`` dict must be forwarded to the MCPServer model so that @@ -213,7 +212,6 @@ class TestExecuteWithMcpClient: assert server.has_client_credentials is True @pytest.mark.asyncio - @pytest.mark.skip(reason="PR #23187 changed has_client_credentials to require explicit oauth2_flow opt-in, but NewMCPServerRequest and _execute_with_mcp_client were not updated - needs fix") async def test_m2m_drops_incoming_oauth2_headers(self, monkeypatch): """For M2M OAuth servers the incoming Authorization header (which carries the litellm API key) must NOT be forwarded as extra_headers — otherwise From bc810f99e4bf6cdfd6d7831888b7d82744a8663a Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 16 Mar 2026 14:46:50 -0700 Subject: [PATCH 11/60] [Fix] Privilege escalation: restrict /key/block, /key/unblock, and max_budget updates to admins Non-admin users (INTERNAL_USER) could call /key/block and /key/unblock on arbitrary keys, and modify max_budget on their own keys via /key/update. These endpoints are now restricted to proxy admins, team admins, or org admins. Co-Authored-By: Claude Opus 4.6 --- .../key_management_endpoints.py | 94 ++++- .../test_key_management_endpoints.py | 350 ++++++++++++++++++ 2 files changed, 442 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index 1c0c212b60b..2907f5f089f 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -54,6 +54,7 @@ from litellm.proxy.auth.user_api_key_auth import user_api_key_auth from litellm.proxy.common_utils.timezone_utils import get_budget_reset_time from litellm.proxy.hooks.key_management_event_hooks import KeyManagementEventHooks from litellm.proxy.management_endpoints.common_utils import ( + _is_user_org_admin_for_team, _is_user_team_admin, _set_object_metadata_field, ) @@ -1836,6 +1837,18 @@ async def _validate_update_key_data( user_api_key_cache=user_api_key_cache, ) + # Admin-only: only proxy admins, team admins, or org admins can modify max_budget + if data.max_budget is not None and data.max_budget != existing_key_row.max_budget: + if prisma_client is not None: + hashed_key = existing_key_row.token + await _check_key_admin_access( + user_api_key_dict=user_api_key_dict, + hashed_token=hashed_key, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + route="/key/update (max_budget)", + ) + # Check team limits if key has a team_id (from request or existing key) team_obj: Optional[LiteLLM_TeamTableCachedObj] = None _team_id_to_check = data.team_id or getattr(existing_key_row, "team_id", None) @@ -4733,6 +4746,65 @@ def _get_condition_to_filter_out_ui_session_tokens() -> Dict[str, Any]: } +async def _check_key_admin_access( + user_api_key_dict: UserAPIKeyAuth, + hashed_token: str, + prisma_client: Any, + user_api_key_cache: DualCache, + route: str, +) -> None: + """ + Check that the caller has admin privileges for the target key. + + Allowed callers: + - Proxy admin + - Team admin for the key's team + - Org admin for the key's team's organization + + Raises HTTPException(403) if the caller is not authorized. + """ + from litellm.proxy.proxy_server import proxy_logging_obj + + if user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value: + return + + # Look up the target key to find its team + target_key_row = await prisma_client.db.litellm_verificationtoken.find_unique( + where={"token": hashed_token} + ) + if target_key_row is None: + raise HTTPException( + status_code=404, + detail={"error": f"Key not found: {hashed_token}"}, + ) + + # If the key belongs to a team, check team admin / org admin + if target_key_row.team_id: + team_obj = await get_team_object( + team_id=target_key_row.team_id, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + check_db_only=True, + ) + if team_obj is not None: + if _is_user_team_admin( + user_api_key_dict=user_api_key_dict, team_obj=team_obj + ): + return + if await _is_user_org_admin_for_team( + user_api_key_dict=user_api_key_dict, team_obj=team_obj + ): + return + + raise HTTPException( + status_code=403, + detail={ + "error": f"Only proxy admins, team admins, or org admins can call {route}. " + f"user_role={user_api_key_dict.user_role}, user_id={user_api_key_dict.user_id}" + }, + ) + + @router.post( "/key/block", tags=["key management"], dependencies=[Depends(user_api_key_auth)] ) @@ -4762,7 +4834,7 @@ async def block_key( }' ``` - Note: This is an admin-only endpoint. Only proxy admins can block keys. + Note: This is an admin-only endpoint. Only proxy admins, team admins, or org admins can block keys. """ from litellm.proxy.proxy_server import ( create_audit_log_for_update, @@ -4788,6 +4860,15 @@ async def block_key( else: hashed_token = data.key + # Admin-only: only proxy admins, team admins, or org admins can block keys + await _check_key_admin_access( + user_api_key_dict=user_api_key_dict, + hashed_token=hashed_token, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + route="/key/block", + ) + if litellm.store_audit_logs is True: # make an audit log for key update record = await prisma_client.db.litellm_verificationtoken.find_unique( @@ -4876,7 +4957,7 @@ async def unblock_key( }' ``` - Note: This is an admin-only endpoint. Only proxy admins can unblock keys. + Note: This is an admin-only endpoint. Only proxy admins, team admins, or org admins can unblock keys. """ from litellm.proxy.proxy_server import ( create_audit_log_for_update, @@ -4902,6 +4983,15 @@ async def unblock_key( else: hashed_token = data.key + # Admin-only: only proxy admins, team admins, or org admins can unblock keys + await _check_key_admin_access( + user_api_key_dict=user_api_key_dict, + hashed_token=hashed_token, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + route="/key/unblock", + ) + if litellm.store_audit_logs is True: # make an audit log for key update record = await prisma_client.db.litellm_verificationtoken.find_unique( diff --git a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py index cfc16808afb..4bb0cd72ed2 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py @@ -7185,3 +7185,353 @@ def test_update_key_request_has_organization_id(): # Also verify it defaults to None data_no_org = UpdateKeyRequest(key="sk-test-key") assert data_no_org.organization_id is None + + +# ============================================================================ +# Tests for admin-only access on /key/block, /key/unblock, /key/update max_budget +# ============================================================================ + + +def _setup_block_unblock_mocks(monkeypatch, mock_key_team_id=None): + """Helper to set up common mocks for block/unblock tests.""" + mock_prisma_client = AsyncMock() + mock_user_api_key_cache = MagicMock() + mock_proxy_logging_obj = MagicMock() + + test_hashed_token = ( + "a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd" + ) + + mock_key_record = MagicMock() + mock_key_record.token = test_hashed_token + mock_key_record.blocked = False + mock_key_record.team_id = mock_key_team_id + mock_key_record.model_dump_json.return_value = ( + f'{{"token": "{test_hashed_token}", "blocked": false}}' + ) + + mock_prisma_client.db.litellm_verificationtoken.find_unique = AsyncMock( + return_value=mock_key_record + ) + mock_prisma_client.db.litellm_verificationtoken.update = AsyncMock( + return_value=mock_key_record + ) + + mock_key_object = MagicMock() + mock_key_object.blocked = True + + def mock_hash_token(token): + if token.startswith("sk-"): + return test_hashed_token + return token + + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) + monkeypatch.setattr( + "litellm.proxy.proxy_server.user_api_key_cache", mock_user_api_key_cache + ) + monkeypatch.setattr( + "litellm.proxy.proxy_server.proxy_logging_obj", mock_proxy_logging_obj + ) + monkeypatch.setattr("litellm.proxy.proxy_server.hash_token", mock_hash_token) + monkeypatch.setattr("litellm.store_audit_logs", False) + + async def mock_get_key_object(**kwargs): + return mock_key_object + + async def mock_cache_key_object(**kwargs): + pass + + monkeypatch.setattr( + "litellm.proxy.management_endpoints.key_management_endpoints.get_key_object", + mock_get_key_object, + ) + monkeypatch.setattr( + "litellm.proxy.management_endpoints.key_management_endpoints._cache_key_object", + mock_cache_key_object, + ) + + return mock_prisma_client, test_hashed_token + + +@pytest.mark.asyncio +async def test_block_key_rejected_for_internal_user(monkeypatch): + """Internal users should not be able to block keys.""" + from litellm.proxy._types import BlockKeyRequest + from litellm.proxy.management_endpoints.key_management_endpoints import block_key + + _setup_block_unblock_mocks(monkeypatch) + + mock_request = MagicMock() + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + api_key="sk-internal", + user_id="internal_user", + ) + + with pytest.raises(HTTPException) as exc: + await block_key( + data=BlockKeyRequest(key="sk-test123456789"), + http_request=mock_request, + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + + assert exc.value.status_code == 403 + assert "Only proxy admins, team admins, or org admins" in str(exc.value.detail) + + +@pytest.mark.asyncio +async def test_unblock_key_rejected_for_internal_user(monkeypatch): + """Internal users should not be able to unblock keys.""" + from litellm.proxy._types import BlockKeyRequest + from litellm.proxy.management_endpoints.key_management_endpoints import unblock_key + + _setup_block_unblock_mocks(monkeypatch) + + mock_request = MagicMock() + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + api_key="sk-internal", + user_id="internal_user", + ) + + with pytest.raises(HTTPException) as exc: + await unblock_key( + data=BlockKeyRequest(key="sk-test123456789"), + http_request=mock_request, + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + + assert exc.value.status_code == 403 + assert "Only proxy admins, team admins, or org admins" in str(exc.value.detail) + + +@pytest.mark.asyncio +async def test_block_key_allowed_for_proxy_admin(monkeypatch): + """Proxy admins should be able to block keys.""" + from litellm.proxy._types import BlockKeyRequest + from litellm.proxy.management_endpoints.key_management_endpoints import block_key + + _setup_block_unblock_mocks(monkeypatch) + + mock_request = MagicMock() + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.PROXY_ADMIN, + api_key="sk-admin", + user_id="admin_user", + ) + + result = await block_key( + data=BlockKeyRequest(key="sk-test123456789"), + http_request=mock_request, + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + assert result is not None + + +@pytest.mark.asyncio +async def test_block_key_allowed_for_team_admin(monkeypatch): + """Team admins should be able to block keys belonging to their team.""" + from litellm.proxy._types import BlockKeyRequest + from litellm.proxy.management_endpoints.key_management_endpoints import block_key + + team_id = "team-123" + _setup_block_unblock_mocks(monkeypatch, mock_key_team_id=team_id) + + # Mock get_team_object to return a team where the user is admin + team_obj = LiteLLM_TeamTableCachedObj( + team_id=team_id, + members_with_roles=[ + Member(user_id="team_admin_user", role="admin"), + ], + ) + + async def mock_get_team_object(**kwargs): + return team_obj + + monkeypatch.setattr( + "litellm.proxy.management_endpoints.key_management_endpoints.get_team_object", + mock_get_team_object, + ) + + mock_request = MagicMock() + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + api_key="sk-teamadmin", + user_id="team_admin_user", + ) + + result = await block_key( + data=BlockKeyRequest(key="sk-test123456789"), + http_request=mock_request, + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + assert result is not None + + +@pytest.mark.asyncio +async def test_update_key_max_budget_rejected_for_internal_user(monkeypatch): + """Internal users should not be able to modify max_budget on keys.""" + from litellm.proxy.management_endpoints.key_management_endpoints import ( + update_key_fn, + ) + + mock_prisma_client = AsyncMock() + mock_user_api_key_cache = AsyncMock() + mock_proxy_logging_obj = MagicMock() + + test_hashed_token = ( + "a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd" + ) + + # Mock existing key row + mock_existing_key = MagicMock() + mock_existing_key.token = test_hashed_token + mock_existing_key.user_id = "internal_user" + mock_existing_key.team_id = None + mock_existing_key.project_id = None + mock_existing_key.max_budget = 10.0 + mock_existing_key.models = [] + mock_existing_key.model_dump.return_value = { + "token": test_hashed_token, + "user_id": "internal_user", + "team_id": None, + "max_budget": 10.0, + } + + mock_prisma_client.get_data = AsyncMock(return_value=mock_existing_key) + mock_prisma_client.db.litellm_verificationtoken.find_unique = AsyncMock( + return_value=mock_existing_key + ) + + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) + monkeypatch.setattr( + "litellm.proxy.proxy_server.user_api_key_cache", mock_user_api_key_cache + ) + monkeypatch.setattr( + "litellm.proxy.proxy_server.proxy_logging_obj", mock_proxy_logging_obj + ) + monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", None) + monkeypatch.setattr("litellm.proxy.proxy_server.premium_user", True) + + mock_request = MagicMock() + mock_request.query_params = {} + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + api_key="sk-internal", + user_id="internal_user", + ) + + with pytest.raises(ProxyException) as exc: + await update_key_fn( + request=mock_request, + data=UpdateKeyRequest(key=test_hashed_token, max_budget=999999), + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + + assert str(exc.value.code) == "403" + assert "Only proxy admins, team admins, or org admins" in str(exc.value.message) + + +@pytest.mark.asyncio +async def test_update_key_non_budget_fields_allowed_for_internal_user(monkeypatch): + """Internal users should still be able to update non-budget fields on their own keys.""" + from litellm.proxy.management_endpoints.key_management_endpoints import ( + update_key_fn, + ) + + mock_prisma_client = AsyncMock() + mock_user_api_key_cache = AsyncMock() + mock_proxy_logging_obj = MagicMock() + + test_hashed_token = ( + "a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd" + ) + + # Mock existing key row + mock_existing_key = MagicMock() + mock_existing_key.token = test_hashed_token + mock_existing_key.user_id = "internal_user" + mock_existing_key.team_id = None + mock_existing_key.project_id = None + mock_existing_key.max_budget = 10.0 + mock_existing_key.key_alias = None + mock_existing_key.models = [] + mock_existing_key.model_dump.return_value = { + "token": test_hashed_token, + "user_id": "internal_user", + "team_id": None, + "max_budget": 10.0, + } + + mock_updated_key = MagicMock() + mock_updated_key.token = test_hashed_token + mock_updated_key.key_alias = "my-alias" + + mock_prisma_client.get_data = AsyncMock(return_value=mock_existing_key) + mock_prisma_client.update_data = AsyncMock(return_value=mock_updated_key) + mock_prisma_client.db.litellm_verificationtoken.find_unique = AsyncMock( + return_value=mock_existing_key + ) + + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) + monkeypatch.setattr( + "litellm.proxy.proxy_server.user_api_key_cache", mock_user_api_key_cache + ) + monkeypatch.setattr( + "litellm.proxy.proxy_server.proxy_logging_obj", mock_proxy_logging_obj + ) + monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", None) + monkeypatch.setattr("litellm.proxy.proxy_server.premium_user", True) + monkeypatch.setattr("litellm.store_audit_logs", False) + + def mock_hash_token(token): + return test_hashed_token + + monkeypatch.setattr("litellm.proxy.proxy_server.hash_token", mock_hash_token) + + async def mock_cache_key_object(**kwargs): + pass + + async def mock_delete_cache_key_object(**kwargs): + pass + + monkeypatch.setattr( + "litellm.proxy.management_endpoints.key_management_endpoints._cache_key_object", + mock_cache_key_object, + ) + monkeypatch.setattr( + "litellm.proxy.management_endpoints.key_management_endpoints._delete_cache_key_object", + mock_delete_cache_key_object, + ) + + # Mock _enforce_unique_key_alias to avoid DB call + async def mock_enforce_unique_key_alias(**kwargs): + pass + + monkeypatch.setattr( + "litellm.proxy.management_endpoints.key_management_endpoints._enforce_unique_key_alias", + mock_enforce_unique_key_alias, + ) + + mock_request = MagicMock() + mock_request.query_params = {} + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + api_key="sk-internal", + user_id="internal_user", + ) + + # Updating key_alias (non-budget field) should succeed + result = await update_key_fn( + request=mock_request, + data=UpdateKeyRequest(key=test_hashed_token, key_alias="my-alias"), + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + + assert result is not None From 016667b2fab1b15340d6a55c29675fd1b57779da Mon Sep 17 00:00:00 2001 From: Ryan Crabbe Date: Mon, 16 Mar 2026 15:04:22 -0700 Subject: [PATCH 12/60] chore(ui): migrate DefaultUserSettings buttons from Tremor to antd --- .../src/components/DefaultUserSettings.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/ui/litellm-dashboard/src/components/DefaultUserSettings.tsx b/ui/litellm-dashboard/src/components/DefaultUserSettings.tsx index 988a3bcec92..9c38d628b95 100644 --- a/ui/litellm-dashboard/src/components/DefaultUserSettings.tsx +++ b/ui/litellm-dashboard/src/components/DefaultUserSettings.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from "react"; -import { Card, Title, Text, Divider, Button, TextInput } from "@tremor/react"; -import { Typography, Spin, Switch, Select, InputNumber } from "antd"; +import { Card, Title, Text, Divider, TextInput } from "@tremor/react"; +import { Button, Typography, Spin, Switch, Select, InputNumber } from "antd"; import { PlusOutlined, DeleteOutlined } from "@ant-design/icons"; import { getInternalUserSettings, updateInternalUserSettings, modelAvailableCall } from "./networking"; import BudgetDurationDropdown, { getBudgetDurationLabel } from "./common_components/budget_duration_dropdown"; @@ -160,9 +160,8 @@ const DefaultUserSettings: React.FC = ({
Team {index + 1}
))} - @@ -462,7 +461,6 @@ const DefaultUserSettings: React.FC = ({ (isEditing ? (
-
) : ( - + ))} From 278c9babc6ff75d7f0301b5b7f02afc266557d0b Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 16 Mar 2026 15:32:20 -0700 Subject: [PATCH 13/60] [Infra] Merging RC Branch with Main (#23786) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(test): add missing mocks for test_streamable_http_mcp_handler_mock The test was missing mocks for extract_mcp_auth_context and set_auth_context, causing the handler to fail silently in the except block instead of reaching session_manager.handle_request. This mirrors the fix already applied to the sibling test_sse_mcp_handler_mock. Co-authored-by: Ishaan Jaff * fix(ci): route OpenAI models through chat completions in pass-through tests The test_anthropic_messages_openai_model_streaming_cost_injection test fails because the OpenAI Responses API returns 400 for requests routed through the Anthropic Messages endpoint. Setting LITELLM_USE_CHAT_COMPLETIONS_URL_FOR_ANTHROPIC_MESSAGES=true routes OpenAI models through the stable chat completions path instead. Cost injection still works since it happens at the proxy level. Co-authored-by: Ishaan Jaff * fix(ci): fix assemblyai custom auth and router wildcard test flakiness 1. custom_auth_basic.py: Add user_role='proxy_admin' so the custom auth user can access management endpoints like /key/generate. The test test_assemblyai_transcribe_with_non_admin_key was hidden behind an earlier -x failure and was never reached before. 2. test_router_utils.py: Add flaky(retries=3) and increase sleep from 1s to 2s for test_router_get_model_group_usage_wildcard_routes. The async callback needs time to write usage to cache, and 1s is insufficient on slower CI hardware. Co-authored-by: Ishaan Jaff * ci: retrigger CI pipeline Co-authored-by: Ishaan Jaff * fix(mypy): use LitellmUserRoles enum instead of raw string in custom_auth_basic Fixes mypy error: Argument 'user_role' has incompatible type 'str'; expected 'LitellmUserRoles | None' Co-authored-by: Ishaan Jaff * fix: don't close HTTP/SDK clients on LLMClientCache eviction (#22926) * fix: don't close HTTP/SDK clients on LLMClientCache eviction Removing the _remove_key override that eagerly called aclose()/close() on evicted clients. Evicted clients may still be held by in-flight streaming requests; closing them causes: RuntimeError: Cannot send a request, as the client has been closed. This is a regression from commit fb72979432. Clients that are no longer referenced will be garbage-collected naturally. Explicit shutdown cleanup happens via close_litellm_async_clients(). Fixes production crashes after the 1-hour cache TTL expires. * test: update LLMClientCache unit tests for no-close-on-eviction behavior Flip the assertions: evicted clients must NOT be closed. Replace test_remove_key_closes_async_client → test_remove_key_does_not_close_async_client and equivalents for sync/eviction paths. Add test_remove_key_removes_plain_values for non-client cache entries. Remove test_background_tasks_cleaned_up_after_completion (no more _background_tasks). Remove test_remove_key_no_event_loop variant that depended on old behavior. * test: add e2e tests for OpenAI SDK client surviving cache eviction Add two new e2e tests using real AsyncOpenAI clients: - test_evicted_openai_sdk_client_stays_usable: verifies size-based eviction doesn't close the client - test_ttl_expired_openai_sdk_client_stays_usable: verifies TTL expiry eviction doesn't close the client Both tests sleep after eviction so any create_task()-based close would have time to run, making the regression detectable. Also expand the module docstring to explain why the sleep is required. * docs(AGENTS.md): add rule — never close HTTP/SDK clients on cache eviction * docs(CLAUDE.md): add HTTP client cache safety guideline * [Fix] Install bsdmainutils for column command in security scans The security_scans.sh script uses `column` to format vulnerability output, but the package wasn't installed in the CI environment. Co-Authored-By: Claude Opus 4.6 * fix: handle string callback values in prometheus multiproc setup When callbacks are configured as a plain string (e.g., `callbacks: "my_callback"`) instead of a list, the proxy crashes on startup with: TypeError: can only concatenate str (not "list") to str Normalize each callback setting to a list before concatenating. Co-Authored-By: Claude Opus 4.6 * bump: version 1.82.2 → 1.82.3 * fix(test): update test_startup_fails_when_db_setup_fails for opt-in enforcement The --enforce_prisma_migration_check flag is now required to trigger sys.exit(1) on DB migration failure, after #23675 flipped the default behavior to warn-and-continue. Co-Authored-By: Claude Opus 4.6 * fix(cost_calculator): use model name for per-request custom pricing when router_model_id has no pricing When custom pricing is passed as per-request kwargs (input_cost_per_token/output_cost_per_token), completion() registers pricing under the model name, but _select_model_name_for_cost_calc was selecting the router deployment hash (which has no pricing data), causing response_cost to be 0.0. Now checks whether the router_model_id entry actually has pricing before preferring it. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Cursor Agent Co-authored-by: Ishaan Jaff Co-authored-by: Ishaan Jaff Co-authored-by: Claude Opus 4.6 --- .circleci/config.yml | 1 + CLAUDE.md | 2 +- ci_cd/security_scans.sh | 2 +- litellm/cost_calculator.py | 9 ++- .../out/{404.html => 404/index.html} | 0 .../index.html} | 0 .../index.html} | 0 .../index.html} | 0 .../{budgets.html => budgets/index.html} | 0 .../{caching.html => caching/index.html} | 0 .../index.html} | 0 .../{old-usage.html => old-usage/index.html} | 0 .../{prompts.html => prompts/index.html} | 0 .../index.html} | 0 .../index.html} | 0 .../out/{login.html => login/index.html} | 0 .../out/{logs.html => logs/index.html} | 0 .../{callback.html => callback/index.html} | 0 .../{model-hub.html => model-hub/index.html} | 0 .../{model_hub.html => model_hub/index.html} | 0 .../index.html} | 0 .../index.html} | 0 .../index.html} | 0 .../index.html} | 0 .../index.html} | 0 .../{policies.html => policies/index.html} | 0 .../index.html} | 0 .../index.html} | 0 .../index.html} | 0 .../{ui-theme.html => ui-theme/index.html} | 0 .../out/{teams.html => teams/index.html} | 0 .../{test-key.html => test-key/index.html} | 0 .../index.html} | 0 .../index.html} | 0 .../out/{usage.html => usage/index.html} | 0 .../out/{users.html => users/index.html} | 0 .../index.html} | 0 .../example_config_yaml/custom_auth_basic.py | 3 +- litellm/proxy/proxy_cli.py | 7 +++ pyproject.toml | 4 +- tests/local_testing/test_router_utils.py | 3 +- tests/mcp_tests/test_mcp_server.py | 3 + .../proxy/test_prometheus_cleanup.py | 24 ++++++++ tests/test_litellm/proxy/test_proxy_cli.py | 4 +- tests/test_litellm/test_cost_calculator.py | 59 +++++++++++++++++++ 45 files changed, 112 insertions(+), 9 deletions(-) rename litellm/proxy/_experimental/out/{404.html => 404/index.html} (100%) rename litellm/proxy/_experimental/out/{_not-found.html => _not-found/index.html} (100%) rename litellm/proxy/_experimental/out/{api-reference.html => api-reference/index.html} (100%) rename litellm/proxy/_experimental/out/experimental/{api-playground.html => api-playground/index.html} (100%) rename litellm/proxy/_experimental/out/experimental/{budgets.html => budgets/index.html} (100%) rename litellm/proxy/_experimental/out/experimental/{caching.html => caching/index.html} (100%) rename litellm/proxy/_experimental/out/experimental/{claude-code-plugins.html => claude-code-plugins/index.html} (100%) rename litellm/proxy/_experimental/out/experimental/{old-usage.html => old-usage/index.html} (100%) rename litellm/proxy/_experimental/out/experimental/{prompts.html => prompts/index.html} (100%) rename litellm/proxy/_experimental/out/experimental/{tag-management.html => tag-management/index.html} (100%) rename litellm/proxy/_experimental/out/{guardrails.html => guardrails/index.html} (100%) rename litellm/proxy/_experimental/out/{login.html => login/index.html} (100%) rename litellm/proxy/_experimental/out/{logs.html => logs/index.html} (100%) rename litellm/proxy/_experimental/out/mcp/oauth/{callback.html => callback/index.html} (100%) rename litellm/proxy/_experimental/out/{model-hub.html => model-hub/index.html} (100%) rename litellm/proxy/_experimental/out/{model_hub.html => model_hub/index.html} (100%) rename litellm/proxy/_experimental/out/{model_hub_table.html => model_hub_table/index.html} (100%) rename litellm/proxy/_experimental/out/{models-and-endpoints.html => models-and-endpoints/index.html} (100%) rename litellm/proxy/_experimental/out/{onboarding.html => onboarding/index.html} (100%) rename litellm/proxy/_experimental/out/{organizations.html => organizations/index.html} (100%) rename litellm/proxy/_experimental/out/{playground.html => playground/index.html} (100%) rename litellm/proxy/_experimental/out/{policies.html => policies/index.html} (100%) rename litellm/proxy/_experimental/out/settings/{admin-settings.html => admin-settings/index.html} (100%) rename litellm/proxy/_experimental/out/settings/{logging-and-alerts.html => logging-and-alerts/index.html} (100%) rename litellm/proxy/_experimental/out/settings/{router-settings.html => router-settings/index.html} (100%) rename litellm/proxy/_experimental/out/settings/{ui-theme.html => ui-theme/index.html} (100%) rename litellm/proxy/_experimental/out/{teams.html => teams/index.html} (100%) rename litellm/proxy/_experimental/out/{test-key.html => test-key/index.html} (100%) rename litellm/proxy/_experimental/out/tools/{mcp-servers.html => mcp-servers/index.html} (100%) rename litellm/proxy/_experimental/out/tools/{vector-stores.html => vector-stores/index.html} (100%) rename litellm/proxy/_experimental/out/{usage.html => usage/index.html} (100%) rename litellm/proxy/_experimental/out/{users.html => users/index.html} (100%) rename litellm/proxy/_experimental/out/{virtual-keys.html => virtual-keys/index.html} (100%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 38e6d1fc332..12e3cb1f6b6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3218,6 +3218,7 @@ jobs: -e DD_API_KEY=$DD_API_KEY \ -e DD_SITE=$DD_SITE \ -e LITELLM_LICENSE=$LITELLM_LICENSE \ + -e LITELLM_USE_CHAT_COMPLETIONS_URL_FOR_ANTHROPIC_MESSAGES=true \ --add-host host.docker.internal:host-gateway \ --name my-app \ -v $(pwd)/litellm/proxy/example_config_yaml/pass_through_config.yaml:/app/config.yaml \ diff --git a/CLAUDE.md b/CLAUDE.md index 5395d6d938e..d9061b5e2be 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -156,4 +156,4 @@ LiteLLM is a unified interface for 100+ LLM providers with two main components: **Fix options:** 1. **Create a Prisma migration** (permanent) — run `prisma migrate dev --name ` in the worktree. The generated file will be picked up by `prisma migrate deploy` on next startup. 2. **Apply manually for local dev** — `psql -d litellm -c "ALTER TABLE ... ADD COLUMN IF NOT EXISTS ..."` after each proxy start. Fine for dev, not for production. -3. **Update litellm-proxy-extras** — if the package is installed from PyPI, its migration directory must include the new file. Either update the package or run the migration manually until the next release ships it. \ No newline at end of file +3. **Update litellm-proxy-extras** — if the package is installed from PyPI, its migration directory must include the new file. Either update the package or run the migration manually until the next release ships it. diff --git a/ci_cd/security_scans.sh b/ci_cd/security_scans.sh index 62440d13ebb..e0f370e0035 100755 --- a/ci_cd/security_scans.sh +++ b/ci_cd/security_scans.sh @@ -11,7 +11,7 @@ echo "Starting security scans for LiteLLM..." install_trivy() { echo "Installing Trivy and required tools..." sudo apt-get update - sudo apt-get install -y wget apt-transport-https gnupg lsb-release jq curl + sudo apt-get install -y wget apt-transport-https gnupg lsb-release jq curl bsdmainutils wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add - echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee -a /etc/apt/sources.list.d/trivy.list sudo apt-get update diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index a3ef7b264ec..e0e1e35b94e 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -660,7 +660,14 @@ def _select_model_name_for_cost_calc( if custom_pricing is True: if router_model_id is not None and router_model_id in litellm.model_cost: - return_model = router_model_id + entry = litellm.model_cost[router_model_id] + if ( + entry.get("input_cost_per_token") is not None + or entry.get("input_cost_per_second") is not None + ): + return_model = router_model_id + else: + return_model = model else: return_model = model diff --git a/litellm/proxy/_experimental/out/404.html b/litellm/proxy/_experimental/out/404/index.html similarity index 100% rename from litellm/proxy/_experimental/out/404.html rename to litellm/proxy/_experimental/out/404/index.html diff --git a/litellm/proxy/_experimental/out/_not-found.html b/litellm/proxy/_experimental/out/_not-found/index.html similarity index 100% rename from litellm/proxy/_experimental/out/_not-found.html rename to litellm/proxy/_experimental/out/_not-found/index.html diff --git a/litellm/proxy/_experimental/out/api-reference.html b/litellm/proxy/_experimental/out/api-reference/index.html similarity index 100% rename from litellm/proxy/_experimental/out/api-reference.html rename to litellm/proxy/_experimental/out/api-reference/index.html diff --git a/litellm/proxy/_experimental/out/experimental/api-playground.html b/litellm/proxy/_experimental/out/experimental/api-playground/index.html similarity index 100% rename from litellm/proxy/_experimental/out/experimental/api-playground.html rename to litellm/proxy/_experimental/out/experimental/api-playground/index.html diff --git a/litellm/proxy/_experimental/out/experimental/budgets.html b/litellm/proxy/_experimental/out/experimental/budgets/index.html similarity index 100% rename from litellm/proxy/_experimental/out/experimental/budgets.html rename to litellm/proxy/_experimental/out/experimental/budgets/index.html diff --git a/litellm/proxy/_experimental/out/experimental/caching.html b/litellm/proxy/_experimental/out/experimental/caching/index.html similarity index 100% rename from litellm/proxy/_experimental/out/experimental/caching.html rename to litellm/proxy/_experimental/out/experimental/caching/index.html diff --git a/litellm/proxy/_experimental/out/experimental/claude-code-plugins.html b/litellm/proxy/_experimental/out/experimental/claude-code-plugins/index.html similarity index 100% rename from litellm/proxy/_experimental/out/experimental/claude-code-plugins.html rename to litellm/proxy/_experimental/out/experimental/claude-code-plugins/index.html diff --git a/litellm/proxy/_experimental/out/experimental/old-usage.html b/litellm/proxy/_experimental/out/experimental/old-usage/index.html similarity index 100% rename from litellm/proxy/_experimental/out/experimental/old-usage.html rename to litellm/proxy/_experimental/out/experimental/old-usage/index.html diff --git a/litellm/proxy/_experimental/out/experimental/prompts.html b/litellm/proxy/_experimental/out/experimental/prompts/index.html similarity index 100% rename from litellm/proxy/_experimental/out/experimental/prompts.html rename to litellm/proxy/_experimental/out/experimental/prompts/index.html diff --git a/litellm/proxy/_experimental/out/experimental/tag-management.html b/litellm/proxy/_experimental/out/experimental/tag-management/index.html similarity index 100% rename from litellm/proxy/_experimental/out/experimental/tag-management.html rename to litellm/proxy/_experimental/out/experimental/tag-management/index.html diff --git a/litellm/proxy/_experimental/out/guardrails.html b/litellm/proxy/_experimental/out/guardrails/index.html similarity index 100% rename from litellm/proxy/_experimental/out/guardrails.html rename to litellm/proxy/_experimental/out/guardrails/index.html diff --git a/litellm/proxy/_experimental/out/login.html b/litellm/proxy/_experimental/out/login/index.html similarity index 100% rename from litellm/proxy/_experimental/out/login.html rename to litellm/proxy/_experimental/out/login/index.html diff --git a/litellm/proxy/_experimental/out/logs.html b/litellm/proxy/_experimental/out/logs/index.html similarity index 100% rename from litellm/proxy/_experimental/out/logs.html rename to litellm/proxy/_experimental/out/logs/index.html diff --git a/litellm/proxy/_experimental/out/mcp/oauth/callback.html b/litellm/proxy/_experimental/out/mcp/oauth/callback/index.html similarity index 100% rename from litellm/proxy/_experimental/out/mcp/oauth/callback.html rename to litellm/proxy/_experimental/out/mcp/oauth/callback/index.html diff --git a/litellm/proxy/_experimental/out/model-hub.html b/litellm/proxy/_experimental/out/model-hub/index.html similarity index 100% rename from litellm/proxy/_experimental/out/model-hub.html rename to litellm/proxy/_experimental/out/model-hub/index.html diff --git a/litellm/proxy/_experimental/out/model_hub.html b/litellm/proxy/_experimental/out/model_hub/index.html similarity index 100% rename from litellm/proxy/_experimental/out/model_hub.html rename to litellm/proxy/_experimental/out/model_hub/index.html diff --git a/litellm/proxy/_experimental/out/model_hub_table.html b/litellm/proxy/_experimental/out/model_hub_table/index.html similarity index 100% rename from litellm/proxy/_experimental/out/model_hub_table.html rename to litellm/proxy/_experimental/out/model_hub_table/index.html diff --git a/litellm/proxy/_experimental/out/models-and-endpoints.html b/litellm/proxy/_experimental/out/models-and-endpoints/index.html similarity index 100% rename from litellm/proxy/_experimental/out/models-and-endpoints.html rename to litellm/proxy/_experimental/out/models-and-endpoints/index.html diff --git a/litellm/proxy/_experimental/out/onboarding.html b/litellm/proxy/_experimental/out/onboarding/index.html similarity index 100% rename from litellm/proxy/_experimental/out/onboarding.html rename to litellm/proxy/_experimental/out/onboarding/index.html diff --git a/litellm/proxy/_experimental/out/organizations.html b/litellm/proxy/_experimental/out/organizations/index.html similarity index 100% rename from litellm/proxy/_experimental/out/organizations.html rename to litellm/proxy/_experimental/out/organizations/index.html diff --git a/litellm/proxy/_experimental/out/playground.html b/litellm/proxy/_experimental/out/playground/index.html similarity index 100% rename from litellm/proxy/_experimental/out/playground.html rename to litellm/proxy/_experimental/out/playground/index.html diff --git a/litellm/proxy/_experimental/out/policies.html b/litellm/proxy/_experimental/out/policies/index.html similarity index 100% rename from litellm/proxy/_experimental/out/policies.html rename to litellm/proxy/_experimental/out/policies/index.html diff --git a/litellm/proxy/_experimental/out/settings/admin-settings.html b/litellm/proxy/_experimental/out/settings/admin-settings/index.html similarity index 100% rename from litellm/proxy/_experimental/out/settings/admin-settings.html rename to litellm/proxy/_experimental/out/settings/admin-settings/index.html diff --git a/litellm/proxy/_experimental/out/settings/logging-and-alerts.html b/litellm/proxy/_experimental/out/settings/logging-and-alerts/index.html similarity index 100% rename from litellm/proxy/_experimental/out/settings/logging-and-alerts.html rename to litellm/proxy/_experimental/out/settings/logging-and-alerts/index.html diff --git a/litellm/proxy/_experimental/out/settings/router-settings.html b/litellm/proxy/_experimental/out/settings/router-settings/index.html similarity index 100% rename from litellm/proxy/_experimental/out/settings/router-settings.html rename to litellm/proxy/_experimental/out/settings/router-settings/index.html diff --git a/litellm/proxy/_experimental/out/settings/ui-theme.html b/litellm/proxy/_experimental/out/settings/ui-theme/index.html similarity index 100% rename from litellm/proxy/_experimental/out/settings/ui-theme.html rename to litellm/proxy/_experimental/out/settings/ui-theme/index.html diff --git a/litellm/proxy/_experimental/out/teams.html b/litellm/proxy/_experimental/out/teams/index.html similarity index 100% rename from litellm/proxy/_experimental/out/teams.html rename to litellm/proxy/_experimental/out/teams/index.html diff --git a/litellm/proxy/_experimental/out/test-key.html b/litellm/proxy/_experimental/out/test-key/index.html similarity index 100% rename from litellm/proxy/_experimental/out/test-key.html rename to litellm/proxy/_experimental/out/test-key/index.html diff --git a/litellm/proxy/_experimental/out/tools/mcp-servers.html b/litellm/proxy/_experimental/out/tools/mcp-servers/index.html similarity index 100% rename from litellm/proxy/_experimental/out/tools/mcp-servers.html rename to litellm/proxy/_experimental/out/tools/mcp-servers/index.html diff --git a/litellm/proxy/_experimental/out/tools/vector-stores.html b/litellm/proxy/_experimental/out/tools/vector-stores/index.html similarity index 100% rename from litellm/proxy/_experimental/out/tools/vector-stores.html rename to litellm/proxy/_experimental/out/tools/vector-stores/index.html diff --git a/litellm/proxy/_experimental/out/usage.html b/litellm/proxy/_experimental/out/usage/index.html similarity index 100% rename from litellm/proxy/_experimental/out/usage.html rename to litellm/proxy/_experimental/out/usage/index.html diff --git a/litellm/proxy/_experimental/out/users.html b/litellm/proxy/_experimental/out/users/index.html similarity index 100% rename from litellm/proxy/_experimental/out/users.html rename to litellm/proxy/_experimental/out/users/index.html diff --git a/litellm/proxy/_experimental/out/virtual-keys.html b/litellm/proxy/_experimental/out/virtual-keys/index.html similarity index 100% rename from litellm/proxy/_experimental/out/virtual-keys.html rename to litellm/proxy/_experimental/out/virtual-keys/index.html diff --git a/litellm/proxy/example_config_yaml/custom_auth_basic.py b/litellm/proxy/example_config_yaml/custom_auth_basic.py index 4d633a54fe2..0da6105a305 100644 --- a/litellm/proxy/example_config_yaml/custom_auth_basic.py +++ b/litellm/proxy/example_config_yaml/custom_auth_basic.py @@ -1,6 +1,6 @@ from fastapi import Request -from litellm.proxy._types import UserAPIKeyAuth +from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth async def user_api_key_auth(request: Request, api_key: str) -> UserAPIKeyAuth: @@ -9,6 +9,7 @@ async def user_api_key_auth(request: Request, api_key: str) -> UserAPIKeyAuth: api_key="best-api-key-ever", user_id="best-user-id-ever", team_id="best-team-id-ever", + user_role=LitellmUserRoles.PROXY_ADMIN, ) except Exception: raise Exception diff --git a/litellm/proxy/proxy_cli.py b/litellm/proxy/proxy_cli.py index 701762c834c..e5a34ae8bdd 100644 --- a/litellm/proxy/proxy_cli.py +++ b/litellm/proxy/proxy_cli.py @@ -340,9 +340,16 @@ class ProxyInitializationHelpers: return # Check if prometheus is in any callback list + # Each setting can be a list or a single string; normalize to list callbacks = litellm_settings.get("callbacks") or [] success_callbacks = litellm_settings.get("success_callback") or [] failure_callbacks = litellm_settings.get("failure_callback") or [] + if isinstance(callbacks, str): + callbacks = [callbacks] + if isinstance(success_callbacks, str): + success_callbacks = [success_callbacks] + if isinstance(failure_callbacks, str): + failure_callbacks = [failure_callbacks] all_callbacks = callbacks + success_callbacks + failure_callbacks if "prometheus" not in all_callbacks: return diff --git a/pyproject.toml b/pyproject.toml index 8efc6366128..07004f3ae16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "1.82.2" +version = "1.82.3" description = "Library to easily interface with LLM API providers" authors = ["BerriAI"] license = "MIT" @@ -183,7 +183,7 @@ requires = ["poetry-core", "wheel"] build-backend = "poetry.core.masonry.api" [tool.commitizen] -version = "1.82.2" +version = "1.82.3" version_files = [ "pyproject.toml:^version" ] diff --git a/tests/local_testing/test_router_utils.py b/tests/local_testing/test_router_utils.py index 9d51685751a..4f7f53cef02 100644 --- a/tests/local_testing/test_router_utils.py +++ b/tests/local_testing/test_router_utils.py @@ -199,6 +199,7 @@ def test_router_get_model_info_wildcard_routes(): @pytest.mark.asyncio +@pytest.mark.flaky(retries=3, delay=1) async def test_router_get_model_group_usage_wildcard_routes(): os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" litellm.model_cost = litellm.get_model_cost_map(url="") @@ -219,7 +220,7 @@ async def test_router_get_model_group_usage_wildcard_routes(): ) print(resp) - await asyncio.sleep(1) + await asyncio.sleep(2) tpm, rpm = await router.get_model_group_usage(model_group="gemini/gemini-1.5-flash") diff --git a/tests/mcp_tests/test_mcp_server.py b/tests/mcp_tests/test_mcp_server.py index 2544e06598e..a4a28215e16 100644 --- a/tests/mcp_tests/test_mcp_server.py +++ b/tests/mcp_tests/test_mcp_server.py @@ -395,6 +395,7 @@ async def test_mcp_http_transport_tool_not_found(): @pytest.mark.asyncio async def test_streamable_http_mcp_handler_mock(): """Test the streamable HTTP MCP handler functionality""" + from litellm.proxy._types import UserAPIKeyAuth # Mock the session manager and its methods mock_session_manager = AsyncMock() @@ -425,6 +426,8 @@ async def test_streamable_http_mcp_handler_mock(): ), patch( "litellm.proxy._experimental.mcp_server.server.extract_mcp_auth_context", AsyncMock(return_value=mock_auth_context), + ), patch( + "litellm.proxy._experimental.mcp_server.server.set_auth_context", ): from litellm.proxy._experimental.mcp_server.server import ( handle_streamable_http_mcp, diff --git a/tests/test_litellm/proxy/test_prometheus_cleanup.py b/tests/test_litellm/proxy/test_prometheus_cleanup.py index b3d785f1133..0a67d5e64e0 100644 --- a/tests/test_litellm/proxy/test_prometheus_cleanup.py +++ b/tests/test_litellm/proxy/test_prometheus_cleanup.py @@ -67,6 +67,30 @@ class TestMaybeSetupPrometheusMultiprocDir: assert os.environ["PROMETHEUS_MULTIPROC_DIR"] == custom_dir assert os.path.isdir(custom_dir) + @pytest.mark.parametrize( + "litellm_settings", + [ + {"callbacks": "prometheus"}, + {"success_callback": "prometheus"}, + {"failure_callback": "prometheus"}, + {"callbacks": "custom_callback"}, # string but not prometheus + ], + ) + def test_handles_string_callbacks(self, litellm_settings): + """When callbacks are specified as a string instead of a list, should not crash.""" + with patch.dict(os.environ, {}, clear=False): + os.environ.pop("PROMETHEUS_MULTIPROC_DIR", None) + os.environ.pop("prometheus_multiproc_dir", None) + + # Should not raise TypeError + ProxyInitializationHelpers._maybe_setup_prometheus_multiproc_dir( + num_workers=4, + litellm_settings=litellm_settings, + ) + + # Cleanup + os.environ.pop("PROMETHEUS_MULTIPROC_DIR", None) + @pytest.mark.parametrize( "num_workers, litellm_settings", [ diff --git a/tests/test_litellm/proxy/test_proxy_cli.py b/tests/test_litellm/proxy/test_proxy_cli.py index 642d21a42f7..c5d6c45f9a5 100644 --- a/tests/test_litellm/proxy/test_proxy_cli.py +++ b/tests/test_litellm/proxy/test_proxy_cli.py @@ -677,7 +677,7 @@ class TestHealthAppFactory: mock_atexit_register, mock_subprocess_run, ): - """Test that proxy exits with code 1 when PrismaManager.setup_database returns False""" + """Test that proxy exits with code 1 when PrismaManager.setup_database returns False and --enforce_prisma_migration_check is set""" from litellm.proxy.proxy_cli import run_server mock_subprocess_run.return_value = MagicMock(returncode=0) @@ -717,7 +717,7 @@ class TestHealthAppFactory: with pytest.raises(SystemExit) as exc_info: run_server.main( - ["--local", "--skip_server_startup"], standalone_mode=False + ["--local", "--skip_server_startup", "--enforce_prisma_migration_check"], standalone_mode=False ) assert exc_info.value.code == 1 mock_setup_database.assert_called_once_with(use_migrate=True) diff --git a/tests/test_litellm/test_cost_calculator.py b/tests/test_litellm/test_cost_calculator.py index 463f6952d23..8f5c3ece0ca 100644 --- a/tests/test_litellm/test_cost_calculator.py +++ b/tests/test_litellm/test_cost_calculator.py @@ -388,6 +388,65 @@ def test_custom_pricing_cost_calc_uses_router_model_id_from_litellm_metadata(): assert custom_model_id not in (selected_model_no_custom or "") +def test_per_request_custom_pricing_with_router(): + """When custom pricing is passed as per-request kwargs (not in model_list), + _select_model_name_for_cost_calc should fall back to the model name + (where register_model stored the pricing) instead of the router_model_id + (which has no pricing data). + + Regression test for the bug where response._hidden_params["response_cost"] + returned 0.0 for per-request custom pricing via Router. + """ + from litellm import Router + from litellm.cost_calculator import _select_model_name_for_cost_calc + + router = Router( + model_list=[ + { + "model_name": "openai/gpt-3.5-turbo", + "litellm_params": { + "model": "openai/gpt-3.5-turbo", + "api_key": "test_api_key", + }, + }, + ] + ) + + # Get the deployment's model_id (hash) that the router registered + deployment = router.model_list[0] + router_model_id = deployment["model_info"]["id"] + + # The router registered this hash in model_cost but without custom pricing + assert router_model_id in litellm.model_cost + entry = litellm.model_cost[router_model_id] + # No custom pricing was set in model_list, so these should be None + assert entry.get("input_cost_per_token") is None + + # Now simulate what completion() does: register custom pricing under the model name + litellm.register_model( + { + "openai/gpt-3.5-turbo": { + "input_cost_per_token": 2.0, + "output_cost_per_token": 2.0, + "litellm_provider": "openai", + } + } + ) + + # _select_model_name_for_cost_calc should pick the model name (which has pricing), + # NOT the router_model_id (which has no pricing) + selected = _select_model_name_for_cost_calc( + model="openai/gpt-3.5-turbo", + completion_response=None, + custom_pricing=True, + custom_llm_provider="openai", + router_model_id=router_model_id, + ) + assert selected is not None + assert router_model_id not in selected + assert "gpt-3.5-turbo" in selected + + def test_azure_realtime_cost_calculator(): os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" litellm.model_cost = litellm.get_model_cost_map(url="") From 55c7ba94e617d3dedf75dbbc7bea283054e467ce Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 16 Mar 2026 15:34:25 -0700 Subject: [PATCH 14/60] Update litellm/proxy/management_endpoints/key_management_endpoints.py Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- litellm/proxy/management_endpoints/key_management_endpoints.py | 1 - 1 file changed, 1 deletion(-) diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index 2907f5f089f..6572e3406fe 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -4763,7 +4763,6 @@ async def _check_key_admin_access( Raises HTTPException(403) if the caller is not authorized. """ - from litellm.proxy.proxy_server import proxy_logging_obj if user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value: return From d58b0a9e06be2b5b285a280d1c9f247a8fee472e Mon Sep 17 00:00:00 2001 From: joereyna Date: Mon, 16 Mar 2026 15:36:27 -0700 Subject: [PATCH 15/60] fix: clear oauth2_flow when client_credentials set without token_url --- litellm/proxy/_experimental/mcp_server/rest_endpoints.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py index 5faa0104ac1..ef01f027d6f 100644 --- a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py +++ b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py @@ -910,6 +910,10 @@ if MCP_AVAILABLE: else None ) ) + # client_credentials requires token_url to fetch a token; without it the + # incoming auth header would be dropped with nothing to replace it. + if _oauth2_flow == "client_credentials" and not request.token_url: + _oauth2_flow = None server_model = MCPServer( server_id=request.server_id or "", From 5befc025d8ebb41b5a7914e732ab862e8e05b0e4 Mon Sep 17 00:00:00 2001 From: Ryan Crabbe Date: Mon, 16 Mar 2026 15:36:28 -0700 Subject: [PATCH 16/60] chore(ui): use antd danger prop instead of tailwind for Remove button --- ui/litellm-dashboard/src/components/DefaultUserSettings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/components/DefaultUserSettings.tsx b/ui/litellm-dashboard/src/components/DefaultUserSettings.tsx index 9c38d628b95..314946c520b 100644 --- a/ui/litellm-dashboard/src/components/DefaultUserSettings.tsx +++ b/ui/litellm-dashboard/src/components/DefaultUserSettings.tsx @@ -161,9 +161,9 @@ const DefaultUserSettings: React.FC = ({ Team {index + 1} From 67482db49dd7aecfeec2e797f993718db037f2d8 Mon Sep 17 00:00:00 2001 From: Ryan Crabbe Date: Mon, 16 Mar 2026 15:55:16 -0700 Subject: [PATCH 17/60] feat: fetch blog posts from docs RSS feed instead of static JSON on GitHub --- litellm/__init__.py | 2 +- litellm/litellm_core_utils/get_blog_posts.py | 82 +++++++++++++----- tests/test_litellm/test_get_blog_posts.py | 89 ++++++++++++++------ 3 files changed, 122 insertions(+), 51 deletions(-) diff --git a/litellm/__init__.py b/litellm/__init__.py index 299bb18245c..51c66838613 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -358,7 +358,7 @@ model_cost_map_url: str = os.getenv( ) blog_posts_url: str = os.getenv( "LITELLM_BLOG_POSTS_URL", - "https://raw.githubusercontent.com/BerriAI/litellm/main/litellm/blog_posts.json", + "https://docs.litellm.ai/blog/rss.xml", ) anthropic_beta_headers_url: str = os.getenv( "LITELLM_ANTHROPIC_BETA_HEADERS_URL", diff --git a/litellm/litellm_core_utils/get_blog_posts.py b/litellm/litellm_core_utils/get_blog_posts.py index f54deb59290..99e31cb48b9 100644 --- a/litellm/litellm_core_utils/get_blog_posts.py +++ b/litellm/litellm_core_utils/get_blog_posts.py @@ -1,8 +1,8 @@ """ -Pulls the latest LiteLLM blog posts from GitHub. +Pulls the latest LiteLLM blog posts from the docs RSS feed. Falls back to the bundled local backup on any failure. -GitHub JSON URL is configured via litellm.blog_posts_url (or LITELLM_BLOG_POSTS_URL env var). +RSS URL is configured via litellm.blog_posts_url (or LITELLM_BLOG_POSTS_URL env var). Disable remote fetching entirely: export LITELLM_LOCAL_BLOG_POSTS=True @@ -11,6 +11,8 @@ Disable remote fetching entirely: import json import os import time +import xml.etree.ElementTree as ET +from email.utils import parsedate_to_datetime from importlib.resources import files from typing import Any, Dict, List, Optional @@ -37,9 +39,8 @@ class GetBlogPosts: """ Fetches, validates, and caches LiteLLM blog posts. - Mirrors the structure of GetModelCostMap: - - Fetches from GitHub with a 5-second timeout - - Validates the response has a non-empty ``posts`` list + - Fetches RSS feed from docs site with a 5-second timeout + - Parses the XML and extracts the latest blog post - Caches the result in-process for BLOG_POSTS_TTL_SECONDS (1 hour) - Falls back to the bundled local backup on any failure """ @@ -56,30 +57,67 @@ class GetBlogPosts: return content.get("posts", []) @staticmethod - def fetch_remote_blog_posts(url: str, timeout: int = 5) -> dict: + def fetch_rss_feed(url: str, timeout: int = 5) -> str: """ - Fetch blog posts JSON from a remote URL. + Fetch RSS XML from a remote URL. - Returns the parsed response. Raises on network/parse errors. + Returns the raw XML text. Raises on network errors. """ response = httpx.get(url, timeout=timeout) response.raise_for_status() - return response.json() + return response.text @staticmethod - def validate_blog_posts(data: Any) -> bool: - """Return True if data is a dict with a non-empty ``posts`` list.""" - if not isinstance(data, dict): - verbose_logger.warning( - "LiteLLM: Blog posts response is not a dict (type=%s). " - "Falling back to local backup.", - type(data).__name__, + def parse_rss_to_posts(xml_text: str, max_posts: int = 1) -> List[Dict[str, str]]: + """ + Parse RSS XML and return a list of blog post dicts. + + Extracts title, description, date (YYYY-MM-DD), and url from each . + """ + root = ET.fromstring(xml_text) + channel = root.find("channel") + if channel is None: + raise ValueError("RSS feed missing element") + + posts: List[Dict[str, str]] = [] + for item in channel.findall("item"): + if len(posts) >= max_posts: + break + + title_el = item.find("title") + link_el = item.find("link") + desc_el = item.find("description") + pub_date_el = item.find("pubDate") + + if title_el is None or link_el is None: + continue + + # Parse RFC 2822 date to YYYY-MM-DD + date_str = "" + if pub_date_el is not None and pub_date_el.text: + try: + dt = parsedate_to_datetime(pub_date_el.text) + date_str = dt.strftime("%Y-%m-%d") + except Exception: + date_str = pub_date_el.text + + posts.append( + { + "title": title_el.text or "", + "description": desc_el.text or "" if desc_el is not None else "", + "date": date_str, + "url": link_el.text or "", + } ) - return False - posts = data.get("posts") + + return posts + + @staticmethod + def validate_blog_posts(posts: List[Dict[str, str]]) -> bool: + """Return True if posts is a non-empty list.""" if not isinstance(posts, list) or len(posts) == 0: verbose_logger.warning( - "LiteLLM: Blog posts response has no valid 'posts' list. " + "LiteLLM: Parsed RSS feed has no valid posts. " "Falling back to local backup.", ) return False @@ -102,7 +140,8 @@ class GetBlogPosts: return cached try: - data = cls.fetch_remote_blog_posts(url) + xml_text = cls.fetch_rss_feed(url) + posts = cls.parse_rss_to_posts(xml_text) except Exception as e: verbose_logger.warning( "LiteLLM: Failed to fetch blog posts from %s: %s. " @@ -112,10 +151,9 @@ class GetBlogPosts: ) return cls.load_local_blog_posts() - if not cls.validate_blog_posts(data): + if not cls.validate_blog_posts(posts): return cls.load_local_blog_posts() - posts = data["posts"] cls._cached_posts = posts cls._last_fetch_time = now return posts diff --git a/tests/test_litellm/test_get_blog_posts.py b/tests/test_litellm/test_get_blog_posts.py index a17d78e0bb6..b04fb4ec703 100644 --- a/tests/test_litellm/test_get_blog_posts.py +++ b/tests/test_litellm/test_get_blog_posts.py @@ -1,5 +1,4 @@ """Tests for GetBlogPosts utility class.""" -import json import time from unittest.mock import MagicMock, patch @@ -13,16 +12,26 @@ from litellm.litellm_core_utils.get_blog_posts import ( get_blog_posts, ) -SAMPLE_RESPONSE = { - "posts": [ - { - "title": "Test Post", - "description": "A test post.", - "date": "2026-01-01", - "url": "https://www.litellm.ai/blog/test", - } - ] -} +SAMPLE_RSS = """\ + + + + LiteLLM Blog + + Test Post + https://docs.litellm.ai/blog/test + A test post. + Wed, 01 Jan 2026 10:00:00 GMT + + + Second Post + https://docs.litellm.ai/blog/second + Another post. + Tue, 31 Dec 2025 10:00:00 GMT + + + +""" @pytest.fixture(autouse=True) @@ -45,26 +54,48 @@ def test_load_local_blog_posts_returns_list(): assert "url" in first +def test_parse_rss_to_posts(): + posts = GetBlogPosts.parse_rss_to_posts(SAMPLE_RSS, max_posts=1) + assert len(posts) == 1 + assert posts[0]["title"] == "Test Post" + assert posts[0]["url"] == "https://docs.litellm.ai/blog/test" + assert posts[0]["description"] == "A test post." + assert posts[0]["date"] == "2026-01-01" + + +def test_parse_rss_to_posts_multiple(): + posts = GetBlogPosts.parse_rss_to_posts(SAMPLE_RSS, max_posts=5) + assert len(posts) == 2 + assert posts[1]["title"] == "Second Post" + + +def test_parse_rss_to_posts_invalid_xml(): + with pytest.raises(Exception): + GetBlogPosts.parse_rss_to_posts("not xml") + + +def test_parse_rss_to_posts_missing_channel(): + with pytest.raises(ValueError, match="missing "): + GetBlogPosts.parse_rss_to_posts("") + + def test_validate_blog_posts_valid(): - assert GetBlogPosts.validate_blog_posts(SAMPLE_RESPONSE) is True - - -def test_validate_blog_posts_missing_posts_key(): - assert GetBlogPosts.validate_blog_posts({"other": []}) is False + posts = [{"title": "T", "description": "D", "date": "2026-01-01", "url": "https://x.com"}] + assert GetBlogPosts.validate_blog_posts(posts) is True def test_validate_blog_posts_empty_list(): - assert GetBlogPosts.validate_blog_posts({"posts": []}) is False + assert GetBlogPosts.validate_blog_posts([]) is False -def test_validate_blog_posts_not_dict(): - assert GetBlogPosts.validate_blog_posts("not a dict") is False +def test_validate_blog_posts_not_list(): + assert GetBlogPosts.validate_blog_posts("not a list") is False def test_get_blog_posts_success(): - """Fetches from remote on first call.""" + """Fetches from RSS on first call.""" mock_response = MagicMock() - mock_response.json.return_value = SAMPLE_RESPONSE + mock_response.text = SAMPLE_RSS mock_response.raise_for_status = MagicMock() with patch("litellm.litellm_core_utils.get_blog_posts.httpx.get", return_value=mock_response): @@ -86,10 +117,10 @@ def test_get_blog_posts_network_error_falls_back_to_local(): assert len(posts) > 0 -def test_get_blog_posts_invalid_json_falls_back_to_local(): - """Falls back when remote returns non-dict.""" +def test_get_blog_posts_invalid_xml_falls_back_to_local(): + """Falls back when remote returns invalid XML.""" mock_response = MagicMock() - mock_response.json.return_value = "not a dict" + mock_response.text = "not valid xml" mock_response.raise_for_status = MagicMock() with patch("litellm.litellm_core_utils.get_blog_posts.httpx.get", return_value=mock_response): @@ -101,7 +132,8 @@ def test_get_blog_posts_invalid_json_falls_back_to_local(): def test_get_blog_posts_ttl_cache_not_refetched(): """Within TTL window, does not re-fetch.""" - GetBlogPosts._cached_posts = SAMPLE_RESPONSE["posts"] + cached = [{"title": "Cached", "description": "D", "date": "2026-01-01", "url": "https://x.com"}] + GetBlogPosts._cached_posts = cached GetBlogPosts._last_fetch_time = time.time() # just now call_count = 0 @@ -110,7 +142,7 @@ def test_get_blog_posts_ttl_cache_not_refetched(): nonlocal call_count call_count += 1 m = MagicMock() - m.json.return_value = SAMPLE_RESPONSE + m.text = SAMPLE_RSS m.raise_for_status = MagicMock() return m @@ -123,11 +155,12 @@ def test_get_blog_posts_ttl_cache_not_refetched(): def test_get_blog_posts_ttl_expired_refetches(): """After TTL window, re-fetches from remote.""" - GetBlogPosts._cached_posts = SAMPLE_RESPONSE["posts"] + cached = [{"title": "Cached", "description": "D", "date": "2026-01-01", "url": "https://x.com"}] + GetBlogPosts._cached_posts = cached GetBlogPosts._last_fetch_time = time.time() - 7200 # 2 hours ago mock_response = MagicMock() - mock_response.json.return_value = SAMPLE_RESPONSE + mock_response.text = SAMPLE_RSS mock_response.raise_for_status = MagicMock() with patch( From 12facb27e143d02880592f863bc913ce06b0b35e Mon Sep 17 00:00:00 2001 From: Ryan Crabbe Date: Mon, 16 Mar 2026 16:23:06 -0700 Subject: [PATCH 18/60] fix: remove unused Any import from get_blog_posts --- litellm/litellm_core_utils/get_blog_posts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/litellm_core_utils/get_blog_posts.py b/litellm/litellm_core_utils/get_blog_posts.py index 99e31cb48b9..2f9a14f1279 100644 --- a/litellm/litellm_core_utils/get_blog_posts.py +++ b/litellm/litellm_core_utils/get_blog_posts.py @@ -14,7 +14,7 @@ import time import xml.etree.ElementTree as ET from email.utils import parsedate_to_datetime from importlib.resources import files -from typing import Any, Dict, List, Optional +from typing import Dict, List, Optional import httpx from pydantic import BaseModel From 57bba3b863f9fcd128b26386348f5050abd862d5 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 16 Mar 2026 16:28:11 -0700 Subject: [PATCH 19/60] [Fix] UI - Logs: Fix empty filter results showing stale data Remove `.length > 0` check so that when a backend filter returns an empty result set the table correctly shows no data instead of falling back to the previous unfiltered logs. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/components/view_logs/log_filter_logic.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.tsx b/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.tsx index 400e86d19ee..4e7153b64cd 100644 --- a/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.tsx @@ -228,7 +228,7 @@ export function useLogFilterLogic({ const filteredLogs: PaginatedResponse = useMemo(() => { if (hasBackendFilters) { // Prefer backend result if present; otherwise fall back to latest logs - if (backendFilteredLogs && backendFilteredLogs.data && backendFilteredLogs.data.length > 0) { + if (backendFilteredLogs && backendFilteredLogs.data) { return backendFilteredLogs; } return ( From c951b337e1823010b0a6bbe7848817060f2c91b3 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 16 Mar 2026 16:32:12 -0700 Subject: [PATCH 20/60] [Fix] Reapply empty filter fix after merge with main Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/components/view_logs/log_filter_logic.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.tsx b/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.tsx index 400e86d19ee..4e7153b64cd 100644 --- a/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.tsx @@ -228,7 +228,7 @@ export function useLogFilterLogic({ const filteredLogs: PaginatedResponse = useMemo(() => { if (hasBackendFilters) { // Prefer backend result if present; otherwise fall back to latest logs - if (backendFilteredLogs && backendFilteredLogs.data && backendFilteredLogs.data.length > 0) { + if (backendFilteredLogs && backendFilteredLogs.data) { return backendFilteredLogs; } return ( From bc752fb10965de0210ebf695310035ad903608c2 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 16 Mar 2026 17:27:12 -0700 Subject: [PATCH 21/60] [Fix] Prevent internal users from creating invalid keys via key/generate and key/update Internal users could exploit key/generate and key/update to create unbound keys (no user_id, no budget) or attach keys to non-existent teams. This adds validation for non-admin callers: auto-assign user_id on generate, reject invalid team_ids, and prevent removing user_id on update. Closes LIT-1884 Co-Authored-By: Claude Opus 4.6 --- .../key_management_endpoints.py | 73 ++++- .../test_key_management_endpoints.py | 292 ++++++++++++++++++ 2 files changed, 363 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index 6572e3406fe..726874bc82d 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -354,6 +354,10 @@ def key_generation_check( ## check if key is for team or individual is_team_key = _is_team_key(data=data) + _is_admin = ( + user_api_key_dict.user_role is not None + and user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value + ) if is_team_key: if team_table is None and litellm.key_generation_settings is not None: raise HTTPException( @@ -361,7 +365,13 @@ def key_generation_check( detail=f"Unable to find team object in database. Team ID: {data.team_id}", ) elif team_table is None: - return True # assume user is assigning team_id without using the team table + if _is_admin: + return True # admins can assign team_id without team table + # Non-admin callers must have a valid team (LIT-1884) + raise HTTPException( + status_code=400, + detail=f"Unable to find team object in database. Team ID: {data.team_id}", + ) return _team_key_generation_check( team_table=team_table, user_api_key_dict=user_api_key_dict, @@ -1214,6 +1224,19 @@ async def generate_key_fn( raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=message ) + # For non-admin internal users: auto-assign caller's user_id if not provided + # This prevents creating unbound keys with no user association (LIT-1884) + _is_proxy_admin = ( + user_api_key_dict.user_role is not None + and user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value + ) + if not _is_proxy_admin and data.user_id is None: + data.user_id = user_api_key_dict.user_id + verbose_proxy_logger.warning( + "key/generate: auto-assigning user_id=%s for non-admin caller", + user_api_key_dict.user_id, + ) + team_table: Optional[LiteLLM_TeamTableCachedObj] = None if data.team_id is not None: try: @@ -1228,6 +1251,12 @@ async def generate_key_fn( verbose_proxy_logger.debug( f"Error getting team object in `/key/generate`: {e}" ) + # For non-admin callers, team must exist (LIT-1884) + if not _is_proxy_admin: + raise HTTPException( + status_code=400, + detail=f"Team not found for team_id={data.team_id}. Non-admin users cannot create keys for non-existent teams.", + ) key_generation_check( team_table=team_table, @@ -1810,17 +1839,57 @@ async def _validate_update_key_data( user_api_key_cache: Any, ) -> None: """Validate permissions and constraints for key update.""" + _is_proxy_admin = ( + user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value + ) + + # Prevent non-admin from removing user_id (setting to empty string) (LIT-1884) + if ( + data.user_id is not None + and data.user_id == "" + and not _is_proxy_admin + ): + raise HTTPException( + status_code=403, + detail="Non-admin users cannot remove the user_id from a key.", + ) + # sanity check - prevent non-proxy admin user from updating key to belong to a different user if ( data.user_id is not None and data.user_id != existing_key_row.user_id - and user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN.value + and not _is_proxy_admin ): raise HTTPException( status_code=403, detail=f"User={data.user_id} is not allowed to update key={data.key} to belong to user={existing_key_row.user_id}", ) + # Validate team exists when non-admin changes team_id (LIT-1884) + if ( + data.team_id is not None + and not _is_proxy_admin + ): + try: + _team_obj = await get_team_object( + team_id=data.team_id, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + check_db_only=True, + ) + if _team_obj is None: + raise HTTPException( + status_code=400, + detail=f"Team not found for team_id={data.team_id}. Non-admin users cannot set keys to non-existent teams.", + ) + except HTTPException: + raise + except Exception: + raise HTTPException( + status_code=400, + detail=f"Team not found for team_id={data.team_id}. Non-admin users cannot set keys to non-existent teams.", + ) + common_key_access_checks( user_api_key_dict=user_api_key_dict, data=data, diff --git a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py index 4bb0cd72ed2..e1b4a5288fb 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py @@ -41,12 +41,15 @@ from litellm.proxy.management_endpoints.key_management_endpoints import ( _transform_verification_tokens_to_deleted_records, _validate_max_budget, _validate_reset_spend_value, + _validate_update_key_data, can_modify_verification_token, check_org_key_model_specific_limits, check_team_key_model_specific_limits, delete_verification_tokens, + generate_key_fn, generate_key_helper_fn, key_aliases, + key_generation_check, list_keys, prepare_key_update_data, reset_key_spend_fn, @@ -7535,3 +7538,292 @@ async def test_update_key_non_budget_fields_allowed_for_internal_user(monkeypatc ) assert result is not None + + +# ============================================================================ +# LIT-1884: Internal users cannot create invalid keys +# ============================================================================ + + +class TestLIT1884KeyGenerateValidation: + """Tests for LIT-1884: internal users should not be able to generate invalid keys.""" + + @pytest.mark.asyncio + async def test_internal_user_generate_key_no_user_id_auto_assigns(self): + """ + When an internal_user calls /key/generate without user_id, + the caller's user_id should be auto-assigned before reaching + _common_key_generation_helper. + """ + mock_prisma_client = AsyncMock() + + data = GenerateKeyRequest(key_alias="test-alias") + assert data.user_id is None + + user_api_key_dict = UserAPIKeyAuth( + user_id="internal-user-123", + user_role=LitellmUserRoles.INTERNAL_USER, + ) + + # Patch _common_key_generation_helper to avoid needing full DB mocks. + # We just want to verify user_id is set before we reach this point. + with patch("litellm.proxy.proxy_server.prisma_client", mock_prisma_client), \ + patch("litellm.proxy.proxy_server.user_api_key_cache", MagicMock()), \ + patch("litellm.proxy.proxy_server.user_custom_key_generate", None), \ + patch( + "litellm.proxy.management_endpoints.key_management_endpoints._common_key_generation_helper", + new_callable=AsyncMock, + return_value=MagicMock(), + ): + await generate_key_fn( + data=data, + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + + # The data object should have been mutated to include the caller's user_id + assert data.user_id == "internal-user-123" + + @pytest.mark.asyncio + async def test_internal_user_generate_key_invalid_team_id_rejected(self): + """ + When an internal_user provides a non-existent team_id, + key/generate should raise ProxyException with status 400. + """ + mock_prisma_client = AsyncMock() + + data = GenerateKeyRequest( + key_alias="test-alias", + team_id="nonexistent-team-id", + ) + + user_api_key_dict = UserAPIKeyAuth( + user_id="internal-user-123", + user_role=LitellmUserRoles.INTERNAL_USER, + ) + + with patch("litellm.proxy.proxy_server.prisma_client", mock_prisma_client), \ + patch("litellm.proxy.proxy_server.user_api_key_cache", MagicMock()), \ + patch("litellm.proxy.proxy_server.user_custom_key_generate", None), \ + patch( + "litellm.proxy.management_endpoints.key_management_endpoints.get_team_object", + AsyncMock(side_effect=Exception("Team not found")), + ): + with pytest.raises(ProxyException) as exc_info: + await generate_key_fn( + data=data, + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + assert str(exc_info.value.code) == "400" + assert "Team not found" in str(exc_info.value.message) + + @pytest.mark.asyncio + async def test_admin_generate_key_invalid_team_id_allowed(self): + """ + Admin callers should be allowed to create keys with any team_id, + even if the team doesn't exist (team_table=None is OK for admins). + """ + data = GenerateKeyRequest( + key_alias="admin-key", + team_id="nonexistent-team-id", + user_id="admin-user", + ) + + user_api_key_dict = UserAPIKeyAuth( + user_id="admin-user", + user_role=LitellmUserRoles.PROXY_ADMIN, + ) + + mock_prisma_client = AsyncMock() + + with patch("litellm.proxy.proxy_server.prisma_client", mock_prisma_client), \ + patch("litellm.proxy.proxy_server.user_api_key_cache", MagicMock()), \ + patch("litellm.proxy.proxy_server.user_custom_key_generate", None), \ + patch( + "litellm.proxy.management_endpoints.key_management_endpoints.get_team_object", + AsyncMock(side_effect=Exception("Team not found")), + ), \ + patch( + "litellm.proxy.management_endpoints.key_management_endpoints._common_key_generation_helper", + new_callable=AsyncMock, + return_value=MagicMock(), + ): + # Should NOT raise — admin bypasses team validation + result = await generate_key_fn( + data=data, + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + assert result is not None + + @pytest.mark.asyncio + async def test_admin_generate_key_no_user_id_not_auto_assigned(self): + """ + Admin callers should NOT have user_id auto-assigned — they may + intentionally create keys without a user_id. + """ + data = GenerateKeyRequest(key_alias="admin-unbound-key") + assert data.user_id is None + + user_api_key_dict = UserAPIKeyAuth( + user_id="admin-user", + user_role=LitellmUserRoles.PROXY_ADMIN, + ) + + mock_prisma_client = AsyncMock() + + with patch("litellm.proxy.proxy_server.prisma_client", mock_prisma_client), \ + patch("litellm.proxy.proxy_server.user_api_key_cache", MagicMock()), \ + patch("litellm.proxy.proxy_server.user_custom_key_generate", None), \ + patch( + "litellm.proxy.management_endpoints.key_management_endpoints._common_key_generation_helper", + new_callable=AsyncMock, + return_value=MagicMock(), + ): + await generate_key_fn( + data=data, + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + + # user_id should remain None for admin + assert data.user_id is None + + def test_key_generation_check_non_admin_no_team_table_raises(self): + """ + key_generation_check should raise 400 for non-admin when team_table is None + and key_generation_settings is not set. + """ + data = GenerateKeyRequest(team_id="some-team-id") + user_api_key_dict = UserAPIKeyAuth( + user_id="internal-user", + user_role=LitellmUserRoles.INTERNAL_USER, + ) + + with patch.object(litellm, "key_generation_settings", None): + with pytest.raises(HTTPException) as exc_info: + key_generation_check( + team_table=None, + user_api_key_dict=user_api_key_dict, + data=data, + route="key_generate", + ) + assert exc_info.value.status_code == 400 + assert "Unable to find team object" in str(exc_info.value.detail) + + def test_key_generation_check_admin_no_team_table_allowed(self): + """ + key_generation_check should allow admin to proceed even when team_table is None. + """ + data = GenerateKeyRequest(team_id="some-team-id") + user_api_key_dict = UserAPIKeyAuth( + user_id="admin-user", + user_role=LitellmUserRoles.PROXY_ADMIN, + ) + + with patch.object(litellm, "key_generation_settings", None): + result = key_generation_check( + team_table=None, + user_api_key_dict=user_api_key_dict, + data=data, + route="key_generate", + ) + assert result is True + + +class TestLIT1884KeyUpdateValidation: + """Tests for LIT-1884: internal users should not be able to update keys to remove user_id or set invalid team.""" + + @pytest.mark.asyncio + async def test_internal_user_cannot_remove_user_id(self): + """ + Non-admin users should not be able to set user_id to empty string (remove it). + """ + data = UpdateKeyRequest(key="sk-test-key", user_id="") + existing_key_row = MagicMock() + existing_key_row.user_id = "internal-user-123" + existing_key_row.token = "hashed_token" + existing_key_row.team_id = None + + user_api_key_dict = UserAPIKeyAuth( + user_id="internal-user-123", + user_role=LitellmUserRoles.INTERNAL_USER, + ) + + with pytest.raises(HTTPException) as exc_info: + await _validate_update_key_data( + data=data, + existing_key_row=existing_key_row, + user_api_key_dict=user_api_key_dict, + llm_router=None, + premium_user=False, + prisma_client=AsyncMock(), + user_api_key_cache=MagicMock(), + ) + assert exc_info.value.status_code == 403 + assert "cannot remove the user_id" in str(exc_info.value.detail) + + @pytest.mark.asyncio + async def test_internal_user_cannot_set_invalid_team_id(self): + """ + Non-admin users should not be able to update a key to a non-existent team. + """ + data = UpdateKeyRequest(key="sk-test-key", team_id="nonexistent-team") + existing_key_row = MagicMock() + existing_key_row.user_id = "internal-user-123" + existing_key_row.token = "hashed_token" + existing_key_row.team_id = None + + user_api_key_dict = UserAPIKeyAuth( + user_id="internal-user-123", + user_role=LitellmUserRoles.INTERNAL_USER, + ) + + with patch( + "litellm.proxy.management_endpoints.key_management_endpoints.get_team_object", + AsyncMock(side_effect=Exception("Team not found")), + ): + with pytest.raises(HTTPException) as exc_info: + await _validate_update_key_data( + data=data, + existing_key_row=existing_key_row, + user_api_key_dict=user_api_key_dict, + llm_router=None, + premium_user=False, + prisma_client=AsyncMock(), + user_api_key_cache=MagicMock(), + ) + assert exc_info.value.status_code == 400 + assert "Team not found" in str(exc_info.value.detail) + + @pytest.mark.asyncio + async def test_admin_can_remove_user_id(self): + """ + Admin users should be allowed to set user_id to empty string. + """ + data = UpdateKeyRequest(key="sk-test-key", user_id="") + existing_key_row = MagicMock() + existing_key_row.user_id = "some-user" + existing_key_row.token = "hashed_token" + existing_key_row.team_id = None + existing_key_row.organization_id = None + existing_key_row.project_id = None + + user_api_key_dict = UserAPIKeyAuth( + user_id="admin-user", + user_role=LitellmUserRoles.PROXY_ADMIN, + ) + + mock_prisma_client = AsyncMock() + + # Should NOT raise + await _validate_update_key_data( + data=data, + existing_key_row=existing_key_row, + user_api_key_dict=user_api_key_dict, + llm_router=None, + premium_user=False, + prisma_client=mock_prisma_client, + user_api_key_cache=MagicMock(), + ) From 208740a87c4da78028475b87222f3c9ea1ee05ef Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 16 Mar 2026 17:40:42 -0700 Subject: [PATCH 22/60] [Fix] Remove duplicate get_team_object call in _validate_update_key_data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the non-admin team validation into the existing get_team_object call site to avoid an extra DB round-trip. The existing call already fetches the team for limits checking — we now add the LIT-1884 guard there when team_obj is None for non-admin callers. Co-Authored-By: Claude Opus 4.6 --- .../key_management_endpoints.py | 32 ++++--------------- .../test_key_management_endpoints.py | 12 +++++-- 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index 726874bc82d..67dee47ca8f 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -1865,31 +1865,6 @@ async def _validate_update_key_data( detail=f"User={data.user_id} is not allowed to update key={data.key} to belong to user={existing_key_row.user_id}", ) - # Validate team exists when non-admin changes team_id (LIT-1884) - if ( - data.team_id is not None - and not _is_proxy_admin - ): - try: - _team_obj = await get_team_object( - team_id=data.team_id, - prisma_client=prisma_client, - user_api_key_cache=user_api_key_cache, - check_db_only=True, - ) - if _team_obj is None: - raise HTTPException( - status_code=400, - detail=f"Team not found for team_id={data.team_id}. Non-admin users cannot set keys to non-existent teams.", - ) - except HTTPException: - raise - except Exception: - raise HTTPException( - status_code=400, - detail=f"Team not found for team_id={data.team_id}. Non-admin users cannot set keys to non-existent teams.", - ) - common_key_access_checks( user_api_key_dict=user_api_key_dict, data=data, @@ -1929,6 +1904,13 @@ async def _validate_update_key_data( check_db_only=True, ) + # Validate team exists when non-admin sets a new team_id (LIT-1884) + if team_obj is None and data.team_id is not None and not _is_proxy_admin: + raise HTTPException( + status_code=400, + detail=f"Team not found for team_id={data.team_id}. Non-admin users cannot set keys to non-existent teams.", + ) + if team_obj is not None: await _check_team_key_limits( team_table=team_obj, diff --git a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py index e1b4a5288fb..08e9b5028d0 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py @@ -7768,12 +7768,15 @@ class TestLIT1884KeyUpdateValidation: async def test_internal_user_cannot_set_invalid_team_id(self): """ Non-admin users should not be able to update a key to a non-existent team. + get_team_object raises HTTPException(404) when team doesn't exist in DB. """ data = UpdateKeyRequest(key="sk-test-key", team_id="nonexistent-team") existing_key_row = MagicMock() existing_key_row.user_id = "internal-user-123" existing_key_row.token = "hashed_token" existing_key_row.team_id = None + existing_key_row.organization_id = None + existing_key_row.project_id = None user_api_key_dict = UserAPIKeyAuth( user_id="internal-user-123", @@ -7782,7 +7785,10 @@ class TestLIT1884KeyUpdateValidation: with patch( "litellm.proxy.management_endpoints.key_management_endpoints.get_team_object", - AsyncMock(side_effect=Exception("Team not found")), + AsyncMock(side_effect=HTTPException( + status_code=404, + detail="Team doesn't exist in db. Team=nonexistent-team.", + )), ): with pytest.raises(HTTPException) as exc_info: await _validate_update_key_data( @@ -7794,8 +7800,8 @@ class TestLIT1884KeyUpdateValidation: prisma_client=AsyncMock(), user_api_key_cache=MagicMock(), ) - assert exc_info.value.status_code == 400 - assert "Team not found" in str(exc_info.value.detail) + assert exc_info.value.status_code == 404 + assert "Team doesn't exist" in str(exc_info.value.detail) @pytest.mark.asyncio async def test_admin_can_remove_user_id(self): From 4a92db8da16180da10f12ef72625ff9869c8a9bd Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 16 Mar 2026 18:07:03 -0700 Subject: [PATCH 23/60] [Fix] Skip key_alias re-validation on update/regenerate when alias unchanged When updating or regenerating a key without changing its key_alias, the existing alias was being re-validated against current format rules. This caused keys with legacy aliases (created before stricter validation) to become uneditable. Now validation only runs when the alias actually changes. Co-Authored-By: Claude Opus 4.6 --- .../key_management_endpoints.py | 10 +- .../test_key_management_endpoints.py | 125 ++++++++++++++++++ 2 files changed, 133 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index 67dee47ca8f..87078a5df2f 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -2120,7 +2120,10 @@ async def update_key_fn( data=data, existing_key_row=existing_key_row ) - _validate_key_alias_format(key_alias=non_default_values.get("key_alias", None)) + # Only validate key_alias format if it's actually being changed + new_key_alias = non_default_values.get("key_alias", None) + if new_key_alias != existing_key_row.key_alias: + _validate_key_alias_format(key_alias=new_key_alias) await _enforce_unique_key_alias( key_alias=non_default_values.get("key_alias", None), @@ -3579,7 +3582,10 @@ async def _execute_virtual_key_regeneration( non_default_values = await prepare_key_update_data( data=data, existing_key_row=key_in_db ) - _validate_key_alias_format(key_alias=non_default_values.get("key_alias")) + # Only validate key_alias format if it's actually being changed + new_key_alias = non_default_values.get("key_alias") + if new_key_alias != key_in_db.key_alias: + _validate_key_alias_format(key_alias=new_key_alias) verbose_proxy_logger.debug("non_default_values: %s", non_default_values) update_data.update(non_default_values) update_data = prisma_client.jsonify_object(data=update_data) diff --git a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py index 08e9b5028d0..ed2607da24a 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py @@ -7833,3 +7833,128 @@ class TestLIT1884KeyUpdateValidation: prisma_client=mock_prisma_client, user_api_key_cache=MagicMock(), ) + + +class TestKeyAliasSkipValidationOnUnchanged: + """ + Test that updating/regenerating a key without changing its key_alias + does NOT re-validate the alias. This prevents legacy aliases (created + before stricter validation rules) from blocking edits to other fields. + """ + + @pytest.fixture(autouse=True) + def enable_validation(self): + litellm.enable_key_alias_format_validation = True + yield + litellm.enable_key_alias_format_validation = False + + @pytest.fixture + def mock_prisma(self): + prisma = MagicMock() + prisma.db = MagicMock() + prisma.db.litellm_verificationtoken = MagicMock() + prisma.get_data = AsyncMock(return_value=None) # no duplicate alias + prisma.update_data = AsyncMock(return_value=None) + prisma.jsonify_object = MagicMock(side_effect=lambda data: data) + return prisma + + @pytest.fixture + def existing_key_with_legacy_alias(self): + """A key whose alias contains '@' — valid now, but simulates a legacy alias.""" + return LiteLLM_VerificationToken( + token="hashed_token_123", + key_alias="user@domain.com", + team_id="team-1", + models=[], + max_budget=100.0, + ) + + @pytest.mark.asyncio + async def test_update_key_unchanged_legacy_alias_passes( + self, mock_prisma, existing_key_with_legacy_alias + ): + """ + Updating a key without changing its key_alias should skip format + validation — even if the alias wouldn't pass current rules. + """ + from litellm.proxy.management_endpoints.key_management_endpoints import ( + _validate_key_alias_format, + ) + + # Temporarily make the regex reject '@' to simulate stricter rules + import re + from litellm.proxy.management_endpoints import key_management_endpoints as mod + + original_pattern = mod._KEY_ALIAS_PATTERN + mod._KEY_ALIAS_PATTERN = re.compile( + r"^[a-zA-Z0-9][a-zA-Z0-9_\-/\.]{0,253}[a-zA-Z0-9]$" + ) + try: + # Confirm the alias WOULD fail validation directly + with pytest.raises(ProxyException): + _validate_key_alias_format("user@domain.com") + + # But prepare_key_update_data + the skip logic should allow it + # Simulate what update_key_fn does: alias is in non_default_values + # but matches existing_key_row.key_alias => skip validation + existing_alias = existing_key_with_legacy_alias.key_alias + new_alias = "user@domain.com" # same as existing + assert new_alias == existing_alias # unchanged + + # This is the core logic from update_key_fn: + if new_alias != existing_alias: + _validate_key_alias_format(new_alias) + # No exception raised — test passes + finally: + mod._KEY_ALIAS_PATTERN = original_pattern + + @pytest.mark.asyncio + async def test_update_key_changed_alias_still_validated( + self, mock_prisma, existing_key_with_legacy_alias + ): + """ + When the alias IS being changed, validation should still run. + """ + from litellm.proxy.management_endpoints.key_management_endpoints import ( + _validate_key_alias_format, + ) + + existing_alias = existing_key_with_legacy_alias.key_alias + new_alias = "!invalid!" + + assert new_alias != existing_alias + with pytest.raises(ProxyException): + if new_alias != existing_alias: + _validate_key_alias_format(new_alias) + + @pytest.mark.asyncio + async def test_update_key_changed_to_valid_alias_passes( + self, mock_prisma, existing_key_with_legacy_alias + ): + """ + Changing the alias to a new valid value should pass validation. + """ + from litellm.proxy.management_endpoints.key_management_endpoints import ( + _validate_key_alias_format, + ) + + existing_alias = existing_key_with_legacy_alias.key_alias + new_alias = "new-valid-alias" + + assert new_alias != existing_alias + # Should not raise + if new_alias != existing_alias: + _validate_key_alias_format(new_alias) + + @pytest.mark.asyncio + async def test_update_key_alias_none_skips_validation(self): + """ + When key_alias is not in the update payload (None), validation + should be skipped regardless. + """ + from litellm.proxy.management_endpoints.key_management_endpoints import ( + _validate_key_alias_format, + ) + + # None alias should always pass + _validate_key_alias_format(None) From a771fe55e478bdc06e4b8b47177f7f12cfc7f44b Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 16 Mar 2026 18:19:02 -0700 Subject: [PATCH 24/60] [Fix] Update log filter test to match empty-result behavior The test expected fallback to all logs when backend filters return empty, but the source was intentionally changed to show empty results instead of stale data. Updated test to match. Co-Authored-By: Claude Opus 4.6 --- .../src/components/view_logs/log_filter_logic.test.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.test.tsx b/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.test.tsx index 0bb0f2a44d4..8ccf0a9e1b5 100644 --- a/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.test.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.test.tsx @@ -451,7 +451,7 @@ describe("useLogFilterLogic", () => { ); }); - it("should fall back to logs when backend filters are active but API returns empty", async () => { + it("should return empty results when backend filters are active but API returns empty", async () => { vi.mocked(uiSpendLogsCall).mockResolvedValue({ data: [], total: 0, @@ -474,8 +474,7 @@ describe("useLogFilterLogic", () => { { timeout: 500 }, ); - expect(result.current.filteredLogs.data).toHaveLength(1); - expect(result.current.filteredLogs.data[0].request_id).toBe("client-req"); + expect(result.current.filteredLogs.data).toHaveLength(0); }); it("should refetch when sortBy changes and backend filters are active", async () => { From 7b66c970e9706a5b96c1987bc445ff941e51fb66 Mon Sep 17 00:00:00 2001 From: voidborne-d Date: Tue, 17 Mar 2026 03:11:58 +0000 Subject: [PATCH 25/60] fix: auto-recover shared aiohttp session when closed (#23806) When the shared aiohttp session closes (due to network interruption, idle timeout, or Redis failover side effects), the proxy permanently falls back to creating a new HTTPS connection per request, losing the benefit of connection pooling for the entire pod lifetime. Fix: make add_shared_session_to_data() async and recreate the session when it is found closed, restoring connection pooling automatically. Fixes #23806 --- litellm/proxy/route_llm_request.py | 29 ++++-- .../proxy/test_aiohttp_session_recovery.py | 99 +++++++++++++++++++ 2 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 tests/test_litellm/proxy/test_aiohttp_session_recovery.py diff --git a/litellm/proxy/route_llm_request.py b/litellm/proxy/route_llm_request.py index e5fc9fe76a4..265a311d393 100644 --- a/litellm/proxy/route_llm_request.py +++ b/litellm/proxy/route_llm_request.py @@ -123,23 +123,40 @@ def get_team_id_from_data(data: dict) -> Optional[str]: return None -def add_shared_session_to_data(data: dict) -> None: +async def add_shared_session_to_data(data: dict) -> None: """ Add shared aiohttp session for connection reuse (prevents cold starts). + If the session was closed (e.g. due to network interruption or idle timeout), + automatically recreates it so connection pooling is restored. Silently continues without session reuse if import fails or session is unavailable. Args: data: Dictionary to add the shared session to """ try: + import litellm.proxy.proxy_server as proxy_server from litellm._logging import verbose_proxy_logger - from litellm.proxy.proxy_server import shared_aiohttp_session - if shared_aiohttp_session is not None and not shared_aiohttp_session.closed: - data["shared_session"] = shared_aiohttp_session + session = proxy_server.shared_aiohttp_session + + if session is not None and not session.closed: + data["shared_session"] = session verbose_proxy_logger.info( - f"SESSION REUSE: Attached shared aiohttp session to request (ID: {id(shared_aiohttp_session)})" + f"SESSION REUSE: Attached shared aiohttp session to request (ID: {id(session)})" ) + elif session is not None and session.closed: + # Session was created at startup but has since closed — recreate it + verbose_proxy_logger.warning( + f"SESSION REUSE: Shared aiohttp session is closed (ID: {id(session)}), recreating..." + ) + new_session = await proxy_server._initialize_shared_aiohttp_session() + if new_session is not None: + proxy_server.shared_aiohttp_session = new_session + data["shared_session"] = new_session + else: + verbose_proxy_logger.info( + "SESSION REUSE: Failed to recreate shared session, continuing without session reuse" + ) else: verbose_proxy_logger.info( "SESSION REUSE: No shared session available for this request" @@ -248,7 +265,7 @@ async def route_request( # noqa: PLR0915 - Complex routing function, refactorin """ Common helper to route the request """ - add_shared_session_to_data(data) + await add_shared_session_to_data(data) team_id = get_team_id_from_data(data) router_model_names = llm_router.model_names if llm_router is not None else [] diff --git a/tests/test_litellm/proxy/test_aiohttp_session_recovery.py b/tests/test_litellm/proxy/test_aiohttp_session_recovery.py new file mode 100644 index 00000000000..e87ca5a1632 --- /dev/null +++ b/tests/test_litellm/proxy/test_aiohttp_session_recovery.py @@ -0,0 +1,99 @@ +""" +Tests for shared aiohttp session auto-recovery. + +When the shared session closes (e.g. network interruption, idle timeout), +add_shared_session_to_data should recreate it instead of permanently +falling back to per-request connections. + +Fixes: https://github.com/BerriAI/litellm/issues/23806 +""" + +import asyncio +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + + +@pytest.mark.asyncio +async def test_add_shared_session_attaches_open_session(): + """When the shared session is open, it should be attached to data.""" + from litellm.proxy.route_llm_request import add_shared_session_to_data + + mock_session = MagicMock() + mock_session.closed = False + + with patch( + "litellm.proxy.proxy_server.shared_aiohttp_session", mock_session + ): + data = {} + await add_shared_session_to_data(data) + assert data["shared_session"] is mock_session + + +@pytest.mark.asyncio +async def test_add_shared_session_recreates_closed_session(): + """When the shared session is closed, it should be recreated.""" + from litellm.proxy import proxy_server as proxy_server_module + from litellm.proxy.route_llm_request import add_shared_session_to_data + + closed_session = MagicMock() + closed_session.closed = True + + new_session = MagicMock() + new_session.closed = False + + with patch.object( + proxy_server_module, + "shared_aiohttp_session", + closed_session, + ): + with patch.object( + proxy_server_module, + "_initialize_shared_aiohttp_session", + new_callable=AsyncMock, + return_value=new_session, + ) as mock_init: + data = {} + await add_shared_session_to_data(data) + + mock_init.assert_called_once() + assert data["shared_session"] is new_session + assert proxy_server_module.shared_aiohttp_session is new_session + + +@pytest.mark.asyncio +async def test_add_shared_session_handles_recreation_failure(): + """When recreation fails, data should not contain shared_session.""" + from litellm.proxy import proxy_server as proxy_server_module + from litellm.proxy.route_llm_request import add_shared_session_to_data + + closed_session = MagicMock() + closed_session.closed = True + + with patch.object( + proxy_server_module, + "shared_aiohttp_session", + closed_session, + ): + with patch.object( + proxy_server_module, + "_initialize_shared_aiohttp_session", + new_callable=AsyncMock, + return_value=None, + ): + data = {} + await add_shared_session_to_data(data) + assert "shared_session" not in data + + +@pytest.mark.asyncio +async def test_add_shared_session_no_session_available(): + """When no session was ever created, data should not contain shared_session.""" + from litellm.proxy.route_llm_request import add_shared_session_to_data + + with patch( + "litellm.proxy.proxy_server.shared_aiohttp_session", None + ): + data = {} + await add_shared_session_to_data(data) + assert "shared_session" not in data From 53d96c8353b2fd18669a54ec0ff2410ff3518ad5 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 16 Mar 2026 21:35:21 -0700 Subject: [PATCH 26/60] [Feature] Disable custom API key values via UI setting Add disable_custom_api_keys UI setting that prevents users from specifying custom key values during key generation and regeneration. When enabled, all keys must be auto-generated, eliminating the risk of key hash collisions in multi-tenant environments. Co-Authored-By: Claude Opus 4.6 --- .../key_management_endpoints.py | 30 +++- .../proxy_setting_endpoints.py | 1 + .../test_key_management_endpoints.py | 149 +++++++++++++++++- .../organisms/create_key_button.tsx | 2 + 4 files changed, 176 insertions(+), 6 deletions(-) diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index 87078a5df2f..e09d7607ebe 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -72,6 +72,9 @@ from litellm.proxy.management_helpers.team_member_permission_checks import ( ) from litellm.proxy.management_helpers.utils import management_endpoint_wrapper from litellm.proxy.spend_tracking.spend_tracking_utils import _is_master_key +from litellm.proxy.ui_crud_endpoints.proxy_setting_endpoints import ( + get_ui_settings_cached, +) from litellm.proxy.utils import ( PrismaClient, ProxyLogging, @@ -96,6 +99,24 @@ from litellm.types.utils import ( ) +async def _check_custom_key_allowed(custom_key_value: Optional[str]) -> None: + """Raise 403 if custom API keys are disabled and a custom key was provided.""" + if custom_key_value is None: + return + + ui_settings = await get_ui_settings_cached() + if ui_settings.get("disable_custom_api_keys", False) is True: + verbose_proxy_logger.warning( + "Custom API key rejected: disable_custom_api_keys is enabled" + ) + raise HTTPException( + status_code=403, + detail={ + "error": "Custom API key values are disabled by your administrator. Keys must be auto-generated." + }, + ) + + def _is_team_key(data: Union[GenerateKeyRequest, LiteLLM_VerificationToken]): return data.team_id is not None @@ -671,6 +692,9 @@ async def _common_key_generation_helper( # noqa: PLR0915 prisma_client=prisma_client, ) + # Reject custom key values if disabled by admin + await _check_custom_key_allowed(data.key) + # Validate user-provided key format if data.key is not None and not data.key.startswith("sk-"): _masked = ( @@ -3479,8 +3503,10 @@ async def _rotate_master_key( # noqa: PLR0915 ) -def get_new_token(data: Optional[RegenerateKeyRequest]) -> str: +async def get_new_token(data: Optional[RegenerateKeyRequest]) -> str: if data and data.new_key is not None: + # Reject custom key values if disabled by admin + await _check_custom_key_allowed(data.new_key) new_token = data.new_key if not data.new_key.startswith("sk-"): raise HTTPException( @@ -3572,7 +3598,7 @@ async def _execute_virtual_key_regeneration( """Generate new token, update DB, invalidate cache, and return response.""" from litellm.proxy.proxy_server import hash_token - new_token = get_new_token(data=data) + new_token = await get_new_token(data=data) new_token_hash = hash_token(new_token) new_token_key_name = f"sk-...{new_token[-4:]}" update_data = {"token": new_token_hash, "key_name": new_token_key_name} diff --git a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py index 0fa27905bab..d31079f14fb 100644 --- a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py +++ b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py @@ -149,6 +149,7 @@ ALLOWED_UI_SETTINGS_FIELDS = { "disable_vector_stores_for_internal_users", "allow_vector_stores_for_team_admins", "scope_user_search_to_org", + "disable_custom_api_keys", } # Flags that must be synced from the persisted UISettings into diff --git a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py index ed2607da24a..a3bca77ae3a 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py @@ -960,22 +960,34 @@ async def test_key_info_returns_object_permission(monkeypatch): ) -def test_get_new_token_with_valid_key(): +@pytest.mark.asyncio +async def test_get_new_token_with_valid_key(monkeypatch): """Test get_new_token function when provided with a valid key that starts with 'sk-'""" + from unittest.mock import AsyncMock + from litellm.proxy._types import RegenerateKeyRequest from litellm.proxy.management_endpoints.key_management_endpoints import ( get_new_token, ) + # Mock get_ui_settings_cached to return setting disabled (custom keys allowed) + monkeypatch.setattr( + "litellm.proxy.management_endpoints.key_management_endpoints.get_ui_settings_cached", + AsyncMock(return_value={}), + ) + # Test with valid new_key data = RegenerateKeyRequest(new_key="sk-test123456789") - result = get_new_token(data) + result = await get_new_token(data) assert result == "sk-test123456789" -def test_get_new_token_with_invalid_key(): +@pytest.mark.asyncio +async def test_get_new_token_with_invalid_key(monkeypatch): """Test get_new_token function when provided with an invalid key that doesn't start with 'sk-'""" + from unittest.mock import AsyncMock + from fastapi import HTTPException from litellm.proxy._types import RegenerateKeyRequest @@ -983,16 +995,145 @@ def test_get_new_token_with_invalid_key(): get_new_token, ) + # Mock get_ui_settings_cached to return setting disabled (custom keys allowed) + monkeypatch.setattr( + "litellm.proxy.management_endpoints.key_management_endpoints.get_ui_settings_cached", + AsyncMock(return_value={}), + ) + # Test with invalid new_key (doesn't start with 'sk-') data = RegenerateKeyRequest(new_key="invalid-key-123") with pytest.raises(HTTPException) as exc_info: - get_new_token(data) + await get_new_token(data) assert exc_info.value.status_code == 400 assert "New key must start with 'sk-'" in str(exc_info.value.detail) +@pytest.mark.asyncio +async def test_check_custom_key_allowed_when_disabled(monkeypatch): + """_check_custom_key_allowed raises 403 when disable_custom_api_keys is true.""" + from unittest.mock import AsyncMock + + from fastapi import HTTPException + + from litellm.proxy.management_endpoints.key_management_endpoints import ( + _check_custom_key_allowed, + ) + + monkeypatch.setattr( + "litellm.proxy.management_endpoints.key_management_endpoints.get_ui_settings_cached", + AsyncMock(return_value={"disable_custom_api_keys": True}), + ) + + with pytest.raises(HTTPException) as exc_info: + await _check_custom_key_allowed("sk-custom-key-123") + + assert exc_info.value.status_code == 403 + assert "disabled" in str(exc_info.value.detail).lower() + + +@pytest.mark.asyncio +async def test_check_custom_key_allowed_when_enabled(monkeypatch): + """_check_custom_key_allowed does nothing when disable_custom_api_keys is false.""" + from unittest.mock import AsyncMock + + from litellm.proxy.management_endpoints.key_management_endpoints import ( + _check_custom_key_allowed, + ) + + monkeypatch.setattr( + "litellm.proxy.management_endpoints.key_management_endpoints.get_ui_settings_cached", + AsyncMock(return_value={"disable_custom_api_keys": False}), + ) + + # Should not raise + await _check_custom_key_allowed("sk-custom-key-123") + + +@pytest.mark.asyncio +async def test_check_custom_key_allowed_when_unset(monkeypatch): + """_check_custom_key_allowed does nothing when setting is not present.""" + from unittest.mock import AsyncMock + + from litellm.proxy.management_endpoints.key_management_endpoints import ( + _check_custom_key_allowed, + ) + + monkeypatch.setattr( + "litellm.proxy.management_endpoints.key_management_endpoints.get_ui_settings_cached", + AsyncMock(return_value={}), + ) + + # Should not raise + await _check_custom_key_allowed("sk-custom-key-123") + + +@pytest.mark.asyncio +async def test_check_custom_key_allowed_none_key_always_passes(monkeypatch): + """_check_custom_key_allowed does nothing when key is None, even if setting is on.""" + from unittest.mock import AsyncMock + + from litellm.proxy.management_endpoints.key_management_endpoints import ( + _check_custom_key_allowed, + ) + + monkeypatch.setattr( + "litellm.proxy.management_endpoints.key_management_endpoints.get_ui_settings_cached", + AsyncMock(return_value={"disable_custom_api_keys": True}), + ) + + # Should not raise — None means auto-generate + await _check_custom_key_allowed(None) + + +@pytest.mark.asyncio +async def test_get_new_token_rejected_when_custom_keys_disabled(monkeypatch): + """get_new_token raises 403 when new_key is set and disable_custom_api_keys is true.""" + from unittest.mock import AsyncMock + + from fastapi import HTTPException + + from litellm.proxy._types import RegenerateKeyRequest + from litellm.proxy.management_endpoints.key_management_endpoints import ( + get_new_token, + ) + + monkeypatch.setattr( + "litellm.proxy.management_endpoints.key_management_endpoints.get_ui_settings_cached", + AsyncMock(return_value={"disable_custom_api_keys": True}), + ) + + data = RegenerateKeyRequest(new_key="sk-custom-regen-key") + + with pytest.raises(HTTPException) as exc_info: + await get_new_token(data) + + assert exc_info.value.status_code == 403 + + +@pytest.mark.asyncio +async def test_get_new_token_auto_generates_when_custom_keys_disabled(monkeypatch): + """get_new_token auto-generates a key when new_key is None, even if setting is on.""" + from unittest.mock import AsyncMock + + from litellm.proxy._types import RegenerateKeyRequest + from litellm.proxy.management_endpoints.key_management_endpoints import ( + get_new_token, + ) + + monkeypatch.setattr( + "litellm.proxy.management_endpoints.key_management_endpoints.get_ui_settings_cached", + AsyncMock(return_value={"disable_custom_api_keys": True}), + ) + + data = RegenerateKeyRequest() # no new_key + result = await get_new_token(data) + + assert result.startswith("sk-") + + @pytest.mark.asyncio async def test_generate_service_account_requires_team_id(): with pytest.raises(HTTPException): diff --git a/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx b/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx index 71e882db3fb..d0a7a909d6e 100644 --- a/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx +++ b/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx @@ -166,6 +166,7 @@ const CreateKey: React.FC = ({ team, teams, data, addKey, autoOp const { data: projects, isLoading: isProjectsLoading } = useProjects(); const { data: uiSettingsData } = useUISettings(); const enableProjectsUI = Boolean(uiSettingsData?.values?.enable_projects_ui); + const disableCustomApiKeys = Boolean(uiSettingsData?.values?.disable_custom_api_keys); const queryClient = useQueryClient(); const [form] = Form.useForm(); const [isModalVisible, setIsModalVisible] = useState(false); @@ -1581,6 +1582,7 @@ const CreateKey: React.FC = ({ team, teams, data, addKey, autoOp "budget_duration", "tpm_limit", "rpm_limit", + ...(disableCustomApiKeys ? ["key"] : []), ]} /> From 72aa5fc21926865d3aa6b20afad11c51273539ac Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 16 Mar 2026 22:10:02 -0700 Subject: [PATCH 27/60] [Fix] Add disable_custom_api_keys to UISettings Pydantic model Without this field on the model, GET /get/ui_settings omits the setting from the response and field_schema, preventing the UI from reading it. Co-Authored-By: Claude Opus 4.6 --- litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py index d31079f14fb..9faadd334a9 100644 --- a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py +++ b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py @@ -129,6 +129,11 @@ class UISettings(BaseModel): description="If enabled, the user search endpoint (/user/filter/ui) restricts results by organization. When off, any authenticated user can search all users.", ) + disable_custom_api_keys: bool = Field( + default=False, + description="If true, users cannot specify custom API key values. All keys must be auto-generated.", + ) + class UISettingsResponse(SettingsResponse): """Response model for UI settings""" From d15c2d546ecf3448b2447b24aea0d7643b6efe3f Mon Sep 17 00:00:00 2001 From: Harshit Jain <48647625+Harshit28j@users.noreply.github.com> Date: Tue, 17 Mar 2026 10:54:29 +0530 Subject: [PATCH 28/60] fix: Register DynamoAI guardrail initializer and enum entry (#23752) * fix: Register DynamoAI guardrail initializer and enum entry Fix the "Unsupported guardrail: dynamoai" error by: 1. Adding DYNAMOAI to SupportedGuardrailIntegrations enum 2. Implementing initialize_guardrail() and registries in dynamoai/__init__.py The DynamoAI guardrail was added in PR #15920 but never properly registered in the initialization system. The __init__.py was missing the guardrail_initializer_registry and guardrail_class_registry dictionaries that the dynamic discovery mechanism looks for at module load time. Fixes #22773 Co-Authored-By: Claude Haiku 4.5 * Update litellm/proxy/guardrails/guardrail_hooks/dynamoai/__init__.py Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Update litellm/proxy/guardrails/guardrail_hooks/dynamoai/__init__.py Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * test: Add tests for DynamoAI guardrail registration Verifies enum entry, initializer registry, class registry, instance creation, and global registry discovery. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Haiku 4.5 Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- .../guardrail_hooks/dynamoai/__init__.py | 32 +++++++- litellm/types/guardrails.py | 1 + .../guardrail_hooks/test_dynamoai.py | 81 +++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 tests/test_litellm/proxy/guardrails/guardrail_hooks/test_dynamoai.py diff --git a/litellm/proxy/guardrails/guardrail_hooks/dynamoai/__init__.py b/litellm/proxy/guardrails/guardrail_hooks/dynamoai/__init__.py index 79f1992da44..f9ebf46a270 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/dynamoai/__init__.py +++ b/litellm/proxy/guardrails/guardrail_hooks/dynamoai/__init__.py @@ -1,3 +1,33 @@ +from typing import TYPE_CHECKING + +from litellm.types.guardrails import SupportedGuardrailIntegrations + from .dynamoai import DynamoAIGuardrails -__all__ = ["DynamoAIGuardrails"] +if TYPE_CHECKING: + from litellm.types.guardrails import Guardrail, LitellmParams + + +def initialize_guardrail(litellm_params: "LitellmParams", guardrail: "Guardrail"): + import litellm + + _dynamoai_callback = DynamoAIGuardrails( + api_base=litellm_params.api_base, + api_key=litellm_params.api_key, + guardrail_name=guardrail.get("guardrail_name", ""), + event_hook=litellm_params.mode, + default_on=litellm_params.default_on, + ) + litellm.logging_callback_manager.add_litellm_callback(_dynamoai_callback) + + return _dynamoai_callback + + +guardrail_initializer_registry = { + SupportedGuardrailIntegrations.DYNAMOAI.value: initialize_guardrail, +} + + +guardrail_class_registry = { + SupportedGuardrailIntegrations.DYNAMOAI.value: DynamoAIGuardrails, +} diff --git a/litellm/types/guardrails.py b/litellm/types/guardrails.py index d5abf5c8fbf..27fa27e6da3 100644 --- a/litellm/types/guardrails.py +++ b/litellm/types/guardrails.py @@ -44,6 +44,7 @@ guardrails: class SupportedGuardrailIntegrations(Enum): APORIA = "aporia" BEDROCK = "bedrock" + DYNAMOAI = "dynamoai" GUARDRAILS_AI = "guardrails_ai" LAKERA = "lakera" LAKERA_V2 = "lakera_v2" diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_dynamoai.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_dynamoai.py new file mode 100644 index 00000000000..7bc4e951a5f --- /dev/null +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_dynamoai.py @@ -0,0 +1,81 @@ +""" +Tests for DynamoAI guardrail registration and initialization. +""" + +import os +from unittest.mock import patch + +import pytest + + +class TestDynamoAIGuardrailRegistration: + """Tests for DynamoAI guardrail registration in the guardrail system.""" + + def test_supported_guardrail_enum_entry(self): + """Test that DYNAMOAI is in SupportedGuardrailIntegrations enum.""" + from litellm.types.guardrails import SupportedGuardrailIntegrations + + assert hasattr(SupportedGuardrailIntegrations, "DYNAMOAI") + assert SupportedGuardrailIntegrations.DYNAMOAI.value == "dynamoai" + + def test_initialize_guardrail_function_exists(self): + """Test that initialize_guardrail function is properly exported.""" + from litellm.proxy.guardrails.guardrail_hooks.dynamoai import ( + guardrail_initializer_registry, + initialize_guardrail, + ) + + assert initialize_guardrail is not None + assert "dynamoai" in guardrail_initializer_registry + + def test_guardrail_class_registry_exists(self): + """Test that guardrail_class_registry is properly exported.""" + from litellm.proxy.guardrails.guardrail_hooks.dynamoai import ( + guardrail_class_registry, + ) + from litellm.proxy.guardrails.guardrail_hooks.dynamoai.dynamoai import ( + DynamoAIGuardrails, + ) + + assert "dynamoai" in guardrail_class_registry + assert guardrail_class_registry["dynamoai"] == DynamoAIGuardrails + + def test_initialize_guardrail_creates_instance(self): + """Test that initialize_guardrail creates a DynamoAIGuardrails instance.""" + from litellm.proxy.guardrails.guardrail_hooks.dynamoai import ( + initialize_guardrail, + ) + from litellm.proxy.guardrails.guardrail_hooks.dynamoai.dynamoai import ( + DynamoAIGuardrails, + ) + from litellm.types.guardrails import LitellmParams + + litellm_params = LitellmParams( + guardrail="dynamoai", + mode="pre_call", + api_key="test-key", + api_base="https://test.dynamo.ai", + ) + + guardrail = { + "guardrail_name": "test-dynamoai-guard", + } + + with patch( + "litellm.logging_callback_manager.add_litellm_callback" + ) as mock_add: + result = initialize_guardrail(litellm_params, guardrail) + + assert isinstance(result, DynamoAIGuardrails) + assert result.api_key == "test-key" + assert result.api_base == "https://test.dynamo.ai" + assert result.guardrail_name == "test-dynamoai-guard" + mock_add.assert_called_once_with(result) + + def test_dynamoai_in_global_registry(self): + """Test that dynamoai is discoverable in the global guardrail registry.""" + from litellm.proxy.guardrails.guardrail_registry import ( + guardrail_initializer_registry, + ) + + assert "dynamoai" in guardrail_initializer_registry From 966124966f83e6e1091ad08d9dfee6e341d3ad85 Mon Sep 17 00:00:00 2001 From: Joe Reyna Date: Mon, 16 Mar 2026 22:25:55 -0700 Subject: [PATCH 29/60] docs: add v1.82.3 release notes and update provider_endpoints_support.json (#23816) --- docs/my-website/release_notes/v1.82.3.md | 374 +++++++++++++++++++++++ provider_endpoints_support.json | 55 ++-- 2 files changed, 406 insertions(+), 23 deletions(-) create mode 100644 docs/my-website/release_notes/v1.82.3.md diff --git a/docs/my-website/release_notes/v1.82.3.md b/docs/my-website/release_notes/v1.82.3.md new file mode 100644 index 00000000000..98351a30a27 --- /dev/null +++ b/docs/my-website/release_notes/v1.82.3.md @@ -0,0 +1,374 @@ +--- +title: "v1.82.3 - Nebius AI, gpt-5.4, Gemini 3.x, FLUX Kontext, and 116 New Models" +slug: "v1-82-3" +date: 2026-03-16T00:00:00 +authors: + - name: Krrish Dholakia + title: CEO, LiteLLM + url: https://www.linkedin.com/in/krish-d/ + image_url: https://pbs.twimg.com/profile_images/1298587542745358340/DZv3Oj-h_400x400.jpg + - name: Ishaan Jaff + title: CTO, LiteLLM + url: https://www.linkedin.com/in/reffajnaahsi/ + image_url: https://pbs.twimg.com/profile_images/1613813310264340481/lz54oEiB_400x400.jpg +hide_table_of_contents: false +--- + +## Deploy this version + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + +``` showLineNumbers title="docker run litellm" +docker run \ +-e STORE_MODEL_IN_DB=True \ +-p 4000:4000 \ +ghcr.io/berriai/litellm:main-1.82.3-stable +``` + + + + +``` showLineNumbers title="pip install litellm" +pip install litellm==1.82.3 +``` + + + + +## Key Highlights + +- **Nebius AI — new provider** — [30 models across DeepSeek, Qwen, Llama, Mistral, NVIDIA, and BAAI available via Nebius AI cloud](../../docs/providers/nebius) - [PR #21542](https://github.com/BerriAI/litellm/pull/21542) +- **OpenAI gpt-5.4 / gpt-5.4-pro — day 0** — Full pricing and routing support for `gpt-5.4` (1M context, $2.50/$15.00) and `gpt-5.4-pro` ($30.00/$180.00) on OpenAI and Azure +- **Gemini 3.x models** — `gemini-3-flash-preview`, `gemini-3.1-pro-preview`, `gemini-3.1-flash-image-preview`, and `gemini-embedding-2-preview` added to cost map for Google AI and Vertex AI +- **FLUX Kontext image editing** — `flux-kontext-pro` and `flux-kontext-max` added to Black Forest Labs, alongside `flux-pro-1.0-fill` and `flux-pro-1.0-expand` for inpainting and outpainting +- **116 new models, 132 deprecated models cleaned up** — Major model map refresh including Mistral Magistral, Dashscope Qwen3 VL, xAI Grok via Azure AI, ZAI GLM-5, Serper Search; removal of OpenAI GPT-3.5/GPT-4 legacy variants, Gemini 1.5, and Vertex AI PaLM2 +- **SageMaker Nova provider** — [New `sagemaker_nova` provider for Amazon Nova models on SageMaker](../../docs/providers/aws_sagemaker) - [PR #21542](https://github.com/BerriAI/litellm/pull/21542) +- **Secret redaction in logs** — API keys, tokens, and credentials automatically scrubbed from all proxy log output. Enabled by default; opt out with `LITELLM_DISABLE_REDACT_SECRETS=true` - [PR #23668](https://github.com/BerriAI/litellm/pull/23668) +- **Streaming stability fix** — Critical fix for `RuntimeError: Cannot send a request, as the client has been closed.` crashes after ~1 hour in production - [PR #22926](https://github.com/BerriAI/litellm/pull/22926) + +--- + +## New Providers and Endpoints + +### New Providers (4 new providers) + +| Provider | Supported LiteLLM Endpoints | Description | +| -------- | --------------------------- | ----------- | +| [Nebius AI](../../docs/providers/nebius) (`nebius/`) | `/chat/completions`, `/embeddings` | EU-based AI cloud with 30+ open models — DeepSeek, Qwen3, Llama 3.1/3.3, NVIDIA Nemotron, BAAI embeddings | +| [ZAI](../../docs/providers/openai_compatible) (`zai/`) | `/chat/completions` | ZhipuAI GLM-5 models via ZAI cloud | +| [Black Forest Labs](../../docs/providers/black_forest_labs) (`black_forest_labs/`) | `/images/generations`, `/images/edits` | FLUX image generation and editing — Kontext Pro/Max, Pro 1.0 Fill/Expand | +| [Serper](../../docs/providers/serper) (`serper/`) | `/search` | Web search via Serper API | +| [SageMaker Nova](../../docs/providers/aws_sagemaker) (`sagemaker_nova/`) | `/chat/completions` | Amazon Nova models via SageMaker endpoint | + +--- + +## New Models / Updated Models + +#### New Model Support (116 new models) + +| Provider | Model | Context Window | Input ($/1M tokens) | Output ($/1M tokens) | Features | +| -------- | ----- | -------------- | ------------------- | -------------------- | -------- | +| OpenAI | `gpt-5.4` | 1.05M | $2.50 | $15.00 | chat, vision, tools, reasoning | +| OpenAI | `gpt-5.4-pro` | 1.05M | $30.00 | $180.00 | responses, vision, tools, reasoning | +| OpenAI | `gpt-5.3-chat-latest` | 128K | $1.75 | $14.00 | chat, vision, tools, reasoning | +| Azure OpenAI | `azure/gpt-5.4` | 1.05M | $2.50 | $15.00 | chat, vision, tools, reasoning | +| Azure OpenAI | `azure/gpt-5.4-pro` | 1.05M | $30.00 | $180.00 | responses, vision, tools, reasoning | +| Azure OpenAI | `azure/gpt-5.3-chat` | 128K | $1.75 | $14.00 | chat, vision, tools, reasoning | +| Google Gemini | `gemini/gemini-3-flash-preview` | 1M | $0.50 | $3.00 | chat, vision, tools, reasoning | +| Google Gemini | `gemini/gemini-3.1-pro-preview` | 1M | $2.00 | $12.00 | chat, vision, tools, reasoning | +| Google Gemini | `gemini/gemini-3.1-flash-image-preview` | 65K | $0.25 | $1.50 | image generation, vision | +| Google Gemini | `gemini/gemini-3.1-flash-lite-preview` | - | - | - | chat | +| Google Gemini | `gemini/gemini-3-pro-image-preview` | - | - | - | image generation | +| Google Gemini | `gemini/gemini-embedding-2-preview` | 8K | $0.20 | - | embeddings | +| Google Vertex AI | `vertex_ai/gemini-3-flash-preview` | - | - | - | chat | +| Google Vertex AI | `vertex_ai/gemini-3.1-pro-preview` | - | - | - | chat | +| Google Vertex AI | `vertex_ai/gemini-3.1-flash-lite-preview` | - | - | - | chat | +| Google Vertex AI | `vertex_ai/gemini-embedding-2-preview` | - | $0.20 | - | embeddings | +| Mistral | `mistral/magistral-medium-1-2-2509` | 40K | $2.00 | $5.00 | chat, tools, reasoning | +| Mistral | `mistral/magistral-small-1-2-2509` | 40K | $0.50 | $1.50 | chat, tools, reasoning | +| Mistral | `mistral/mistral-large-2512` | 262K | $0.50 | $1.50 | chat, vision, tools | +| Mistral | `mistral/mistral-medium-3-1-2508` | - | - | - | chat | +| Mistral | `mistral/mistral-small-3-2-2506` | - | - | - | chat | +| Mistral | `mistral/ministral-3-3b-2512` | - | - | - | chat | +| Mistral | `mistral/ministral-3-8b-2512` | - | - | - | chat | +| Mistral | `mistral/ministral-3-14b-2512` | - | - | - | chat | +| Black Forest Labs | `black_forest_labs/flux-kontext-pro` | - | - | - | image editing | +| Black Forest Labs | `black_forest_labs/flux-kontext-max` | - | - | - | image editing | +| Black Forest Labs | `black_forest_labs/flux-pro-1.0-fill` | - | - | - | image editing (inpaint) | +| Black Forest Labs | `black_forest_labs/flux-pro-1.0-expand` | - | - | - | image editing (outpaint) | +| Black Forest Labs | `black_forest_labs/flux-pro-1.1` | - | - | - | image generation | +| Black Forest Labs | `black_forest_labs/flux-pro-1.1-ultra` | - | - | - | image generation | +| Black Forest Labs | `black_forest_labs/flux-dev` | - | - | - | image generation | +| Black Forest Labs | `black_forest_labs/flux-pro` | - | - | - | image generation | +| Azure AI | `azure_ai/grok-4-1-fast-non-reasoning` | 131K | $0.20 | $0.50 | chat, tools | +| Azure AI | `azure_ai/grok-4-1-fast-reasoning` | 131K | $0.20 | $0.50 | chat, tools, reasoning | +| Azure AI | `azure_ai/mistral-document-ai-2512` | - | - | - | OCR | +| Dashscope | `dashscope/qwen3-next-80b-a3b-instruct` | 262K | $0.15 | $1.20 | chat | +| Dashscope | `dashscope/qwen3-next-80b-a3b-thinking` | 262K | $0.15 | $1.20 | chat, reasoning | +| Dashscope | `dashscope/qwen3-vl-235b-a22b-instruct` | 131K | $0.40 | $1.60 | chat, vision | +| Dashscope | `dashscope/qwen3-vl-235b-a22b-thinking` | 131K | $0.40 | $4.00 | chat, vision, reasoning | +| Dashscope | `dashscope/qwen3-vl-32b-instruct` | 131K | $0.16 | $0.64 | chat, vision | +| Dashscope | `dashscope/qwen3-vl-32b-thinking` | 131K | $0.16 | $2.87 | chat, vision, reasoning | +| Dashscope | `dashscope/qwen3-vl-plus` | 260K | - | - | chat, vision | +| Dashscope | `dashscope/qwen3.5-plus` | 992K | - | - | chat | +| Dashscope | `dashscope/qwen3-max-2026-01-23` | 258K | - | - | chat | +| Nebius AI | `nebius/deepseek-ai/DeepSeek-R1` | 128K | $0.80 | $2.40 | chat, reasoning | +| Nebius AI | `nebius/deepseek-ai/DeepSeek-R1-0528` | 164K | $0.80 | $2.40 | chat, reasoning | +| Nebius AI | `nebius/deepseek-ai/DeepSeek-V3` | 128K | $0.50 | $1.50 | chat | +| Nebius AI | `nebius/deepseek-ai/DeepSeek-V3-0324` | 128K | $0.50 | $1.50 | chat | +| Nebius AI | `nebius/deepseek-ai/DeepSeek-R1-Distill-Llama-70B` | 128K | $0.25 | $0.75 | chat | +| Nebius AI | `nebius/Qwen/Qwen3-235B-A22B` | 262K | $0.20 | $0.60 | chat | +| Nebius AI | `nebius/Qwen/Qwen3-32B` | 32K | $0.10 | $0.30 | chat | +| Nebius AI | `nebius/Qwen/Qwen3-30B-A3B` | 32K | $0.10 | $0.30 | chat | +| Nebius AI | `nebius/Qwen/Qwen3-14B` | 32K | $0.08 | $0.24 | chat | +| Nebius AI | `nebius/Qwen/Qwen3-4B` | 32K | $0.08 | $0.24 | chat | +| Nebius AI | `nebius/Qwen/QwQ-32B` | 32K | $0.15 | $0.45 | chat | +| Nebius AI | `nebius/Qwen/Qwen2.5-72B-Instruct` | 128K | $0.13 | $0.40 | chat | +| Nebius AI | `nebius/Qwen/Qwen2.5-32B-Instruct` | 128K | $0.06 | $0.20 | chat | +| Nebius AI | `nebius/Qwen/Qwen2.5-VL-72B-Instruct` | 131K | $0.13 | $0.40 | chat, vision | +| Nebius AI | `nebius/Qwen/Qwen2-VL-72B-Instruct` | 131K | $0.13 | $0.40 | chat, vision | +| Nebius AI | `nebius/Qwen/Qwen2-VL-7B-Instruct` | 131K | $0.02 | $0.06 | chat, vision | +| Nebius AI | `nebius/meta-llama/Meta-Llama-3.1-405B-Instruct` | 128K | $1.00 | $3.00 | chat | +| Nebius AI | `nebius/meta-llama/Meta-Llama-3.1-70B-Instruct` | 128K | $0.13 | $0.40 | chat | +| Nebius AI | `nebius/meta-llama/Meta-Llama-3.1-8B-Instruct` | 128K | $0.02 | $0.06 | chat | +| Nebius AI | `nebius/meta-llama/Llama-3.3-70B-Instruct` | 128K | $0.13 | $0.40 | chat | +| Nebius AI | `nebius/meta-llama/Llama-Guard-3-8B` | 128K | $0.02 | $0.06 | chat | +| Nebius AI | `nebius/nvidia/Llama-3.1-Nemotron-Ultra-253B-v1` | 128K | $0.60 | $1.80 | chat | +| Nebius AI | `nebius/nvidia/Llama-3.3-Nemotron-Super-49B-v1` | 131K | $0.10 | $0.40 | chat | +| Nebius AI | `nebius/NousResearch/Hermes-3-Llama-3.1-405B` | 128K | $1.00 | $3.00 | chat | +| Nebius AI | `nebius/google/gemma-3-27b-it` | 128K | $0.06 | $0.20 | chat | +| Nebius AI | `nebius/mistralai/Mistral-Nemo-Instruct-2407` | 128K | $0.04 | $0.12 | chat | +| Nebius AI | `nebius/Qwen/Qwen2.5-Coder-7B` | 32K | $0.01 | $0.03 | chat | +| Nebius AI | `nebius/BAAI/bge-en-icl` | 32K | $0.01 | - | embeddings | +| Nebius AI | `nebius/BAAI/bge-multilingual-gemma2` | 8K | $0.01 | - | embeddings | +| Nebius AI | `nebius/intfloat/e5-mistral-7b-instruct` | 32K | $0.01 | - | embeddings | +| AWS Bedrock | `mistral.devstral-2-123b` | 256K | $0.40 | $2.00 | chat, tools | +| AWS Bedrock | `zai.glm-4.7-flash` | 200K | $0.07 | $0.40 | chat, tools, reasoning | +| ZAI | `zai/glm-5` | 200K | $1.00 | $3.20 | chat, tools, reasoning | +| ZAI | `zai/glm-5-code` | 200K | $1.20 | $5.00 | chat, tools, reasoning | +| OpenRouter | `openrouter/anthropic/claude-sonnet-4.6` | - | - | - | chat | +| OpenRouter | `openrouter/google/gemini-3.1-pro-preview` | - | - | - | chat | +| OpenRouter | `openrouter/openai/gpt-5.1-codex-max` | - | - | - | chat | +| OpenRouter | `openrouter/qwen/qwen3-coder-plus` | - | - | - | chat | +| OpenRouter | `openrouter/qwen/qwen3.5-*` (5 models) | - | - | - | chat | +| OpenRouter | `openrouter/z-ai/glm-5` | - | - | - | chat | +| Together AI | `together_ai/Qwen/Qwen3.5-397B-A17B` | - | - | - | chat | +| Perplexity | `perplexity/pplx-embed-v1-0.6b` | 32K | $0.00 | - | embeddings | +| Perplexity | `perplexity/pplx-embed-v1-4b` | 32K | $0.03 | - | embeddings | +| Serper | `serper/search` | - | - | - | search | + +#### Updated Models + +- **[AWS Bedrock](../../docs/providers/bedrock)** + - Add `cache_read_input_token_cost` and `cache_creation_input_token_cost` to Bedrock-hosted Anthropic models (`claude-3-opus`, `claude-3-sonnet`, `claude-3-haiku`, and APAC/EU variants) — prompt caching is now tracked for cost estimation + - Rename `apac.anthropic.claude-sonnet-4-6` → `au.anthropic.claude-sonnet-4-6` to reflect correct regional identifier + +- **[Azure OpenAI](../../docs/providers/azure)** + - Add `supports_none_reasoning_effort` to all `gpt-5.1-chat`, `gpt-5.1-codex`, and `gpt-5.4` variants (global, EU, standard deployments) — allows passing `reasoning_effort: null` to disable reasoning + +- **[Azure OpenAI](../../docs/providers/azure)** — Removed deprecated models + - Remove `azure/gpt-35-turbo-0301` (deprecated 2025-02-13) + - Remove `azure/gpt-35-turbo-0613` (deprecated 2025-02-13) + +#### Features + +- **[OpenAI](../../docs/providers/openai)** + - Day 0 support for `gpt-5.4` and `gpt-5.4-pro` on OpenAI and Azure + +- **[Google Gemini](../../docs/providers/gemini)** + - Add Gemini 3.x model cost map entries — `gemini-3-flash-preview`, `gemini-3.1-pro-preview`, `gemini-3.1-flash-lite-preview`, `gemini-3-pro-image-preview`, `gemini-embedding-2-preview` + - Add Gemini 2.0 Flash and Flash Lite to cost map (re-added with updated pricing) + +- **[Google Vertex AI](../../docs/providers/vertex)** + - Add `gemini-3-flash-preview`, `gemini-3.1-flash-lite-preview`, `gemini-flash-experimental`, and `gemini-embedding-2-preview` to Vertex AI model cost map + +- **[Mistral](../../docs/providers/mistral)** + - Add Magistral reasoning models (`magistral-medium-1-2-2509`, `magistral-small-1-2-2509`) + - Add `mistral-large-2512`, `mistral-medium-3-1-2508`, `mistral-small-3-2-2506`, `ministral-3-*` variants + +- **[Dashscope / Qwen](../../docs/providers/dashscope)** + - Add Qwen3 VL multimodal models (`qwen3-vl-235b`, `qwen3-vl-32b` — instruct and thinking variants) + - Add `qwen3-next-80b-a3b` (instruct + thinking), `qwen3.5-plus`, `qwen3-max-2026-01-23` + +- **[Black Forest Labs](../../docs/providers/black_forest_labs)** + - Add FLUX Kontext image editing models (`flux-kontext-pro`, `flux-kontext-max`) + - Add FLUX Pro 1.0 Fill (inpainting) and Expand (outpainting) + - Add `flux-pro-1.1`, `flux-pro-1.1-ultra`, `flux-dev`, `flux-pro` + +- **[Azure AI](../../docs/providers/azure_ai)** + - Add xAI Grok models via Azure AI Foundry (`grok-4-1-fast-non-reasoning`, `grok-4-1-fast-reasoning`) + - Add Mistral Document AI (`mistral-document-ai-2512`) — OCR mode + +- **[AWS Bedrock](../../docs/providers/bedrock)** + - Add `mistral.devstral-2-123b` (256K context, tools) + - Add `zai.glm-4.7-flash` via Bedrock Converse (200K context, tools, reasoning) + +- **[SageMaker](../../docs/providers/aws_sagemaker)** + - Add `sagemaker_nova` provider for Amazon Nova models on SageMaker - [PR #21542](https://github.com/BerriAI/litellm/pull/21542) + +#### Deprecated / Removed Models + +**OpenAI** — Legacy models removed from cost map: +- `gpt-3.5-turbo-0301`, `gpt-3.5-turbo-0613`, `gpt-3.5-turbo-16k-0613` +- `gpt-4-0314`, `gpt-4-32k`, `gpt-4-32k-0314`, `gpt-4-32k-0613`, `gpt-4-1106-vision-preview`, `gpt-4-vision-preview` +- `gpt-4.5-preview`, `gpt-4.5-preview-2025-02-27` +- `gpt-4o-audio-preview-2024-10-01`, `gpt-4o-realtime-preview-2024-10-01` +- `o1-mini`, `o1-mini-2024-09-12`, `o1-preview`, `o1-preview-2024-09-12` + +**Google Gemini** — Gemini 1.5 and legacy 2.0 variants removed: +- All `gemini-1.5-*` variants (flash, flash-8b, pro, and dated versions) +- `gemini-2.0-flash-exp`, `gemini-2.0-pro-exp-02-05`, `gemini-2.5-flash-preview-04-17`, `gemini-2.5-flash-preview-05-20` + +**Google Vertex AI** — PaLM 2 / legacy models removed: +- All `chat-bison`, `text-bison`, `codechat-bison`, `code-bison`, `code-gecko` variants +- Gemini 1.0 Pro, 1.5 Flash/Pro, 2.0 Flash experimental, and preview variants + +**Perplexity** — Legacy Llama-sonar models removed: +- `llama-3.1-sonar-huge-128k-online`, `llama-3.1-sonar-large/small-128k-chat/online` + +--- + +## LLM API Endpoints + +#### Features + +- **[Responses API](../../docs/response_api)** + - Handle `response.failed`, `response.incomplete`, and `response.cancelled` terminal event types in background streaming — previously only `response.completed` was handled - [PR #23492](https://github.com/BerriAI/litellm/pull/23492) + +#### Bug Fixes + +- **[Anthropic](../../docs/providers/anthropic)** + - Preserve native tool format (web_search, bash, tool_search, etc.) when guardrails convert tools for the Anthropic Messages API - [PR #23526](https://github.com/BerriAI/litellm/pull/23526) + +- **[Moonshot / Kimi](../../docs/providers/openai_compatible)** + - Auto-fill `reasoning_content` for Moonshot Kimi reasoning models - [PR #23580](https://github.com/BerriAI/litellm/pull/23580) + +- **[HuggingFace](../../docs/providers/huggingface)** + - Forward `extra_headers` to HuggingFace embedding API - [PR #23525](https://github.com/BerriAI/litellm/pull/23525) + +- **General** + - Normalize `content_filtered` finish reason across providers - [PR #23564](https://github.com/BerriAI/litellm/pull/23564) + - Fix custom cost tracking on deployments for `/v1/messages` and `/v1/responses` - [PR #23647](https://github.com/BerriAI/litellm/pull/23647) + - Fix per-request custom pricing when `router_model_id` has no pricing data — now falls back to model name + +--- + +## Management Endpoints / UI + +#### Features + +- **Virtual Keys** + - Add Organization dropdown to Create/Edit Key form — `organization_id` is now a first-class field in Key Ownership - [PR #23595](https://github.com/BerriAI/litellm/pull/23595) + - Allow setting `organization_id` on `/key/update` — keys can be assigned or moved to a different organization after creation - [PR #23557](https://github.com/BerriAI/litellm/pull/23557) + +- **Internal Users** + - Add/Remove Team Membership directly from the Internal Users info page — includes searchable dropdown and role selector; no longer requires navigating to each team - [PR #23638](https://github.com/BerriAI/litellm/pull/23638) + +- **Default Team Settings** + - Modernize page to antd (consistent with rest of app) - [PR #23614](https://github.com/BerriAI/litellm/pull/23614) + - Fix: default team params (budget, duration, tpm, rpm, permissions) now correctly applied on `/team/new` - [PR #23614](https://github.com/BerriAI/litellm/pull/23614) + - Fix: settings persist across proxy restarts (`default_team_params` added to `LITELLM_SETTINGS_SAFE_DB_OVERRIDES`) - [PR #23614](https://github.com/BerriAI/litellm/pull/23614) + - Fix: resolved race condition in `_update_litellm_setting` where `get_config()` could overwrite freshly saved values - [PR #23614](https://github.com/BerriAI/litellm/pull/23614) + +- **Usage** + - Auto-paginate daily spend data — all entity views (teams, orgs, customers, tags, agents, users) fetch pages progressively with charts updating after each page - [PR #23622](https://github.com/BerriAI/litellm/pull/23622) + +- **Models / Cost** + - Azure Model Router cost breakdown in UI — show per-sub-model `additional_costs` from `hidden_params` in `CostBreakdownViewer` - [PR #23550](https://github.com/BerriAI/litellm/pull/23550) + +- **User Management** + - New `/user/info/v2` endpoint — scoped, paginated replacement for the existing god endpoint that caused memory and stability issues on large installs - [PR #23437](https://github.com/BerriAI/litellm/pull/23437) + +#### Bugs + +- Fix Tag list endpoint returning 500 due to invalid Prisma `group_by` kwargs - [PR #23606](https://github.com/BerriAI/litellm/pull/23606) +- Fix Team Admin getting 403 on `/user/filter/ui` when `scope_user_search_to_org` is enabled - [PR #23671](https://github.com/BerriAI/litellm/pull/23671) +- Fix Public Model Hub not showing config-defined models after save - [PR #23501](https://github.com/BerriAI/litellm/pull/23501) +- Fix fallback popup model dropdown z-index issue - [PR #23516](https://github.com/BerriAI/litellm/pull/23516) +- Fix double-counting bug in org/team key limit checks on `/key/update` + +--- + +## AI Integrations + +### Logging + +- **[Vantage](https://vantage.sh)** + - Add Vantage integration for FOCUS 1.2 CSV export — export LiteLLM proxy spend data as FinOps Open Cost & Usage Specification reports, with time-windowed filenames to prevent overwrites - [PR #23333](https://github.com/BerriAI/litellm/pull/23333) + +- **General** + - Fix silent metrics race condition causing metric collision across experiments - [PR #23542](https://github.com/BerriAI/litellm/pull/23542) + +### Guardrails + +No major guardrail changes in this release. + +### Prompt Management + +No major prompt management changes in this release. + +### Secret Managers + +No major secret manager changes in this release. + +--- + +## Performance / Loadbalancing / Reliability improvements + +- **Fix streaming crashes after ~1 hour** — `LLMClientCache._remove_key()` no longer calls `close()`/`aclose()` on evicted HTTP/SDK clients. In-flight requests were crashing with `RuntimeError: Cannot send a request, as the client has been closed.` after the 1-hour TTL expired. Cleanup now happens only at shutdown via `close_litellm_async_clients()` - [PR #22926](https://github.com/BerriAI/litellm/pull/22926) +- **Fix OOM / Prisma connection loss** on large installs — unbounded managed-object poll was exhausting Prisma connections after ~60–70 minutes on instances with 336K+ queued response rows - [PR #23472](https://github.com/BerriAI/litellm/pull/23472) +- **Centralize logging kwarg updates** — root cause fix migrating all logging updates to a single function, eliminating kwarg inconsistencies across logging paths - [PR #23659](https://github.com/BerriAI/litellm/pull/23659) +- **Fix tiktoken cache for non-root offline containers** — tiktoken cache now works correctly in offline environments running as non-root users - [PR #23498](https://github.com/BerriAI/litellm/pull/23498) +- **Add CodSpeed continuous performance benchmarks** — automated performance regression tracking on CI - [PR #23676](https://github.com/BerriAI/litellm/pull/23676) + +--- + +## Security + +- **Secret redaction in proxy logs** — Adds a `SecretRedactionFilter` to all LiteLLM loggers that scrubs API keys, tokens, and credentials from log messages, format args, exception tracebacks, and extra fields. Enabled by default; opt out with `LITELLM_DISABLE_REDACT_SECRETS=true` - [PR #23668](https://github.com/BerriAI/litellm/pull/23668), [PR #23667](https://github.com/BerriAI/litellm/pull/23667) +- **Bump PyJWT to `^2.12.0`** — addresses security vulnerability in `^2.10.1` - [PR #23678](https://github.com/BerriAI/litellm/pull/23678) +- **Bump `tar` to 7.5.11 and `tornado` to 6.5.5** — addresses CVEs in transitive dependencies - [PR #23602](https://github.com/BerriAI/litellm/pull/23602) + +--- + +## Database / Proxy Operations + +- **Fix Prisma migrate deploy on pre-existing instances** — resolved multiple bugs in migration recovery logic: missing return in the P3018 idempotent error handler and unhandled exceptions in `_roll_back_migration` that caused silent failures even after successful recovery - [PR #23655](https://github.com/BerriAI/litellm/pull/23655) +- **Make DB migration failure exit opt-in** — proxy no longer exits on `prisma migrate deploy` failure by default; enable with `--enforce_prisma_migration_check` - [PR #23675](https://github.com/BerriAI/litellm/pull/23675) + +--- + +## New Contributors + +* @ryanh-ai made their first contribution in [PR #21542](https://github.com/BerriAI/litellm/pull/21542) +* @ryan-crabbe made their first contribution in [PR #23668](https://github.com/BerriAI/litellm/pull/23668) +* @Jah-yee made their first contribution in [PR #23525](https://github.com/BerriAI/litellm/pull/23525) +* @gambletan made their first contribution in [PR #23516](https://github.com/BerriAI/litellm/pull/23516) +* @awais786 made their first contribution in [PR #23183](https://github.com/BerriAI/litellm/pull/23183) +* @pradyyadav made their first contribution in [PR #23580](https://github.com/BerriAI/litellm/pull/23580) +* @xianzongxie-stripe made their first contribution in [PR #23492](https://github.com/BerriAI/litellm/pull/23492) +* @Harshit28j made their first contribution in [PR #23333](https://github.com/BerriAI/litellm/pull/23333) +* @codspeed-hq[bot] made their first contribution in [PR #23676](https://github.com/BerriAI/litellm/pull/23676) + +--- + +## Diff Summary + +## 03/16/2026 +* New Providers: 5 +* New Models / Updated Models: 116 new, 132 removed +* LLM API Endpoints: 5 +* Management Endpoints / UI: 11 +* AI Integrations: 2 +* Performance / Reliability: 5 +* Security: 3 +* Database / Proxy Operations: 2 + +--- + +## Full Changelog +[v1.82.0-stable...v1.82.3-stable](https://github.com/BerriAI/litellm/compare/v1.82.0-stable...v1.82.3-stable) diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index 2f3302bb574..473edb5a6b2 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -471,9 +471,7 @@ "audio_speech": false, "moderations": false, "batches": false, - "rerank": false, - "a2a": false, - "interactions": false + "rerank": false } }, "chutes": { @@ -1486,12 +1484,12 @@ } }, "nebius": { - "display_name": "Nebius AI Studio (`nebius`)", + "display_name": "Nebius AI (`nebius`)", "url": "https://docs.litellm.ai/docs/providers/nebius", "endpoints": { "chat_completions": true, - "messages": true, - "responses": true, + "messages": false, + "responses": false, "embeddings": true, "image_generations": false, "audio_transcriptions": false, @@ -1499,8 +1497,7 @@ "moderations": false, "batches": false, "rerank": false, - "a2a": true, - "interactions": true + "a2a": false } }, "nlp_cloud": { @@ -2081,9 +2078,20 @@ }, "serper": { "display_name": "Serper (`serper`)", - "url": "https://docs.litellm.ai/docs/search/serper", + "url": "https://docs.litellm.ai/docs/providers/serper", "endpoints": { - "search": true + "chat_completions": false, + "messages": false, + "responses": false, + "embeddings": false, + "image_generations": false, + "audio_transcriptions": false, + "audio_speech": false, + "moderations": false, + "batches": false, + "rerank": false, + "search": true, + "a2a": false } }, "triton": { @@ -2265,12 +2273,12 @@ } }, "zai": { - "display_name": "Z.AI (Zhipu AI) (`zai`)", - "url": "https://docs.litellm.ai/docs/providers/zai", + "display_name": "ZAI (`zai`)", + "url": "https://docs.litellm.ai/docs/providers/openai_compatible", "endpoints": { "chat_completions": true, - "messages": true, - "responses": true, + "messages": false, + "responses": false, "embeddings": false, "image_generations": false, "audio_transcriptions": false, @@ -2278,8 +2286,7 @@ "moderations": false, "batches": false, "rerank": false, - "a2a": true, - "interactions": true + "a2a": false } }, "ragflow": { @@ -2551,23 +2558,25 @@ "audio_speech": false, "moderations": false, "batches": false, - "rerank": false + "rerank": false, + "a2a": false } }, - "charity_engine": { - "display_name": "Charity Engine (`charity_engine`)", - "url": "https://docs.litellm.ai/docs/providers/charity_engine", + "sagemaker_nova": { + "display_name": "AWS SageMaker Nova (`sagemaker_nova`)", + "url": "https://docs.litellm.ai/docs/providers/aws_sagemaker", "endpoints": { "chat_completions": true, - "messages": true, - "responses": true, + "messages": false, + "responses": false, "embeddings": false, "image_generations": false, "audio_transcriptions": false, "audio_speech": false, "moderations": false, "batches": false, - "rerank": false + "rerank": false, + "a2a": false } } }, From c687e631b4581afcf9df057f9ad8e5d9c6ec7aa1 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 16 Mar 2026 22:26:11 -0700 Subject: [PATCH 30/60] [Feature] Add disable_custom_api_keys toggle to UI Settings page Adds a toggle switch to the admin UI Settings page so administrators can enable/disable custom API key values without making direct API calls. Co-Authored-By: Claude Opus 4.6 --- .../AdminSettings/UISettings/UISettings.tsx | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx index a22c78c9430..0d12a4c4bf5 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx @@ -24,6 +24,7 @@ export default function UISettings() { const disableVectorStoresProperty = schema?.properties?.disable_vector_stores_for_internal_users; const allowVectorStoresTeamAdminsProperty = schema?.properties?.allow_vector_stores_for_team_admins; const scopeUserSearchProperty = schema?.properties?.scope_user_search_to_org; + const disableCustomApiKeysProperty = schema?.properties?.disable_custom_api_keys; const values = data?.values ?? {}; const isDisabledForInternalUsers = Boolean(values.disable_model_add_for_internal_users); const isDisabledTeamAdminDeleteTeamUser = Boolean(values.disable_team_admin_delete_team_user); @@ -182,6 +183,20 @@ export default function UISettings() { ); }; + const handleToggleDisableCustomApiKeys = (checked: boolean) => { + updateSettings( + { disable_custom_api_keys: checked }, + { + onSuccess: () => { + NotificationManager.success("UI settings updated successfully"); + }, + onError: (error) => { + NotificationManager.fromBackend(error); + }, + }, + ); + }; + return ( {isLoading ? ( @@ -382,6 +397,26 @@ export default function UISettings() { + {/* Disable custom API key values */} + + + + Disable custom API key values + + {disableCustomApiKeysProperty?.description ?? + "If true, users cannot specify custom API key values. All keys must be auto-generated."} + + + + + + {/* Page Visibility for Internal Users */} Date: Mon, 16 Mar 2026 22:26:45 -0700 Subject: [PATCH 31/60] =?UTF-8?q?Revert=20"docs:=20add=20v1.82.3=20release?= =?UTF-8?q?=20notes=20and=20update=20provider=5Fendpoints=5Fsupport?= =?UTF-8?q?=E2=80=A6"=20(#23817)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 966124966f83e6e1091ad08d9dfee6e341d3ad85. --- docs/my-website/release_notes/v1.82.3.md | 374 ----------------------- provider_endpoints_support.json | 55 ++-- 2 files changed, 23 insertions(+), 406 deletions(-) delete mode 100644 docs/my-website/release_notes/v1.82.3.md diff --git a/docs/my-website/release_notes/v1.82.3.md b/docs/my-website/release_notes/v1.82.3.md deleted file mode 100644 index 98351a30a27..00000000000 --- a/docs/my-website/release_notes/v1.82.3.md +++ /dev/null @@ -1,374 +0,0 @@ ---- -title: "v1.82.3 - Nebius AI, gpt-5.4, Gemini 3.x, FLUX Kontext, and 116 New Models" -slug: "v1-82-3" -date: 2026-03-16T00:00:00 -authors: - - name: Krrish Dholakia - title: CEO, LiteLLM - url: https://www.linkedin.com/in/krish-d/ - image_url: https://pbs.twimg.com/profile_images/1298587542745358340/DZv3Oj-h_400x400.jpg - - name: Ishaan Jaff - title: CTO, LiteLLM - url: https://www.linkedin.com/in/reffajnaahsi/ - image_url: https://pbs.twimg.com/profile_images/1613813310264340481/lz54oEiB_400x400.jpg -hide_table_of_contents: false ---- - -## Deploy this version - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - - - - -``` showLineNumbers title="docker run litellm" -docker run \ --e STORE_MODEL_IN_DB=True \ --p 4000:4000 \ -ghcr.io/berriai/litellm:main-1.82.3-stable -``` - - - - -``` showLineNumbers title="pip install litellm" -pip install litellm==1.82.3 -``` - - - - -## Key Highlights - -- **Nebius AI — new provider** — [30 models across DeepSeek, Qwen, Llama, Mistral, NVIDIA, and BAAI available via Nebius AI cloud](../../docs/providers/nebius) - [PR #21542](https://github.com/BerriAI/litellm/pull/21542) -- **OpenAI gpt-5.4 / gpt-5.4-pro — day 0** — Full pricing and routing support for `gpt-5.4` (1M context, $2.50/$15.00) and `gpt-5.4-pro` ($30.00/$180.00) on OpenAI and Azure -- **Gemini 3.x models** — `gemini-3-flash-preview`, `gemini-3.1-pro-preview`, `gemini-3.1-flash-image-preview`, and `gemini-embedding-2-preview` added to cost map for Google AI and Vertex AI -- **FLUX Kontext image editing** — `flux-kontext-pro` and `flux-kontext-max` added to Black Forest Labs, alongside `flux-pro-1.0-fill` and `flux-pro-1.0-expand` for inpainting and outpainting -- **116 new models, 132 deprecated models cleaned up** — Major model map refresh including Mistral Magistral, Dashscope Qwen3 VL, xAI Grok via Azure AI, ZAI GLM-5, Serper Search; removal of OpenAI GPT-3.5/GPT-4 legacy variants, Gemini 1.5, and Vertex AI PaLM2 -- **SageMaker Nova provider** — [New `sagemaker_nova` provider for Amazon Nova models on SageMaker](../../docs/providers/aws_sagemaker) - [PR #21542](https://github.com/BerriAI/litellm/pull/21542) -- **Secret redaction in logs** — API keys, tokens, and credentials automatically scrubbed from all proxy log output. Enabled by default; opt out with `LITELLM_DISABLE_REDACT_SECRETS=true` - [PR #23668](https://github.com/BerriAI/litellm/pull/23668) -- **Streaming stability fix** — Critical fix for `RuntimeError: Cannot send a request, as the client has been closed.` crashes after ~1 hour in production - [PR #22926](https://github.com/BerriAI/litellm/pull/22926) - ---- - -## New Providers and Endpoints - -### New Providers (4 new providers) - -| Provider | Supported LiteLLM Endpoints | Description | -| -------- | --------------------------- | ----------- | -| [Nebius AI](../../docs/providers/nebius) (`nebius/`) | `/chat/completions`, `/embeddings` | EU-based AI cloud with 30+ open models — DeepSeek, Qwen3, Llama 3.1/3.3, NVIDIA Nemotron, BAAI embeddings | -| [ZAI](../../docs/providers/openai_compatible) (`zai/`) | `/chat/completions` | ZhipuAI GLM-5 models via ZAI cloud | -| [Black Forest Labs](../../docs/providers/black_forest_labs) (`black_forest_labs/`) | `/images/generations`, `/images/edits` | FLUX image generation and editing — Kontext Pro/Max, Pro 1.0 Fill/Expand | -| [Serper](../../docs/providers/serper) (`serper/`) | `/search` | Web search via Serper API | -| [SageMaker Nova](../../docs/providers/aws_sagemaker) (`sagemaker_nova/`) | `/chat/completions` | Amazon Nova models via SageMaker endpoint | - ---- - -## New Models / Updated Models - -#### New Model Support (116 new models) - -| Provider | Model | Context Window | Input ($/1M tokens) | Output ($/1M tokens) | Features | -| -------- | ----- | -------------- | ------------------- | -------------------- | -------- | -| OpenAI | `gpt-5.4` | 1.05M | $2.50 | $15.00 | chat, vision, tools, reasoning | -| OpenAI | `gpt-5.4-pro` | 1.05M | $30.00 | $180.00 | responses, vision, tools, reasoning | -| OpenAI | `gpt-5.3-chat-latest` | 128K | $1.75 | $14.00 | chat, vision, tools, reasoning | -| Azure OpenAI | `azure/gpt-5.4` | 1.05M | $2.50 | $15.00 | chat, vision, tools, reasoning | -| Azure OpenAI | `azure/gpt-5.4-pro` | 1.05M | $30.00 | $180.00 | responses, vision, tools, reasoning | -| Azure OpenAI | `azure/gpt-5.3-chat` | 128K | $1.75 | $14.00 | chat, vision, tools, reasoning | -| Google Gemini | `gemini/gemini-3-flash-preview` | 1M | $0.50 | $3.00 | chat, vision, tools, reasoning | -| Google Gemini | `gemini/gemini-3.1-pro-preview` | 1M | $2.00 | $12.00 | chat, vision, tools, reasoning | -| Google Gemini | `gemini/gemini-3.1-flash-image-preview` | 65K | $0.25 | $1.50 | image generation, vision | -| Google Gemini | `gemini/gemini-3.1-flash-lite-preview` | - | - | - | chat | -| Google Gemini | `gemini/gemini-3-pro-image-preview` | - | - | - | image generation | -| Google Gemini | `gemini/gemini-embedding-2-preview` | 8K | $0.20 | - | embeddings | -| Google Vertex AI | `vertex_ai/gemini-3-flash-preview` | - | - | - | chat | -| Google Vertex AI | `vertex_ai/gemini-3.1-pro-preview` | - | - | - | chat | -| Google Vertex AI | `vertex_ai/gemini-3.1-flash-lite-preview` | - | - | - | chat | -| Google Vertex AI | `vertex_ai/gemini-embedding-2-preview` | - | $0.20 | - | embeddings | -| Mistral | `mistral/magistral-medium-1-2-2509` | 40K | $2.00 | $5.00 | chat, tools, reasoning | -| Mistral | `mistral/magistral-small-1-2-2509` | 40K | $0.50 | $1.50 | chat, tools, reasoning | -| Mistral | `mistral/mistral-large-2512` | 262K | $0.50 | $1.50 | chat, vision, tools | -| Mistral | `mistral/mistral-medium-3-1-2508` | - | - | - | chat | -| Mistral | `mistral/mistral-small-3-2-2506` | - | - | - | chat | -| Mistral | `mistral/ministral-3-3b-2512` | - | - | - | chat | -| Mistral | `mistral/ministral-3-8b-2512` | - | - | - | chat | -| Mistral | `mistral/ministral-3-14b-2512` | - | - | - | chat | -| Black Forest Labs | `black_forest_labs/flux-kontext-pro` | - | - | - | image editing | -| Black Forest Labs | `black_forest_labs/flux-kontext-max` | - | - | - | image editing | -| Black Forest Labs | `black_forest_labs/flux-pro-1.0-fill` | - | - | - | image editing (inpaint) | -| Black Forest Labs | `black_forest_labs/flux-pro-1.0-expand` | - | - | - | image editing (outpaint) | -| Black Forest Labs | `black_forest_labs/flux-pro-1.1` | - | - | - | image generation | -| Black Forest Labs | `black_forest_labs/flux-pro-1.1-ultra` | - | - | - | image generation | -| Black Forest Labs | `black_forest_labs/flux-dev` | - | - | - | image generation | -| Black Forest Labs | `black_forest_labs/flux-pro` | - | - | - | image generation | -| Azure AI | `azure_ai/grok-4-1-fast-non-reasoning` | 131K | $0.20 | $0.50 | chat, tools | -| Azure AI | `azure_ai/grok-4-1-fast-reasoning` | 131K | $0.20 | $0.50 | chat, tools, reasoning | -| Azure AI | `azure_ai/mistral-document-ai-2512` | - | - | - | OCR | -| Dashscope | `dashscope/qwen3-next-80b-a3b-instruct` | 262K | $0.15 | $1.20 | chat | -| Dashscope | `dashscope/qwen3-next-80b-a3b-thinking` | 262K | $0.15 | $1.20 | chat, reasoning | -| Dashscope | `dashscope/qwen3-vl-235b-a22b-instruct` | 131K | $0.40 | $1.60 | chat, vision | -| Dashscope | `dashscope/qwen3-vl-235b-a22b-thinking` | 131K | $0.40 | $4.00 | chat, vision, reasoning | -| Dashscope | `dashscope/qwen3-vl-32b-instruct` | 131K | $0.16 | $0.64 | chat, vision | -| Dashscope | `dashscope/qwen3-vl-32b-thinking` | 131K | $0.16 | $2.87 | chat, vision, reasoning | -| Dashscope | `dashscope/qwen3-vl-plus` | 260K | - | - | chat, vision | -| Dashscope | `dashscope/qwen3.5-plus` | 992K | - | - | chat | -| Dashscope | `dashscope/qwen3-max-2026-01-23` | 258K | - | - | chat | -| Nebius AI | `nebius/deepseek-ai/DeepSeek-R1` | 128K | $0.80 | $2.40 | chat, reasoning | -| Nebius AI | `nebius/deepseek-ai/DeepSeek-R1-0528` | 164K | $0.80 | $2.40 | chat, reasoning | -| Nebius AI | `nebius/deepseek-ai/DeepSeek-V3` | 128K | $0.50 | $1.50 | chat | -| Nebius AI | `nebius/deepseek-ai/DeepSeek-V3-0324` | 128K | $0.50 | $1.50 | chat | -| Nebius AI | `nebius/deepseek-ai/DeepSeek-R1-Distill-Llama-70B` | 128K | $0.25 | $0.75 | chat | -| Nebius AI | `nebius/Qwen/Qwen3-235B-A22B` | 262K | $0.20 | $0.60 | chat | -| Nebius AI | `nebius/Qwen/Qwen3-32B` | 32K | $0.10 | $0.30 | chat | -| Nebius AI | `nebius/Qwen/Qwen3-30B-A3B` | 32K | $0.10 | $0.30 | chat | -| Nebius AI | `nebius/Qwen/Qwen3-14B` | 32K | $0.08 | $0.24 | chat | -| Nebius AI | `nebius/Qwen/Qwen3-4B` | 32K | $0.08 | $0.24 | chat | -| Nebius AI | `nebius/Qwen/QwQ-32B` | 32K | $0.15 | $0.45 | chat | -| Nebius AI | `nebius/Qwen/Qwen2.5-72B-Instruct` | 128K | $0.13 | $0.40 | chat | -| Nebius AI | `nebius/Qwen/Qwen2.5-32B-Instruct` | 128K | $0.06 | $0.20 | chat | -| Nebius AI | `nebius/Qwen/Qwen2.5-VL-72B-Instruct` | 131K | $0.13 | $0.40 | chat, vision | -| Nebius AI | `nebius/Qwen/Qwen2-VL-72B-Instruct` | 131K | $0.13 | $0.40 | chat, vision | -| Nebius AI | `nebius/Qwen/Qwen2-VL-7B-Instruct` | 131K | $0.02 | $0.06 | chat, vision | -| Nebius AI | `nebius/meta-llama/Meta-Llama-3.1-405B-Instruct` | 128K | $1.00 | $3.00 | chat | -| Nebius AI | `nebius/meta-llama/Meta-Llama-3.1-70B-Instruct` | 128K | $0.13 | $0.40 | chat | -| Nebius AI | `nebius/meta-llama/Meta-Llama-3.1-8B-Instruct` | 128K | $0.02 | $0.06 | chat | -| Nebius AI | `nebius/meta-llama/Llama-3.3-70B-Instruct` | 128K | $0.13 | $0.40 | chat | -| Nebius AI | `nebius/meta-llama/Llama-Guard-3-8B` | 128K | $0.02 | $0.06 | chat | -| Nebius AI | `nebius/nvidia/Llama-3.1-Nemotron-Ultra-253B-v1` | 128K | $0.60 | $1.80 | chat | -| Nebius AI | `nebius/nvidia/Llama-3.3-Nemotron-Super-49B-v1` | 131K | $0.10 | $0.40 | chat | -| Nebius AI | `nebius/NousResearch/Hermes-3-Llama-3.1-405B` | 128K | $1.00 | $3.00 | chat | -| Nebius AI | `nebius/google/gemma-3-27b-it` | 128K | $0.06 | $0.20 | chat | -| Nebius AI | `nebius/mistralai/Mistral-Nemo-Instruct-2407` | 128K | $0.04 | $0.12 | chat | -| Nebius AI | `nebius/Qwen/Qwen2.5-Coder-7B` | 32K | $0.01 | $0.03 | chat | -| Nebius AI | `nebius/BAAI/bge-en-icl` | 32K | $0.01 | - | embeddings | -| Nebius AI | `nebius/BAAI/bge-multilingual-gemma2` | 8K | $0.01 | - | embeddings | -| Nebius AI | `nebius/intfloat/e5-mistral-7b-instruct` | 32K | $0.01 | - | embeddings | -| AWS Bedrock | `mistral.devstral-2-123b` | 256K | $0.40 | $2.00 | chat, tools | -| AWS Bedrock | `zai.glm-4.7-flash` | 200K | $0.07 | $0.40 | chat, tools, reasoning | -| ZAI | `zai/glm-5` | 200K | $1.00 | $3.20 | chat, tools, reasoning | -| ZAI | `zai/glm-5-code` | 200K | $1.20 | $5.00 | chat, tools, reasoning | -| OpenRouter | `openrouter/anthropic/claude-sonnet-4.6` | - | - | - | chat | -| OpenRouter | `openrouter/google/gemini-3.1-pro-preview` | - | - | - | chat | -| OpenRouter | `openrouter/openai/gpt-5.1-codex-max` | - | - | - | chat | -| OpenRouter | `openrouter/qwen/qwen3-coder-plus` | - | - | - | chat | -| OpenRouter | `openrouter/qwen/qwen3.5-*` (5 models) | - | - | - | chat | -| OpenRouter | `openrouter/z-ai/glm-5` | - | - | - | chat | -| Together AI | `together_ai/Qwen/Qwen3.5-397B-A17B` | - | - | - | chat | -| Perplexity | `perplexity/pplx-embed-v1-0.6b` | 32K | $0.00 | - | embeddings | -| Perplexity | `perplexity/pplx-embed-v1-4b` | 32K | $0.03 | - | embeddings | -| Serper | `serper/search` | - | - | - | search | - -#### Updated Models - -- **[AWS Bedrock](../../docs/providers/bedrock)** - - Add `cache_read_input_token_cost` and `cache_creation_input_token_cost` to Bedrock-hosted Anthropic models (`claude-3-opus`, `claude-3-sonnet`, `claude-3-haiku`, and APAC/EU variants) — prompt caching is now tracked for cost estimation - - Rename `apac.anthropic.claude-sonnet-4-6` → `au.anthropic.claude-sonnet-4-6` to reflect correct regional identifier - -- **[Azure OpenAI](../../docs/providers/azure)** - - Add `supports_none_reasoning_effort` to all `gpt-5.1-chat`, `gpt-5.1-codex`, and `gpt-5.4` variants (global, EU, standard deployments) — allows passing `reasoning_effort: null` to disable reasoning - -- **[Azure OpenAI](../../docs/providers/azure)** — Removed deprecated models - - Remove `azure/gpt-35-turbo-0301` (deprecated 2025-02-13) - - Remove `azure/gpt-35-turbo-0613` (deprecated 2025-02-13) - -#### Features - -- **[OpenAI](../../docs/providers/openai)** - - Day 0 support for `gpt-5.4` and `gpt-5.4-pro` on OpenAI and Azure - -- **[Google Gemini](../../docs/providers/gemini)** - - Add Gemini 3.x model cost map entries — `gemini-3-flash-preview`, `gemini-3.1-pro-preview`, `gemini-3.1-flash-lite-preview`, `gemini-3-pro-image-preview`, `gemini-embedding-2-preview` - - Add Gemini 2.0 Flash and Flash Lite to cost map (re-added with updated pricing) - -- **[Google Vertex AI](../../docs/providers/vertex)** - - Add `gemini-3-flash-preview`, `gemini-3.1-flash-lite-preview`, `gemini-flash-experimental`, and `gemini-embedding-2-preview` to Vertex AI model cost map - -- **[Mistral](../../docs/providers/mistral)** - - Add Magistral reasoning models (`magistral-medium-1-2-2509`, `magistral-small-1-2-2509`) - - Add `mistral-large-2512`, `mistral-medium-3-1-2508`, `mistral-small-3-2-2506`, `ministral-3-*` variants - -- **[Dashscope / Qwen](../../docs/providers/dashscope)** - - Add Qwen3 VL multimodal models (`qwen3-vl-235b`, `qwen3-vl-32b` — instruct and thinking variants) - - Add `qwen3-next-80b-a3b` (instruct + thinking), `qwen3.5-plus`, `qwen3-max-2026-01-23` - -- **[Black Forest Labs](../../docs/providers/black_forest_labs)** - - Add FLUX Kontext image editing models (`flux-kontext-pro`, `flux-kontext-max`) - - Add FLUX Pro 1.0 Fill (inpainting) and Expand (outpainting) - - Add `flux-pro-1.1`, `flux-pro-1.1-ultra`, `flux-dev`, `flux-pro` - -- **[Azure AI](../../docs/providers/azure_ai)** - - Add xAI Grok models via Azure AI Foundry (`grok-4-1-fast-non-reasoning`, `grok-4-1-fast-reasoning`) - - Add Mistral Document AI (`mistral-document-ai-2512`) — OCR mode - -- **[AWS Bedrock](../../docs/providers/bedrock)** - - Add `mistral.devstral-2-123b` (256K context, tools) - - Add `zai.glm-4.7-flash` via Bedrock Converse (200K context, tools, reasoning) - -- **[SageMaker](../../docs/providers/aws_sagemaker)** - - Add `sagemaker_nova` provider for Amazon Nova models on SageMaker - [PR #21542](https://github.com/BerriAI/litellm/pull/21542) - -#### Deprecated / Removed Models - -**OpenAI** — Legacy models removed from cost map: -- `gpt-3.5-turbo-0301`, `gpt-3.5-turbo-0613`, `gpt-3.5-turbo-16k-0613` -- `gpt-4-0314`, `gpt-4-32k`, `gpt-4-32k-0314`, `gpt-4-32k-0613`, `gpt-4-1106-vision-preview`, `gpt-4-vision-preview` -- `gpt-4.5-preview`, `gpt-4.5-preview-2025-02-27` -- `gpt-4o-audio-preview-2024-10-01`, `gpt-4o-realtime-preview-2024-10-01` -- `o1-mini`, `o1-mini-2024-09-12`, `o1-preview`, `o1-preview-2024-09-12` - -**Google Gemini** — Gemini 1.5 and legacy 2.0 variants removed: -- All `gemini-1.5-*` variants (flash, flash-8b, pro, and dated versions) -- `gemini-2.0-flash-exp`, `gemini-2.0-pro-exp-02-05`, `gemini-2.5-flash-preview-04-17`, `gemini-2.5-flash-preview-05-20` - -**Google Vertex AI** — PaLM 2 / legacy models removed: -- All `chat-bison`, `text-bison`, `codechat-bison`, `code-bison`, `code-gecko` variants -- Gemini 1.0 Pro, 1.5 Flash/Pro, 2.0 Flash experimental, and preview variants - -**Perplexity** — Legacy Llama-sonar models removed: -- `llama-3.1-sonar-huge-128k-online`, `llama-3.1-sonar-large/small-128k-chat/online` - ---- - -## LLM API Endpoints - -#### Features - -- **[Responses API](../../docs/response_api)** - - Handle `response.failed`, `response.incomplete`, and `response.cancelled` terminal event types in background streaming — previously only `response.completed` was handled - [PR #23492](https://github.com/BerriAI/litellm/pull/23492) - -#### Bug Fixes - -- **[Anthropic](../../docs/providers/anthropic)** - - Preserve native tool format (web_search, bash, tool_search, etc.) when guardrails convert tools for the Anthropic Messages API - [PR #23526](https://github.com/BerriAI/litellm/pull/23526) - -- **[Moonshot / Kimi](../../docs/providers/openai_compatible)** - - Auto-fill `reasoning_content` for Moonshot Kimi reasoning models - [PR #23580](https://github.com/BerriAI/litellm/pull/23580) - -- **[HuggingFace](../../docs/providers/huggingface)** - - Forward `extra_headers` to HuggingFace embedding API - [PR #23525](https://github.com/BerriAI/litellm/pull/23525) - -- **General** - - Normalize `content_filtered` finish reason across providers - [PR #23564](https://github.com/BerriAI/litellm/pull/23564) - - Fix custom cost tracking on deployments for `/v1/messages` and `/v1/responses` - [PR #23647](https://github.com/BerriAI/litellm/pull/23647) - - Fix per-request custom pricing when `router_model_id` has no pricing data — now falls back to model name - ---- - -## Management Endpoints / UI - -#### Features - -- **Virtual Keys** - - Add Organization dropdown to Create/Edit Key form — `organization_id` is now a first-class field in Key Ownership - [PR #23595](https://github.com/BerriAI/litellm/pull/23595) - - Allow setting `organization_id` on `/key/update` — keys can be assigned or moved to a different organization after creation - [PR #23557](https://github.com/BerriAI/litellm/pull/23557) - -- **Internal Users** - - Add/Remove Team Membership directly from the Internal Users info page — includes searchable dropdown and role selector; no longer requires navigating to each team - [PR #23638](https://github.com/BerriAI/litellm/pull/23638) - -- **Default Team Settings** - - Modernize page to antd (consistent with rest of app) - [PR #23614](https://github.com/BerriAI/litellm/pull/23614) - - Fix: default team params (budget, duration, tpm, rpm, permissions) now correctly applied on `/team/new` - [PR #23614](https://github.com/BerriAI/litellm/pull/23614) - - Fix: settings persist across proxy restarts (`default_team_params` added to `LITELLM_SETTINGS_SAFE_DB_OVERRIDES`) - [PR #23614](https://github.com/BerriAI/litellm/pull/23614) - - Fix: resolved race condition in `_update_litellm_setting` where `get_config()` could overwrite freshly saved values - [PR #23614](https://github.com/BerriAI/litellm/pull/23614) - -- **Usage** - - Auto-paginate daily spend data — all entity views (teams, orgs, customers, tags, agents, users) fetch pages progressively with charts updating after each page - [PR #23622](https://github.com/BerriAI/litellm/pull/23622) - -- **Models / Cost** - - Azure Model Router cost breakdown in UI — show per-sub-model `additional_costs` from `hidden_params` in `CostBreakdownViewer` - [PR #23550](https://github.com/BerriAI/litellm/pull/23550) - -- **User Management** - - New `/user/info/v2` endpoint — scoped, paginated replacement for the existing god endpoint that caused memory and stability issues on large installs - [PR #23437](https://github.com/BerriAI/litellm/pull/23437) - -#### Bugs - -- Fix Tag list endpoint returning 500 due to invalid Prisma `group_by` kwargs - [PR #23606](https://github.com/BerriAI/litellm/pull/23606) -- Fix Team Admin getting 403 on `/user/filter/ui` when `scope_user_search_to_org` is enabled - [PR #23671](https://github.com/BerriAI/litellm/pull/23671) -- Fix Public Model Hub not showing config-defined models after save - [PR #23501](https://github.com/BerriAI/litellm/pull/23501) -- Fix fallback popup model dropdown z-index issue - [PR #23516](https://github.com/BerriAI/litellm/pull/23516) -- Fix double-counting bug in org/team key limit checks on `/key/update` - ---- - -## AI Integrations - -### Logging - -- **[Vantage](https://vantage.sh)** - - Add Vantage integration for FOCUS 1.2 CSV export — export LiteLLM proxy spend data as FinOps Open Cost & Usage Specification reports, with time-windowed filenames to prevent overwrites - [PR #23333](https://github.com/BerriAI/litellm/pull/23333) - -- **General** - - Fix silent metrics race condition causing metric collision across experiments - [PR #23542](https://github.com/BerriAI/litellm/pull/23542) - -### Guardrails - -No major guardrail changes in this release. - -### Prompt Management - -No major prompt management changes in this release. - -### Secret Managers - -No major secret manager changes in this release. - ---- - -## Performance / Loadbalancing / Reliability improvements - -- **Fix streaming crashes after ~1 hour** — `LLMClientCache._remove_key()` no longer calls `close()`/`aclose()` on evicted HTTP/SDK clients. In-flight requests were crashing with `RuntimeError: Cannot send a request, as the client has been closed.` after the 1-hour TTL expired. Cleanup now happens only at shutdown via `close_litellm_async_clients()` - [PR #22926](https://github.com/BerriAI/litellm/pull/22926) -- **Fix OOM / Prisma connection loss** on large installs — unbounded managed-object poll was exhausting Prisma connections after ~60–70 minutes on instances with 336K+ queued response rows - [PR #23472](https://github.com/BerriAI/litellm/pull/23472) -- **Centralize logging kwarg updates** — root cause fix migrating all logging updates to a single function, eliminating kwarg inconsistencies across logging paths - [PR #23659](https://github.com/BerriAI/litellm/pull/23659) -- **Fix tiktoken cache for non-root offline containers** — tiktoken cache now works correctly in offline environments running as non-root users - [PR #23498](https://github.com/BerriAI/litellm/pull/23498) -- **Add CodSpeed continuous performance benchmarks** — automated performance regression tracking on CI - [PR #23676](https://github.com/BerriAI/litellm/pull/23676) - ---- - -## Security - -- **Secret redaction in proxy logs** — Adds a `SecretRedactionFilter` to all LiteLLM loggers that scrubs API keys, tokens, and credentials from log messages, format args, exception tracebacks, and extra fields. Enabled by default; opt out with `LITELLM_DISABLE_REDACT_SECRETS=true` - [PR #23668](https://github.com/BerriAI/litellm/pull/23668), [PR #23667](https://github.com/BerriAI/litellm/pull/23667) -- **Bump PyJWT to `^2.12.0`** — addresses security vulnerability in `^2.10.1` - [PR #23678](https://github.com/BerriAI/litellm/pull/23678) -- **Bump `tar` to 7.5.11 and `tornado` to 6.5.5** — addresses CVEs in transitive dependencies - [PR #23602](https://github.com/BerriAI/litellm/pull/23602) - ---- - -## Database / Proxy Operations - -- **Fix Prisma migrate deploy on pre-existing instances** — resolved multiple bugs in migration recovery logic: missing return in the P3018 idempotent error handler and unhandled exceptions in `_roll_back_migration` that caused silent failures even after successful recovery - [PR #23655](https://github.com/BerriAI/litellm/pull/23655) -- **Make DB migration failure exit opt-in** — proxy no longer exits on `prisma migrate deploy` failure by default; enable with `--enforce_prisma_migration_check` - [PR #23675](https://github.com/BerriAI/litellm/pull/23675) - ---- - -## New Contributors - -* @ryanh-ai made their first contribution in [PR #21542](https://github.com/BerriAI/litellm/pull/21542) -* @ryan-crabbe made their first contribution in [PR #23668](https://github.com/BerriAI/litellm/pull/23668) -* @Jah-yee made their first contribution in [PR #23525](https://github.com/BerriAI/litellm/pull/23525) -* @gambletan made their first contribution in [PR #23516](https://github.com/BerriAI/litellm/pull/23516) -* @awais786 made their first contribution in [PR #23183](https://github.com/BerriAI/litellm/pull/23183) -* @pradyyadav made their first contribution in [PR #23580](https://github.com/BerriAI/litellm/pull/23580) -* @xianzongxie-stripe made their first contribution in [PR #23492](https://github.com/BerriAI/litellm/pull/23492) -* @Harshit28j made their first contribution in [PR #23333](https://github.com/BerriAI/litellm/pull/23333) -* @codspeed-hq[bot] made their first contribution in [PR #23676](https://github.com/BerriAI/litellm/pull/23676) - ---- - -## Diff Summary - -## 03/16/2026 -* New Providers: 5 -* New Models / Updated Models: 116 new, 132 removed -* LLM API Endpoints: 5 -* Management Endpoints / UI: 11 -* AI Integrations: 2 -* Performance / Reliability: 5 -* Security: 3 -* Database / Proxy Operations: 2 - ---- - -## Full Changelog -[v1.82.0-stable...v1.82.3-stable](https://github.com/BerriAI/litellm/compare/v1.82.0-stable...v1.82.3-stable) diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index 473edb5a6b2..2f3302bb574 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -471,7 +471,9 @@ "audio_speech": false, "moderations": false, "batches": false, - "rerank": false + "rerank": false, + "a2a": false, + "interactions": false } }, "chutes": { @@ -1484,12 +1486,12 @@ } }, "nebius": { - "display_name": "Nebius AI (`nebius`)", + "display_name": "Nebius AI Studio (`nebius`)", "url": "https://docs.litellm.ai/docs/providers/nebius", "endpoints": { "chat_completions": true, - "messages": false, - "responses": false, + "messages": true, + "responses": true, "embeddings": true, "image_generations": false, "audio_transcriptions": false, @@ -1497,7 +1499,8 @@ "moderations": false, "batches": false, "rerank": false, - "a2a": false + "a2a": true, + "interactions": true } }, "nlp_cloud": { @@ -2078,20 +2081,9 @@ }, "serper": { "display_name": "Serper (`serper`)", - "url": "https://docs.litellm.ai/docs/providers/serper", + "url": "https://docs.litellm.ai/docs/search/serper", "endpoints": { - "chat_completions": false, - "messages": false, - "responses": false, - "embeddings": false, - "image_generations": false, - "audio_transcriptions": false, - "audio_speech": false, - "moderations": false, - "batches": false, - "rerank": false, - "search": true, - "a2a": false + "search": true } }, "triton": { @@ -2273,12 +2265,12 @@ } }, "zai": { - "display_name": "ZAI (`zai`)", - "url": "https://docs.litellm.ai/docs/providers/openai_compatible", + "display_name": "Z.AI (Zhipu AI) (`zai`)", + "url": "https://docs.litellm.ai/docs/providers/zai", "endpoints": { "chat_completions": true, - "messages": false, - "responses": false, + "messages": true, + "responses": true, "embeddings": false, "image_generations": false, "audio_transcriptions": false, @@ -2286,7 +2278,8 @@ "moderations": false, "batches": false, "rerank": false, - "a2a": false + "a2a": true, + "interactions": true } }, "ragflow": { @@ -2558,25 +2551,23 @@ "audio_speech": false, "moderations": false, "batches": false, - "rerank": false, - "a2a": false + "rerank": false } }, - "sagemaker_nova": { - "display_name": "AWS SageMaker Nova (`sagemaker_nova`)", - "url": "https://docs.litellm.ai/docs/providers/aws_sagemaker", + "charity_engine": { + "display_name": "Charity Engine (`charity_engine`)", + "url": "https://docs.litellm.ai/docs/providers/charity_engine", "endpoints": { "chat_completions": true, - "messages": false, - "responses": false, + "messages": true, + "responses": true, "embeddings": false, "image_generations": false, "audio_transcriptions": false, "audio_speech": false, "moderations": false, "batches": false, - "rerank": false, - "a2a": false + "rerank": false } } }, From 0b0fe7e26374b5473bdfe67c76eb952828c26ce4 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 16 Mar 2026 22:26:53 -0700 Subject: [PATCH 32/60] [Fix] Rename toggle label to "Disable custom Virtual key values" Co-Authored-By: Claude Opus 4.6 --- .../Settings/AdminSettings/UISettings/UISettings.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx index 0d12a4c4bf5..f3eb6abdec4 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx @@ -397,17 +397,17 @@ export default function UISettings() { - {/* Disable custom API key values */} + {/* Disable custom Virtual key values */} - Disable custom API key values + Disable custom Virtual key values {disableCustomApiKeysProperty?.description ?? "If true, users cannot specify custom API key values. All keys must be auto-generated."} From 471e0f147e3a19da4c55e7b54a92c4023a07f49c Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 16 Mar 2026 22:35:44 -0700 Subject: [PATCH 33/60] [Fix] Remove "API" from custom key description text Co-Authored-By: Claude Opus 4.6 --- litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py | 2 +- .../components/Settings/AdminSettings/UISettings/UISettings.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py index 9faadd334a9..60bf41709ef 100644 --- a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py +++ b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py @@ -131,7 +131,7 @@ class UISettings(BaseModel): disable_custom_api_keys: bool = Field( default=False, - description="If true, users cannot specify custom API key values. All keys must be auto-generated.", + description="If true, users cannot specify custom key values. All keys must be auto-generated.", ) diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx index f3eb6abdec4..fb7c38449b0 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx @@ -410,7 +410,7 @@ export default function UISettings() { Disable custom Virtual key values {disableCustomApiKeysProperty?.description ?? - "If true, users cannot specify custom API key values. All keys must be auto-generated."} + "If true, users cannot specify custom key values. All keys must be auto-generated."} From c098eca509ef5f0393ac4e33bc9e5ae2eeb283f8 Mon Sep 17 00:00:00 2001 From: Ryan Crabbe Date: Mon, 16 Mar 2026 22:31:53 -0700 Subject: [PATCH 34/60] fix(ui): CSV export empty on Global Usage page Aggregated endpoint returns empty breakdown.entities; fall back to grouping breakdown.api_keys by team_id. --- .../EntityUsageExport/utils.test.ts | 134 ++++++++++++++++++ .../src/components/EntityUsageExport/utils.ts | 53 ++++++- 2 files changed, 182 insertions(+), 5 deletions(-) diff --git a/ui/litellm-dashboard/src/components/EntityUsageExport/utils.test.ts b/ui/litellm-dashboard/src/components/EntityUsageExport/utils.test.ts index 67de4b69174..2ca7ad7ef31 100644 --- a/ui/litellm-dashboard/src/components/EntityUsageExport/utils.test.ts +++ b/ui/litellm-dashboard/src/components/EntityUsageExport/utils.test.ts @@ -11,6 +11,7 @@ import { getEntityBreakdown, handleExportCSV, handleExportJSON, + resolveEntities, } from "./utils"; vi.mock("@/utils/dataUtils", () => ({ @@ -1561,4 +1562,137 @@ describe("EntityUsageExport utils", () => { window.Blob = originalBlob; }); }); + + describe("resolveEntities and aggregated endpoint fallback", () => { + // Simulates the response from /user/daily/activity/aggregated which has + // empty entities but populated api_keys at the breakdown level. + // Derived from mockSpendData: flatten all entities' api_key_breakdowns + // into top-level api_keys, clear entities, and add a second key for team-1 + // to test multi-key grouping. + const aggregatedSpendData: EntitySpendData = { + ...mockSpendData, + results: mockSpendData.results.slice(0, 1).map((day) => ({ + ...day, + breakdown: { + entities: {}, + api_keys: { + ...Object.fromEntries( + Object.values(day.breakdown.entities as Record).flatMap((e: any) => + Object.entries(e.api_key_breakdown || {}), + ), + ), + // Extra key on team-1 to test multi-key-per-team aggregation + key1b: { + metrics: { spend: 5, api_requests: 50, successful_requests: 48, failed_requests: 2, total_tokens: 500 }, + metadata: { team_id: "team-1", key_alias: "staging-key" }, + }, + }, + models: { "gpt-4": { metrics: { spend: 35, api_requests: 350, total_tokens: 3500 } } }, + }, + })), + }; + + describe("resolveEntities", () => { + it("should return entities when populated", () => { + const breakdown = { + entities: { e1: { metrics: { spend: 1 } } }, + api_keys: { k1: { metrics: { spend: 2 }, metadata: { team_id: "t1" } } }, + }; + const result = resolveEntities(breakdown); + expect(result).toBe(breakdown.entities); + }); + + it("should aggregate api_keys into entities when entities is empty", () => { + const breakdown = aggregatedSpendData.results[0].breakdown; + const result = resolveEntities(breakdown); + + // Two teams: team-1 (key1+key2) and team-2 (key3) + expect(Object.keys(result)).toHaveLength(2); + expect(result["team-1"]).toBeDefined(); + expect(result["team-2"]).toBeDefined(); + + // team-1 spend = 10.5 (key1) + 5 (key1b) + expect(result["team-1"].metrics.spend).toBe(15.5); + expect(result["team-1"].metrics.api_requests).toBe(150); + expect(result["team-1"].metrics.total_tokens).toBe(1500); + + // team-2 spend = 20.3 (key2) + expect(result["team-2"].metrics.spend).toBe(20.3); + expect(result["team-2"].metrics.api_requests).toBe(200); + }); + + it("should use 'Unassigned' for keys without team_id", () => { + const breakdown = { + entities: {}, + api_keys: { + k1: { + metrics: { spend: 7, api_requests: 10, successful_requests: 10, failed_requests: 0, total_tokens: 100 }, + metadata: {}, + }, + }, + }; + const result = resolveEntities(breakdown); + expect(result["Unassigned"]).toBeDefined(); + expect(result["Unassigned"].metrics.spend).toBe(7); + }); + + it("should handle missing or empty api_keys gracefully", () => { + expect(Object.keys(resolveEntities({ entities: {}, api_keys: {} }))).toHaveLength(0); + expect(Object.keys(resolveEntities({ entities: {} }))).toHaveLength(0); + }); + + it("should preserve api_key_breakdown on aggregated entities", () => { + const breakdown = aggregatedSpendData.results[0].breakdown; + const result = resolveEntities(breakdown); + + // team-1 should have key1 and key1b in api_key_breakdown + expect(Object.keys(result["team-1"].api_key_breakdown)).toEqual(["key1", "key1b"]); + // team-2 should have key2 + expect(Object.keys(result["team-2"].api_key_breakdown)).toEqual(["key2"]); + }); + }); + + describe("getEntityBreakdown with aggregated data", () => { + it("should produce breakdown from api_keys when entities is empty", () => { + const result = getEntityBreakdown(aggregatedSpendData); + expect(result.length).toBeGreaterThan(0); + + // Sorted by spend desc: team-2 (20.3) then team-1 (15.5) + expect(result[0].metrics.spend).toBe(20.3); + expect(result[1].metrics.spend).toBe(15.5); + }); + + }); + + describe("generateDailyData with aggregated data", () => { + it("should produce rows from api_keys when entities is empty", () => { + const result = generateDailyData(aggregatedSpendData, "Team"); + expect(result.length).toBeGreaterThan(0); + expect(result[0]).toHaveProperty("Date"); + expect(result[0]).toHaveProperty("Team"); + }); + }); + + describe("generateDailyWithKeysData with aggregated data", () => { + it("should produce rows from api_keys when entities is empty", () => { + const result = generateDailyWithKeysData(aggregatedSpendData, "Team"); + expect(result.length).toBeGreaterThan(0); + + // Should have 3 key rows (key1, key1b, key2) + expect(result).toHaveLength(3); + const keyIds = result.map((r) => r["Key ID"]); + expect(keyIds).toContain("key1"); + expect(keyIds).toContain("key1b"); + expect(keyIds).toContain("key2"); + }); + }); + + describe("generateDailyWithModelsData with aggregated data", () => { + it("should produce rows from api_keys when entities is empty", () => { + const result = generateDailyWithModelsData(aggregatedSpendData, "Team"); + expect(result.length).toBeGreaterThan(0); + expect(result[0]).toHaveProperty("Model"); + }); + }); + }); }); diff --git a/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts b/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts index ebef3da8a77..45bf21a6e7d 100644 --- a/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts +++ b/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts @@ -17,6 +17,49 @@ const extractTeamIdFromApiKeyBreakdown = (apiKeyBreakdown: Record | return null; }; +// Mirrors backend SpendMetrics fields (litellm/types/activity_tracking.py). +// If the backend adds a field, add it here too. +const METRIC_KEYS = [ + "spend", "api_requests", "successful_requests", "failed_requests", + "total_tokens", "prompt_tokens", "completion_tokens", + "cache_read_input_tokens", "cache_creation_input_tokens", +] as const; + +// When breakdown.entities is empty (aggregated endpoint), reconstruct entities +// from breakdown.api_keys by grouping on metadata.team_id. +const aggregateApiKeysIntoEntities = (breakdown: Record): Record => { + const apiKeys = breakdown.api_keys; + if (!apiKeys || Object.keys(apiKeys).length === 0) return {}; + + const grouped: Record = {}; + + for (const [keyId, keyData] of Object.entries(apiKeys)) { + const teamId = keyData?.metadata?.team_id || "Unassigned"; + if (!grouped[teamId]) { + grouped[teamId] = { + metrics: Object.fromEntries(METRIC_KEYS.map((k) => [k, 0])), + api_key_breakdown: {}, + }; + } + const m = grouped[teamId].metrics; + const km = keyData?.metrics || {}; + for (const k of METRIC_KEYS) { + m[k] += km[k] || 0; + } + grouped[teamId].api_key_breakdown[keyId] = keyData; + } + + return grouped; +}; + +// Returns breakdown.entities if populated, otherwise falls back to +// reconstructing entities from breakdown.api_keys. +export const resolveEntities = (breakdown: Record): Record => { + const entities = breakdown.entities; + if (entities && Object.keys(entities).length > 0) return entities; + return aggregateApiKeysIntoEntities(breakdown); +}; + export const getEntityBreakdown = ( spendData: EntitySpendData, teamAliasMap: Record = {}, @@ -24,7 +67,7 @@ export const getEntityBreakdown = ( const entitySpend: { [key: string]: EntityBreakdown } = {}; spendData.results.forEach((day) => { - Object.entries(day.breakdown.entities || {}).forEach(([entity, data]: [string, any]) => { + Object.entries(resolveEntities(day.breakdown)).forEach(([entity, data]: [string, any]) => { // Extract team_id from api_key_breakdown metadata (not data.metadata which is empty) const teamId = extractTeamIdFromApiKeyBreakdown(data.api_key_breakdown) || entity; // Extract key_alias from the first API key that has one @@ -80,7 +123,7 @@ export const generateDailyData = ( const dailyBreakdown: any[] = []; spendData.results.forEach((day) => { - Object.entries(day.breakdown.entities || {}).forEach(([entity, data]: [string, any]) => { + Object.entries(resolveEntities(day.breakdown)).forEach(([entity, data]: [string, any]) => { // Extract team_id from api_key_breakdown metadata (not data.metadata which is empty) const teamId = extractTeamIdFromApiKeyBreakdown(data.api_key_breakdown); const teamAlias = teamId ? teamAliasMap[teamId] || null : null; @@ -129,7 +172,7 @@ export const generateDailyWithKeysData = ( } = {}; spendData.results.forEach((day) => { - Object.entries(day.breakdown.entities || {}).forEach(([entity, data]: [string, any]) => { + Object.entries(resolveEntities(day.breakdown)).forEach(([entity, data]: [string, any]) => { const apiKeyBreakdown = data.api_key_breakdown || {}; // Iterate through each API key in the breakdown @@ -202,7 +245,7 @@ export const generateDailyWithModelsData = ( spendData.results.forEach((day) => { const dailyEntityModels: { [key: string]: { [key: string]: any } } = {}; - Object.entries(day.breakdown.entities || {}).forEach(([entity, entityData]: [string, any]) => { + Object.entries(resolveEntities(day.breakdown)).forEach(([entity, entityData]: [string, any]) => { if (!dailyEntityModels[entity]) { dailyEntityModels[entity] = {}; } @@ -230,7 +273,7 @@ export const generateDailyWithModelsData = ( }); Object.entries(dailyEntityModels).forEach(([entity, models]) => { - const entityData = day.breakdown.entities?.[entity]; + const entityData = resolveEntities(day.breakdown)[entity]; // Extract team_id from api_key_breakdown metadata (not entityData.metadata which is empty) const teamId = extractTeamIdFromApiKeyBreakdown(entityData?.api_key_breakdown); const teamAlias = teamId ? teamAliasMap[teamId] || null : null; From 7348a537e8f85ed1d4a949c3c7dff758ec504287 Mon Sep 17 00:00:00 2001 From: joereyna Date: Mon, 16 Mar 2026 22:41:22 -0700 Subject: [PATCH 35/60] docs: add v1.82.3 release notes and update provider_endpoints_support.json - Fix provider count header: 4 -> 5 new providers - Fix nebius/zai: add bridged endpoint support (messages, responses, a2a, interactions) - Add missing sagemaker_nova to provider_endpoints_support.json --- docs/my-website/release_notes/v1.82.3.md | 374 +++++++++++++++++++++++ provider_endpoints_support.json | 17 +- 2 files changed, 382 insertions(+), 9 deletions(-) create mode 100644 docs/my-website/release_notes/v1.82.3.md diff --git a/docs/my-website/release_notes/v1.82.3.md b/docs/my-website/release_notes/v1.82.3.md new file mode 100644 index 00000000000..73e5bbf1f12 --- /dev/null +++ b/docs/my-website/release_notes/v1.82.3.md @@ -0,0 +1,374 @@ +--- +title: "v1.82.3 - Nebius AI, gpt-5.4, Gemini 3.x, FLUX Kontext, and 116 New Models" +slug: "v1-82-3" +date: 2026-03-16T00:00:00 +authors: + - name: Krrish Dholakia + title: CEO, LiteLLM + url: https://www.linkedin.com/in/krish-d/ + image_url: https://pbs.twimg.com/profile_images/1298587542745358340/DZv3Oj-h_400x400.jpg + - name: Ishaan Jaff + title: CTO, LiteLLM + url: https://www.linkedin.com/in/reffajnaahsi/ + image_url: https://pbs.twimg.com/profile_images/1613813310264340481/lz54oEiB_400x400.jpg +hide_table_of_contents: false +--- + +## Deploy this version + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + +``` showLineNumbers title="docker run litellm" +docker run \ +-e STORE_MODEL_IN_DB=True \ +-p 4000:4000 \ +ghcr.io/berriai/litellm:main-1.82.3-stable +``` + + + + +``` showLineNumbers title="pip install litellm" +pip install litellm==1.82.3 +``` + + + + +## Key Highlights + +- **Nebius AI — new provider** — [30 models across DeepSeek, Qwen, Llama, Mistral, NVIDIA, and BAAI available via Nebius AI cloud](../../docs/providers/nebius) - [PR #21542](https://github.com/BerriAI/litellm/pull/21542) +- **OpenAI gpt-5.4 / gpt-5.4-pro — day 0** — Full pricing and routing support for `gpt-5.4` (1M context, $2.50/$15.00) and `gpt-5.4-pro` ($30.00/$180.00) on OpenAI and Azure +- **Gemini 3.x models** — `gemini-3-flash-preview`, `gemini-3.1-pro-preview`, `gemini-3.1-flash-image-preview`, and `gemini-embedding-2-preview` added to cost map for Google AI and Vertex AI +- **FLUX Kontext image editing** — `flux-kontext-pro` and `flux-kontext-max` added to Black Forest Labs, alongside `flux-pro-1.0-fill` and `flux-pro-1.0-expand` for inpainting and outpainting +- **116 new models, 132 deprecated models cleaned up** — Major model map refresh including Mistral Magistral, Dashscope Qwen3 VL, xAI Grok via Azure AI, ZAI GLM-5, Serper Search; removal of OpenAI GPT-3.5/GPT-4 legacy variants, Gemini 1.5, and Vertex AI PaLM2 +- **SageMaker Nova provider** — [New `sagemaker_nova` provider for Amazon Nova models on SageMaker](../../docs/providers/aws_sagemaker) - [PR #21542](https://github.com/BerriAI/litellm/pull/21542) +- **Secret redaction in logs** — API keys, tokens, and credentials automatically scrubbed from all proxy log output. Enabled by default; opt out with `LITELLM_DISABLE_REDACT_SECRETS=true` - [PR #23668](https://github.com/BerriAI/litellm/pull/23668) +- **Streaming stability fix** — Critical fix for `RuntimeError: Cannot send a request, as the client has been closed.` crashes after ~1 hour in production - [PR #22926](https://github.com/BerriAI/litellm/pull/22926) + +--- + +## New Providers and Endpoints + +### New Providers (5 new providers) + +| Provider | Supported LiteLLM Endpoints | Description | +| -------- | --------------------------- | ----------- | +| [Nebius AI](../../docs/providers/nebius) (`nebius/`) | `/chat/completions`, `/embeddings` | EU-based AI cloud with 30+ open models — DeepSeek, Qwen3, Llama 3.1/3.3, NVIDIA Nemotron, BAAI embeddings | +| [ZAI](../../docs/providers/openai_compatible) (`zai/`) | `/chat/completions` | ZhipuAI GLM-5 models via ZAI cloud | +| [Black Forest Labs](../../docs/providers/black_forest_labs) (`black_forest_labs/`) | `/images/generations`, `/images/edits` | FLUX image generation and editing — Kontext Pro/Max, Pro 1.0 Fill/Expand | +| [Serper](../../docs/providers/serper) (`serper/`) | `/search` | Web search via Serper API | +| [SageMaker Nova](../../docs/providers/aws_sagemaker) (`sagemaker_nova/`) | `/chat/completions` | Amazon Nova models via SageMaker endpoint | + +--- + +## New Models / Updated Models + +#### New Model Support (116 new models) + +| Provider | Model | Context Window | Input ($/1M tokens) | Output ($/1M tokens) | Features | +| -------- | ----- | -------------- | ------------------- | -------------------- | -------- | +| OpenAI | `gpt-5.4` | 1.05M | $2.50 | $15.00 | chat, vision, tools, reasoning | +| OpenAI | `gpt-5.4-pro` | 1.05M | $30.00 | $180.00 | responses, vision, tools, reasoning | +| OpenAI | `gpt-5.3-chat-latest` | 128K | $1.75 | $14.00 | chat, vision, tools, reasoning | +| Azure OpenAI | `azure/gpt-5.4` | 1.05M | $2.50 | $15.00 | chat, vision, tools, reasoning | +| Azure OpenAI | `azure/gpt-5.4-pro` | 1.05M | $30.00 | $180.00 | responses, vision, tools, reasoning | +| Azure OpenAI | `azure/gpt-5.3-chat` | 128K | $1.75 | $14.00 | chat, vision, tools, reasoning | +| Google Gemini | `gemini/gemini-3-flash-preview` | 1M | $0.50 | $3.00 | chat, vision, tools, reasoning | +| Google Gemini | `gemini/gemini-3.1-pro-preview` | 1M | $2.00 | $12.00 | chat, vision, tools, reasoning | +| Google Gemini | `gemini/gemini-3.1-flash-image-preview` | 65K | $0.25 | $1.50 | image generation, vision | +| Google Gemini | `gemini/gemini-3.1-flash-lite-preview` | - | - | - | chat | +| Google Gemini | `gemini/gemini-3-pro-image-preview` | - | - | - | image generation | +| Google Gemini | `gemini/gemini-embedding-2-preview` | 8K | $0.20 | - | embeddings | +| Google Vertex AI | `vertex_ai/gemini-3-flash-preview` | - | - | - | chat | +| Google Vertex AI | `vertex_ai/gemini-3.1-pro-preview` | - | - | - | chat | +| Google Vertex AI | `vertex_ai/gemini-3.1-flash-lite-preview` | - | - | - | chat | +| Google Vertex AI | `vertex_ai/gemini-embedding-2-preview` | - | $0.20 | - | embeddings | +| Mistral | `mistral/magistral-medium-1-2-2509` | 40K | $2.00 | $5.00 | chat, tools, reasoning | +| Mistral | `mistral/magistral-small-1-2-2509` | 40K | $0.50 | $1.50 | chat, tools, reasoning | +| Mistral | `mistral/mistral-large-2512` | 262K | $0.50 | $1.50 | chat, vision, tools | +| Mistral | `mistral/mistral-medium-3-1-2508` | - | - | - | chat | +| Mistral | `mistral/mistral-small-3-2-2506` | - | - | - | chat | +| Mistral | `mistral/ministral-3-3b-2512` | - | - | - | chat | +| Mistral | `mistral/ministral-3-8b-2512` | - | - | - | chat | +| Mistral | `mistral/ministral-3-14b-2512` | - | - | - | chat | +| Black Forest Labs | `black_forest_labs/flux-kontext-pro` | - | - | - | image editing | +| Black Forest Labs | `black_forest_labs/flux-kontext-max` | - | - | - | image editing | +| Black Forest Labs | `black_forest_labs/flux-pro-1.0-fill` | - | - | - | image editing (inpaint) | +| Black Forest Labs | `black_forest_labs/flux-pro-1.0-expand` | - | - | - | image editing (outpaint) | +| Black Forest Labs | `black_forest_labs/flux-pro-1.1` | - | - | - | image generation | +| Black Forest Labs | `black_forest_labs/flux-pro-1.1-ultra` | - | - | - | image generation | +| Black Forest Labs | `black_forest_labs/flux-dev` | - | - | - | image generation | +| Black Forest Labs | `black_forest_labs/flux-pro` | - | - | - | image generation | +| Azure AI | `azure_ai/grok-4-1-fast-non-reasoning` | 131K | $0.20 | $0.50 | chat, tools | +| Azure AI | `azure_ai/grok-4-1-fast-reasoning` | 131K | $0.20 | $0.50 | chat, tools, reasoning | +| Azure AI | `azure_ai/mistral-document-ai-2512` | - | - | - | OCR | +| Dashscope | `dashscope/qwen3-next-80b-a3b-instruct` | 262K | $0.15 | $1.20 | chat | +| Dashscope | `dashscope/qwen3-next-80b-a3b-thinking` | 262K | $0.15 | $1.20 | chat, reasoning | +| Dashscope | `dashscope/qwen3-vl-235b-a22b-instruct` | 131K | $0.40 | $1.60 | chat, vision | +| Dashscope | `dashscope/qwen3-vl-235b-a22b-thinking` | 131K | $0.40 | $4.00 | chat, vision, reasoning | +| Dashscope | `dashscope/qwen3-vl-32b-instruct` | 131K | $0.16 | $0.64 | chat, vision | +| Dashscope | `dashscope/qwen3-vl-32b-thinking` | 131K | $0.16 | $2.87 | chat, vision, reasoning | +| Dashscope | `dashscope/qwen3-vl-plus` | 260K | - | - | chat, vision | +| Dashscope | `dashscope/qwen3.5-plus` | 992K | - | - | chat | +| Dashscope | `dashscope/qwen3-max-2026-01-23` | 258K | - | - | chat | +| Nebius AI | `nebius/deepseek-ai/DeepSeek-R1` | 128K | $0.80 | $2.40 | chat, reasoning | +| Nebius AI | `nebius/deepseek-ai/DeepSeek-R1-0528` | 164K | $0.80 | $2.40 | chat, reasoning | +| Nebius AI | `nebius/deepseek-ai/DeepSeek-V3` | 128K | $0.50 | $1.50 | chat | +| Nebius AI | `nebius/deepseek-ai/DeepSeek-V3-0324` | 128K | $0.50 | $1.50 | chat | +| Nebius AI | `nebius/deepseek-ai/DeepSeek-R1-Distill-Llama-70B` | 128K | $0.25 | $0.75 | chat | +| Nebius AI | `nebius/Qwen/Qwen3-235B-A22B` | 262K | $0.20 | $0.60 | chat | +| Nebius AI | `nebius/Qwen/Qwen3-32B` | 32K | $0.10 | $0.30 | chat | +| Nebius AI | `nebius/Qwen/Qwen3-30B-A3B` | 32K | $0.10 | $0.30 | chat | +| Nebius AI | `nebius/Qwen/Qwen3-14B` | 32K | $0.08 | $0.24 | chat | +| Nebius AI | `nebius/Qwen/Qwen3-4B` | 32K | $0.08 | $0.24 | chat | +| Nebius AI | `nebius/Qwen/QwQ-32B` | 32K | $0.15 | $0.45 | chat | +| Nebius AI | `nebius/Qwen/Qwen2.5-72B-Instruct` | 128K | $0.13 | $0.40 | chat | +| Nebius AI | `nebius/Qwen/Qwen2.5-32B-Instruct` | 128K | $0.06 | $0.20 | chat | +| Nebius AI | `nebius/Qwen/Qwen2.5-VL-72B-Instruct` | 131K | $0.13 | $0.40 | chat, vision | +| Nebius AI | `nebius/Qwen/Qwen2-VL-72B-Instruct` | 131K | $0.13 | $0.40 | chat, vision | +| Nebius AI | `nebius/Qwen/Qwen2-VL-7B-Instruct` | 131K | $0.02 | $0.06 | chat, vision | +| Nebius AI | `nebius/meta-llama/Meta-Llama-3.1-405B-Instruct` | 128K | $1.00 | $3.00 | chat | +| Nebius AI | `nebius/meta-llama/Meta-Llama-3.1-70B-Instruct` | 128K | $0.13 | $0.40 | chat | +| Nebius AI | `nebius/meta-llama/Meta-Llama-3.1-8B-Instruct` | 128K | $0.02 | $0.06 | chat | +| Nebius AI | `nebius/meta-llama/Llama-3.3-70B-Instruct` | 128K | $0.13 | $0.40 | chat | +| Nebius AI | `nebius/meta-llama/Llama-Guard-3-8B` | 128K | $0.02 | $0.06 | chat | +| Nebius AI | `nebius/nvidia/Llama-3.1-Nemotron-Ultra-253B-v1` | 128K | $0.60 | $1.80 | chat | +| Nebius AI | `nebius/nvidia/Llama-3.3-Nemotron-Super-49B-v1` | 131K | $0.10 | $0.40 | chat | +| Nebius AI | `nebius/NousResearch/Hermes-3-Llama-3.1-405B` | 128K | $1.00 | $3.00 | chat | +| Nebius AI | `nebius/google/gemma-3-27b-it` | 128K | $0.06 | $0.20 | chat | +| Nebius AI | `nebius/mistralai/Mistral-Nemo-Instruct-2407` | 128K | $0.04 | $0.12 | chat | +| Nebius AI | `nebius/Qwen/Qwen2.5-Coder-7B` | 32K | $0.01 | $0.03 | chat | +| Nebius AI | `nebius/BAAI/bge-en-icl` | 32K | $0.01 | - | embeddings | +| Nebius AI | `nebius/BAAI/bge-multilingual-gemma2` | 8K | $0.01 | - | embeddings | +| Nebius AI | `nebius/intfloat/e5-mistral-7b-instruct` | 32K | $0.01 | - | embeddings | +| AWS Bedrock | `mistral.devstral-2-123b` | 256K | $0.40 | $2.00 | chat, tools | +| AWS Bedrock | `zai.glm-4.7-flash` | 200K | $0.07 | $0.40 | chat, tools, reasoning | +| ZAI | `zai/glm-5` | 200K | $1.00 | $3.20 | chat, tools, reasoning | +| ZAI | `zai/glm-5-code` | 200K | $1.20 | $5.00 | chat, tools, reasoning | +| OpenRouter | `openrouter/anthropic/claude-sonnet-4.6` | - | - | - | chat | +| OpenRouter | `openrouter/google/gemini-3.1-pro-preview` | - | - | - | chat | +| OpenRouter | `openrouter/openai/gpt-5.1-codex-max` | - | - | - | chat | +| OpenRouter | `openrouter/qwen/qwen3-coder-plus` | - | - | - | chat | +| OpenRouter | `openrouter/qwen/qwen3.5-*` (5 models) | - | - | - | chat | +| OpenRouter | `openrouter/z-ai/glm-5` | - | - | - | chat | +| Together AI | `together_ai/Qwen/Qwen3.5-397B-A17B` | - | - | - | chat | +| Perplexity | `perplexity/pplx-embed-v1-0.6b` | 32K | $0.00 | - | embeddings | +| Perplexity | `perplexity/pplx-embed-v1-4b` | 32K | $0.03 | - | embeddings | +| Serper | `serper/search` | - | - | - | search | + +#### Updated Models + +- **[AWS Bedrock](../../docs/providers/bedrock)** + - Add `cache_read_input_token_cost` and `cache_creation_input_token_cost` to Bedrock-hosted Anthropic models (`claude-3-opus`, `claude-3-sonnet`, `claude-3-haiku`, and APAC/EU variants) — prompt caching is now tracked for cost estimation + - Rename `apac.anthropic.claude-sonnet-4-6` → `au.anthropic.claude-sonnet-4-6` to reflect correct regional identifier + +- **[Azure OpenAI](../../docs/providers/azure)** + - Add `supports_none_reasoning_effort` to all `gpt-5.1-chat`, `gpt-5.1-codex`, and `gpt-5.4` variants (global, EU, standard deployments) — allows passing `reasoning_effort: null` to disable reasoning + +- **[Azure OpenAI](../../docs/providers/azure)** — Removed deprecated models + - Remove `azure/gpt-35-turbo-0301` (deprecated 2025-02-13) + - Remove `azure/gpt-35-turbo-0613` (deprecated 2025-02-13) + +#### Features + +- **[OpenAI](../../docs/providers/openai)** + - Day 0 support for `gpt-5.4` and `gpt-5.4-pro` on OpenAI and Azure + +- **[Google Gemini](../../docs/providers/gemini)** + - Add Gemini 3.x model cost map entries — `gemini-3-flash-preview`, `gemini-3.1-pro-preview`, `gemini-3.1-flash-lite-preview`, `gemini-3-pro-image-preview`, `gemini-embedding-2-preview` + - Add Gemini 2.0 Flash and Flash Lite to cost map (re-added with updated pricing) + +- **[Google Vertex AI](../../docs/providers/vertex)** + - Add `gemini-3-flash-preview`, `gemini-3.1-flash-lite-preview`, `gemini-flash-experimental`, and `gemini-embedding-2-preview` to Vertex AI model cost map + +- **[Mistral](../../docs/providers/mistral)** + - Add Magistral reasoning models (`magistral-medium-1-2-2509`, `magistral-small-1-2-2509`) + - Add `mistral-large-2512`, `mistral-medium-3-1-2508`, `mistral-small-3-2-2506`, `ministral-3-*` variants + +- **[Dashscope / Qwen](../../docs/providers/dashscope)** + - Add Qwen3 VL multimodal models (`qwen3-vl-235b`, `qwen3-vl-32b` — instruct and thinking variants) + - Add `qwen3-next-80b-a3b` (instruct + thinking), `qwen3.5-plus`, `qwen3-max-2026-01-23` + +- **[Black Forest Labs](../../docs/providers/black_forest_labs)** + - Add FLUX Kontext image editing models (`flux-kontext-pro`, `flux-kontext-max`) + - Add FLUX Pro 1.0 Fill (inpainting) and Expand (outpainting) + - Add `flux-pro-1.1`, `flux-pro-1.1-ultra`, `flux-dev`, `flux-pro` + +- **[Azure AI](../../docs/providers/azure_ai)** + - Add xAI Grok models via Azure AI Foundry (`grok-4-1-fast-non-reasoning`, `grok-4-1-fast-reasoning`) + - Add Mistral Document AI (`mistral-document-ai-2512`) — OCR mode + +- **[AWS Bedrock](../../docs/providers/bedrock)** + - Add `mistral.devstral-2-123b` (256K context, tools) + - Add `zai.glm-4.7-flash` via Bedrock Converse (200K context, tools, reasoning) + +- **[SageMaker](../../docs/providers/aws_sagemaker)** + - Add `sagemaker_nova` provider for Amazon Nova models on SageMaker - [PR #21542](https://github.com/BerriAI/litellm/pull/21542) + +#### Deprecated / Removed Models + +**OpenAI** — Legacy models removed from cost map: +- `gpt-3.5-turbo-0301`, `gpt-3.5-turbo-0613`, `gpt-3.5-turbo-16k-0613` +- `gpt-4-0314`, `gpt-4-32k`, `gpt-4-32k-0314`, `gpt-4-32k-0613`, `gpt-4-1106-vision-preview`, `gpt-4-vision-preview` +- `gpt-4.5-preview`, `gpt-4.5-preview-2025-02-27` +- `gpt-4o-audio-preview-2024-10-01`, `gpt-4o-realtime-preview-2024-10-01` +- `o1-mini`, `o1-mini-2024-09-12`, `o1-preview`, `o1-preview-2024-09-12` + +**Google Gemini** — Gemini 1.5 and legacy 2.0 variants removed: +- All `gemini-1.5-*` variants (flash, flash-8b, pro, and dated versions) +- `gemini-2.0-flash-exp`, `gemini-2.0-pro-exp-02-05`, `gemini-2.5-flash-preview-04-17`, `gemini-2.5-flash-preview-05-20` + +**Google Vertex AI** — PaLM 2 / legacy models removed: +- All `chat-bison`, `text-bison`, `codechat-bison`, `code-bison`, `code-gecko` variants +- Gemini 1.0 Pro, 1.5 Flash/Pro, 2.0 Flash experimental, and preview variants + +**Perplexity** — Legacy Llama-sonar models removed: +- `llama-3.1-sonar-huge-128k-online`, `llama-3.1-sonar-large/small-128k-chat/online` + +--- + +## LLM API Endpoints + +#### Features + +- **[Responses API](../../docs/response_api)** + - Handle `response.failed`, `response.incomplete`, and `response.cancelled` terminal event types in background streaming — previously only `response.completed` was handled - [PR #23492](https://github.com/BerriAI/litellm/pull/23492) + +#### Bug Fixes + +- **[Anthropic](../../docs/providers/anthropic)** + - Preserve native tool format (web_search, bash, tool_search, etc.) when guardrails convert tools for the Anthropic Messages API - [PR #23526](https://github.com/BerriAI/litellm/pull/23526) + +- **[Moonshot / Kimi](../../docs/providers/openai_compatible)** + - Auto-fill `reasoning_content` for Moonshot Kimi reasoning models - [PR #23580](https://github.com/BerriAI/litellm/pull/23580) + +- **[HuggingFace](../../docs/providers/huggingface)** + - Forward `extra_headers` to HuggingFace embedding API - [PR #23525](https://github.com/BerriAI/litellm/pull/23525) + +- **General** + - Normalize `content_filtered` finish reason across providers - [PR #23564](https://github.com/BerriAI/litellm/pull/23564) + - Fix custom cost tracking on deployments for `/v1/messages` and `/v1/responses` - [PR #23647](https://github.com/BerriAI/litellm/pull/23647) + - Fix per-request custom pricing when `router_model_id` has no pricing data — now falls back to model name + +--- + +## Management Endpoints / UI + +#### Features + +- **Virtual Keys** + - Add Organization dropdown to Create/Edit Key form — `organization_id` is now a first-class field in Key Ownership - [PR #23595](https://github.com/BerriAI/litellm/pull/23595) + - Allow setting `organization_id` on `/key/update` — keys can be assigned or moved to a different organization after creation - [PR #23557](https://github.com/BerriAI/litellm/pull/23557) + +- **Internal Users** + - Add/Remove Team Membership directly from the Internal Users info page — includes searchable dropdown and role selector; no longer requires navigating to each team - [PR #23638](https://github.com/BerriAI/litellm/pull/23638) + +- **Default Team Settings** + - Modernize page to antd (consistent with rest of app) - [PR #23614](https://github.com/BerriAI/litellm/pull/23614) + - Fix: default team params (budget, duration, tpm, rpm, permissions) now correctly applied on `/team/new` - [PR #23614](https://github.com/BerriAI/litellm/pull/23614) + - Fix: settings persist across proxy restarts (`default_team_params` added to `LITELLM_SETTINGS_SAFE_DB_OVERRIDES`) - [PR #23614](https://github.com/BerriAI/litellm/pull/23614) + - Fix: resolved race condition in `_update_litellm_setting` where `get_config()` could overwrite freshly saved values - [PR #23614](https://github.com/BerriAI/litellm/pull/23614) + +- **Usage** + - Auto-paginate daily spend data — all entity views (teams, orgs, customers, tags, agents, users) fetch pages progressively with charts updating after each page - [PR #23622](https://github.com/BerriAI/litellm/pull/23622) + +- **Models / Cost** + - Azure Model Router cost breakdown in UI — show per-sub-model `additional_costs` from `hidden_params` in `CostBreakdownViewer` - [PR #23550](https://github.com/BerriAI/litellm/pull/23550) + +- **User Management** + - New `/user/info/v2` endpoint — scoped, paginated replacement for the existing god endpoint that caused memory and stability issues on large installs - [PR #23437](https://github.com/BerriAI/litellm/pull/23437) + +#### Bugs + +- Fix Tag list endpoint returning 500 due to invalid Prisma `group_by` kwargs - [PR #23606](https://github.com/BerriAI/litellm/pull/23606) +- Fix Team Admin getting 403 on `/user/filter/ui` when `scope_user_search_to_org` is enabled - [PR #23671](https://github.com/BerriAI/litellm/pull/23671) +- Fix Public Model Hub not showing config-defined models after save - [PR #23501](https://github.com/BerriAI/litellm/pull/23501) +- Fix fallback popup model dropdown z-index issue - [PR #23516](https://github.com/BerriAI/litellm/pull/23516) +- Fix double-counting bug in org/team key limit checks on `/key/update` + +--- + +## AI Integrations + +### Logging + +- **[Vantage](https://vantage.sh)** + - Add Vantage integration for FOCUS 1.2 CSV export — export LiteLLM proxy spend data as FinOps Open Cost & Usage Specification reports, with time-windowed filenames to prevent overwrites - [PR #23333](https://github.com/BerriAI/litellm/pull/23333) + +- **General** + - Fix silent metrics race condition causing metric collision across experiments - [PR #23542](https://github.com/BerriAI/litellm/pull/23542) + +### Guardrails + +No major guardrail changes in this release. + +### Prompt Management + +No major prompt management changes in this release. + +### Secret Managers + +No major secret manager changes in this release. + +--- + +## Performance / Loadbalancing / Reliability improvements + +- **Fix streaming crashes after ~1 hour** — `LLMClientCache._remove_key()` no longer calls `close()`/`aclose()` on evicted HTTP/SDK clients. In-flight requests were crashing with `RuntimeError: Cannot send a request, as the client has been closed.` after the 1-hour TTL expired. Cleanup now happens only at shutdown via `close_litellm_async_clients()` - [PR #22926](https://github.com/BerriAI/litellm/pull/22926) +- **Fix OOM / Prisma connection loss** on large installs — unbounded managed-object poll was exhausting Prisma connections after ~60–70 minutes on instances with 336K+ queued response rows - [PR #23472](https://github.com/BerriAI/litellm/pull/23472) +- **Centralize logging kwarg updates** — root cause fix migrating all logging updates to a single function, eliminating kwarg inconsistencies across logging paths - [PR #23659](https://github.com/BerriAI/litellm/pull/23659) +- **Fix tiktoken cache for non-root offline containers** — tiktoken cache now works correctly in offline environments running as non-root users - [PR #23498](https://github.com/BerriAI/litellm/pull/23498) +- **Add CodSpeed continuous performance benchmarks** — automated performance regression tracking on CI - [PR #23676](https://github.com/BerriAI/litellm/pull/23676) + +--- + +## Security + +- **Secret redaction in proxy logs** — Adds a `SecretRedactionFilter` to all LiteLLM loggers that scrubs API keys, tokens, and credentials from log messages, format args, exception tracebacks, and extra fields. Enabled by default; opt out with `LITELLM_DISABLE_REDACT_SECRETS=true` - [PR #23668](https://github.com/BerriAI/litellm/pull/23668), [PR #23667](https://github.com/BerriAI/litellm/pull/23667) +- **Bump PyJWT to `^2.12.0`** — addresses security vulnerability in `^2.10.1` - [PR #23678](https://github.com/BerriAI/litellm/pull/23678) +- **Bump `tar` to 7.5.11 and `tornado` to 6.5.5** — addresses CVEs in transitive dependencies - [PR #23602](https://github.com/BerriAI/litellm/pull/23602) + +--- + +## Database / Proxy Operations + +- **Fix Prisma migrate deploy on pre-existing instances** — resolved multiple bugs in migration recovery logic: missing return in the P3018 idempotent error handler and unhandled exceptions in `_roll_back_migration` that caused silent failures even after successful recovery - [PR #23655](https://github.com/BerriAI/litellm/pull/23655) +- **Make DB migration failure exit opt-in** — proxy no longer exits on `prisma migrate deploy` failure by default; enable with `--enforce_prisma_migration_check` - [PR #23675](https://github.com/BerriAI/litellm/pull/23675) + +--- + +## New Contributors + +* @ryanh-ai made their first contribution in [PR #21542](https://github.com/BerriAI/litellm/pull/21542) +* @ryan-crabbe made their first contribution in [PR #23668](https://github.com/BerriAI/litellm/pull/23668) +* @Jah-yee made their first contribution in [PR #23525](https://github.com/BerriAI/litellm/pull/23525) +* @gambletan made their first contribution in [PR #23516](https://github.com/BerriAI/litellm/pull/23516) +* @awais786 made their first contribution in [PR #23183](https://github.com/BerriAI/litellm/pull/23183) +* @pradyyadav made their first contribution in [PR #23580](https://github.com/BerriAI/litellm/pull/23580) +* @xianzongxie-stripe made their first contribution in [PR #23492](https://github.com/BerriAI/litellm/pull/23492) +* @Harshit28j made their first contribution in [PR #23333](https://github.com/BerriAI/litellm/pull/23333) +* @codspeed-hq[bot] made their first contribution in [PR #23676](https://github.com/BerriAI/litellm/pull/23676) + +--- + +## Diff Summary + +## 03/16/2026 +* New Providers: 5 +* New Models / Updated Models: 116 new, 132 removed +* LLM API Endpoints: 5 +* Management Endpoints / UI: 11 +* AI Integrations: 2 +* Performance / Reliability: 5 +* Security: 3 +* Database / Proxy Operations: 2 + +--- + +## Full Changelog +[v1.82.0-stable...v1.82.3-stable](https://github.com/BerriAI/litellm/compare/v1.82.0-stable...v1.82.3-stable) diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index 2f3302bb574..bd677c5bb53 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -471,9 +471,7 @@ "audio_speech": false, "moderations": false, "batches": false, - "rerank": false, - "a2a": false, - "interactions": false + "rerank": false } }, "chutes": { @@ -2554,20 +2552,21 @@ "rerank": false } }, - "charity_engine": { - "display_name": "Charity Engine (`charity_engine`)", - "url": "https://docs.litellm.ai/docs/providers/charity_engine", + "sagemaker_nova": { + "display_name": "AWS SageMaker Nova (`sagemaker_nova`)", + "url": "https://docs.litellm.ai/docs/providers/aws_sagemaker", "endpoints": { "chat_completions": true, - "messages": true, - "responses": true, + "messages": false, + "responses": false, "embeddings": false, "image_generations": false, "audio_transcriptions": false, "audio_speech": false, "moderations": false, "batches": false, - "rerank": false + "rerank": false, + "a2a": false } } }, From 71db624bfb70c93cb1f502171e75ad70fcd8bd25 Mon Sep 17 00:00:00 2001 From: joereyna Date: Mon, 16 Mar 2026 22:51:07 -0700 Subject: [PATCH 36/60] fix: add interactions: false to sagemaker_nova provider entry --- provider_endpoints_support.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index bd677c5bb53..d2edb0017f7 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -2566,7 +2566,8 @@ "moderations": false, "batches": false, "rerank": false, - "a2a": false + "a2a": false, + "interactions": false } } }, From afd6c4c6246836110517bc5d37647359b9baef99 Mon Sep 17 00:00:00 2001 From: joereyna Date: Mon, 16 Mar 2026 22:53:01 -0700 Subject: [PATCH 37/60] fix: correct Nebius AI PR link in release notes (22614 not 21542) --- docs/my-website/release_notes/v1.82.3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/my-website/release_notes/v1.82.3.md b/docs/my-website/release_notes/v1.82.3.md index 73e5bbf1f12..44c5cfd85d4 100644 --- a/docs/my-website/release_notes/v1.82.3.md +++ b/docs/my-website/release_notes/v1.82.3.md @@ -41,7 +41,7 @@ pip install litellm==1.82.3 ## Key Highlights -- **Nebius AI — new provider** — [30 models across DeepSeek, Qwen, Llama, Mistral, NVIDIA, and BAAI available via Nebius AI cloud](../../docs/providers/nebius) - [PR #21542](https://github.com/BerriAI/litellm/pull/21542) +- **Nebius AI — new provider** — [30 models across DeepSeek, Qwen, Llama, Mistral, NVIDIA, and BAAI available via Nebius AI cloud](../../docs/providers/nebius) - [PR #22614](https://github.com/BerriAI/litellm/pull/22614) - **OpenAI gpt-5.4 / gpt-5.4-pro — day 0** — Full pricing and routing support for `gpt-5.4` (1M context, $2.50/$15.00) and `gpt-5.4-pro` ($30.00/$180.00) on OpenAI and Azure - **Gemini 3.x models** — `gemini-3-flash-preview`, `gemini-3.1-pro-preview`, `gemini-3.1-flash-image-preview`, and `gemini-embedding-2-preview` added to cost map for Google AI and Vertex AI - **FLUX Kontext image editing** — `flux-kontext-pro` and `flux-kontext-max` added to Black Forest Labs, alongside `flux-pro-1.0-fill` and `flux-pro-1.0-expand` for inpainting and outpainting From 1c90d92bf5acb4d5f90a565b56862272aa454316 Mon Sep 17 00:00:00 2001 From: joereyna Date: Mon, 16 Mar 2026 22:58:53 -0700 Subject: [PATCH 38/60] fix: update ZAI docs link to dedicated provider page --- docs/my-website/release_notes/v1.82.3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/my-website/release_notes/v1.82.3.md b/docs/my-website/release_notes/v1.82.3.md index 44c5cfd85d4..15df33fdb80 100644 --- a/docs/my-website/release_notes/v1.82.3.md +++ b/docs/my-website/release_notes/v1.82.3.md @@ -59,7 +59,7 @@ pip install litellm==1.82.3 | Provider | Supported LiteLLM Endpoints | Description | | -------- | --------------------------- | ----------- | | [Nebius AI](../../docs/providers/nebius) (`nebius/`) | `/chat/completions`, `/embeddings` | EU-based AI cloud with 30+ open models — DeepSeek, Qwen3, Llama 3.1/3.3, NVIDIA Nemotron, BAAI embeddings | -| [ZAI](../../docs/providers/openai_compatible) (`zai/`) | `/chat/completions` | ZhipuAI GLM-5 models via ZAI cloud | +| [ZAI](../../docs/providers/zai) (`zai/`) | `/chat/completions` | ZhipuAI GLM-5 models via ZAI cloud | | [Black Forest Labs](../../docs/providers/black_forest_labs) (`black_forest_labs/`) | `/images/generations`, `/images/edits` | FLUX image generation and editing — Kontext Pro/Max, Pro 1.0 Fill/Expand | | [Serper](../../docs/providers/serper) (`serper/`) | `/search` | Web search via Serper API | | [SageMaker Nova](../../docs/providers/aws_sagemaker) (`sagemaker_nova/`) | `/chat/completions` | Amazon Nova models via SageMaker endpoint | From ab4fda2eebbc6e1dbc772e93d4be8c18b58deb8c Mon Sep 17 00:00:00 2001 From: voidborne-d Date: Tue, 17 Mar 2026 08:08:44 +0000 Subject: [PATCH 39/60] fix: add asyncio.Lock to prevent session/connector leak on concurrent recreation When multiple requests detect a closed shared session simultaneously, they would each create a new aiohttp.ClientSession, leaking intermediate sessions and their TCP connectors. Added double-checked locking pattern with asyncio.Lock to ensure only one coroutine recreates the session. Added concurrent recreation test case. --- litellm/proxy/route_llm_request.py | 45 ++++++++++++---- .../proxy/test_aiohttp_session_recovery.py | 51 +++++++++++++++++++ 2 files changed, 85 insertions(+), 11 deletions(-) diff --git a/litellm/proxy/route_llm_request.py b/litellm/proxy/route_llm_request.py index 265a311d393..376bf07f2b5 100644 --- a/litellm/proxy/route_llm_request.py +++ b/litellm/proxy/route_llm_request.py @@ -1,3 +1,4 @@ +import asyncio from typing import TYPE_CHECKING, Any, Literal, Optional from fastapi import HTTPException, status @@ -123,11 +124,24 @@ def get_team_id_from_data(data: dict) -> Optional[str]: return None +_shared_session_lock: Optional[asyncio.Lock] = None + + +def _get_shared_session_lock() -> asyncio.Lock: + """Lazily create the shared session lock (must be called within a running event loop).""" + global _shared_session_lock + if _shared_session_lock is None: + _shared_session_lock = asyncio.Lock() + return _shared_session_lock + + async def add_shared_session_to_data(data: dict) -> None: """ Add shared aiohttp session for connection reuse (prevents cold starts). If the session was closed (e.g. due to network interruption or idle timeout), automatically recreates it so connection pooling is restored. + Uses an asyncio.Lock to prevent race conditions where multiple concurrent + requests could each create a new session, leaking intermediate ones. Silently continues without session reuse if import fails or session is unavailable. Args: @@ -146,23 +160,32 @@ async def add_shared_session_to_data(data: dict) -> None: ) elif session is not None and session.closed: # Session was created at startup but has since closed — recreate it - verbose_proxy_logger.warning( - f"SESSION REUSE: Shared aiohttp session is closed (ID: {id(session)}), recreating..." - ) - new_session = await proxy_server._initialize_shared_aiohttp_session() - if new_session is not None: - proxy_server.shared_aiohttp_session = new_session - data["shared_session"] = new_session - else: - verbose_proxy_logger.info( - "SESSION REUSE: Failed to recreate shared session, continuing without session reuse" + # Use lock to prevent concurrent recreation (avoids session/connector leak) + lock = _get_shared_session_lock() + async with lock: + # Double-check under lock — another coroutine may have already recreated it + session = proxy_server.shared_aiohttp_session + if session is not None and not session.closed: + data["shared_session"] = session + return + + verbose_proxy_logger.warning( + f"SESSION REUSE: Shared aiohttp session is closed (ID: {id(session)}), recreating..." ) + new_session = await proxy_server._initialize_shared_aiohttp_session() + if new_session is not None: + proxy_server.shared_aiohttp_session = new_session + data["shared_session"] = new_session + else: + verbose_proxy_logger.info( + "SESSION REUSE: Failed to recreate shared session, continuing without session reuse" + ) else: verbose_proxy_logger.info( "SESSION REUSE: No shared session available for this request" ) except Exception: - # Silently continue without session reuse if import fails or session unavailable + # Silently continue without session reuse if import fails or session is unavailable pass diff --git a/tests/test_litellm/proxy/test_aiohttp_session_recovery.py b/tests/test_litellm/proxy/test_aiohttp_session_recovery.py index e87ca5a1632..b71a61d2c29 100644 --- a/tests/test_litellm/proxy/test_aiohttp_session_recovery.py +++ b/tests/test_litellm/proxy/test_aiohttp_session_recovery.py @@ -97,3 +97,54 @@ async def test_add_shared_session_no_session_available(): data = {} await add_shared_session_to_data(data) assert "shared_session" not in data + + +@pytest.mark.asyncio +async def test_add_shared_session_concurrent_recreation_uses_lock(): + """When multiple coroutines detect a closed session concurrently, + only one should recreate it (double-checked locking via asyncio.Lock).""" + import litellm.proxy.route_llm_request as route_module + from litellm.proxy import proxy_server as proxy_server_module + from litellm.proxy.route_llm_request import add_shared_session_to_data + + # Reset the module-level lock so each test is isolated + route_module._shared_session_lock = None + + closed_session = MagicMock() + closed_session.closed = True + + new_session = MagicMock() + new_session.closed = False + + call_count = 0 + + async def mock_init(): + nonlocal call_count + call_count += 1 + # Simulate some async work + await asyncio.sleep(0.01) + proxy_server_module.shared_aiohttp_session = new_session + return new_session + + with patch.object( + proxy_server_module, + "shared_aiohttp_session", + closed_session, + ): + with patch.object( + proxy_server_module, + "_initialize_shared_aiohttp_session", + side_effect=mock_init, + ): + # Launch 5 concurrent calls + results = [{} for _ in range(5)] + await asyncio.gather( + *(add_shared_session_to_data(d) for d in results) + ) + + # Only 1 coroutine should have called _initialize (the rest see the + # re-checked session as open under the lock) + assert call_count == 1, f"Expected 1 init call, got {call_count}" + # All should have the new session + for d in results: + assert d.get("shared_session") is new_session From 9e09bbc1dfbccc551a8898ab3268a1014c7c81d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?d=20=F0=9F=94=B9?= <258577966+voidborne-d@users.noreply.github.com> Date: Tue, 17 Mar 2026 09:54:01 +0000 Subject: [PATCH 40/60] fix: reset _shared_session_lock in all tests for event loop isolation Address Greptile P1 review: tests that exercise the closed-session code path need to reset the module-level lock to avoid RuntimeError on Python < 3.10 when asyncio.Lock is reused across different event loops. --- tests/test_litellm/proxy/test_aiohttp_session_recovery.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_litellm/proxy/test_aiohttp_session_recovery.py b/tests/test_litellm/proxy/test_aiohttp_session_recovery.py index b71a61d2c29..763744609e3 100644 --- a/tests/test_litellm/proxy/test_aiohttp_session_recovery.py +++ b/tests/test_litellm/proxy/test_aiohttp_session_recovery.py @@ -33,9 +33,13 @@ async def test_add_shared_session_attaches_open_session(): @pytest.mark.asyncio async def test_add_shared_session_recreates_closed_session(): """When the shared session is closed, it should be recreated.""" + import litellm.proxy.route_llm_request as route_module from litellm.proxy import proxy_server as proxy_server_module from litellm.proxy.route_llm_request import add_shared_session_to_data + # Reset the module-level lock so each test uses the current event loop + route_module._shared_session_lock = None + closed_session = MagicMock() closed_session.closed = True @@ -64,9 +68,13 @@ async def test_add_shared_session_recreates_closed_session(): @pytest.mark.asyncio async def test_add_shared_session_handles_recreation_failure(): """When recreation fails, data should not contain shared_session.""" + import litellm.proxy.route_llm_request as route_module from litellm.proxy import proxy_server as proxy_server_module from litellm.proxy.route_llm_request import add_shared_session_to_data + # Reset the module-level lock so each test uses the current event loop + route_module._shared_session_lock = None + closed_session = MagicMock() closed_session.closed = True From 32ecd241168c57fcc1de28c75b3cb72524af8ea2 Mon Sep 17 00:00:00 2001 From: d Date: Tue, 17 Mar 2026 13:09:26 +0000 Subject: [PATCH 41/60] fix: address P2 review feedback - exception handling and warning accuracy - Add try/except around _initialize_shared_aiohttp_session call to catch and log exceptions (instead of letting them bubble to outer handler) - Fix warning message when re-checked session is None (was incorrectly logging closed session ID on a None session) - Add debug logging to outer except handler instead of bare pass - Add test for _initialize_shared_aiohttp_session raising exception --- litellm/proxy/route_llm_request.py | 35 +++++++++++++++---- .../proxy/test_aiohttp_session_recovery.py | 30 ++++++++++++++++ 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/litellm/proxy/route_llm_request.py b/litellm/proxy/route_llm_request.py index 376bf07f2b5..96e6f705cca 100644 --- a/litellm/proxy/route_llm_request.py +++ b/litellm/proxy/route_llm_request.py @@ -169,10 +169,23 @@ async def add_shared_session_to_data(data: dict) -> None: data["shared_session"] = session return - verbose_proxy_logger.warning( - f"SESSION REUSE: Shared aiohttp session is closed (ID: {id(session)}), recreating..." - ) - new_session = await proxy_server._initialize_shared_aiohttp_session() + # session could be None here (if another coroutine set it to None) + # or closed — either way we need to recreate + if session is not None: + verbose_proxy_logger.warning( + f"SESSION REUSE: Shared aiohttp session is closed (ID: {id(session)}), recreating..." + ) + else: + verbose_proxy_logger.warning( + "SESSION REUSE: Shared aiohttp session is None after re-check, recreating..." + ) + try: + new_session = await proxy_server._initialize_shared_aiohttp_session() + except Exception: + verbose_proxy_logger.exception( + "SESSION REUSE: Exception during shared session recreation" + ) + new_session = None if new_session is not None: proxy_server.shared_aiohttp_session = new_session data["shared_session"] = new_session @@ -185,8 +198,18 @@ async def add_shared_session_to_data(data: dict) -> None: "SESSION REUSE: No shared session available for this request" ) except Exception: - # Silently continue without session reuse if import fails or session is unavailable - pass + # Continue without session reuse — this outer handler covers import failures + # and other unexpected errors to avoid breaking the request path. + # Inner recovery logic has its own specific exception handling. + try: + from litellm._logging import verbose_proxy_logger + + verbose_proxy_logger.debug( + "SESSION REUSE: Unexpected error in session setup, continuing without reuse", + exc_info=True, + ) + except Exception: + pass async def route_request( # noqa: PLR0915 - Complex routing function, refactoring tracked separately diff --git a/tests/test_litellm/proxy/test_aiohttp_session_recovery.py b/tests/test_litellm/proxy/test_aiohttp_session_recovery.py index 763744609e3..224a4aa1392 100644 --- a/tests/test_litellm/proxy/test_aiohttp_session_recovery.py +++ b/tests/test_litellm/proxy/test_aiohttp_session_recovery.py @@ -94,6 +94,36 @@ async def test_add_shared_session_handles_recreation_failure(): assert "shared_session" not in data +@pytest.mark.asyncio +async def test_add_shared_session_handles_recreation_exception(): + """When _initialize_shared_aiohttp_session raises, data should not contain shared_session.""" + import litellm.proxy.route_llm_request as route_module + from litellm.proxy import proxy_server as proxy_server_module + from litellm.proxy.route_llm_request import add_shared_session_to_data + + # Reset the module-level lock so each test uses the current event loop + route_module._shared_session_lock = None + + closed_session = MagicMock() + closed_session.closed = True + + with patch.object( + proxy_server_module, + "shared_aiohttp_session", + closed_session, + ): + with patch.object( + proxy_server_module, + "_initialize_shared_aiohttp_session", + new_callable=AsyncMock, + side_effect=RuntimeError("connection pool exhausted"), + ): + data = {} + await add_shared_session_to_data(data) + # Should gracefully handle exception — no shared_session attached + assert "shared_session" not in data + + @pytest.mark.asyncio async def test_add_shared_session_no_session_available(): """When no session was ever created, data should not contain shared_session.""" From 467706ea301a367d92e8957a01b1d3a978c418d5 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 17 Mar 2026 08:58:07 -0700 Subject: [PATCH 42/60] Revert "fix: langfuse trace leak key on model params" --- litellm/integrations/langfuse/langfuse.py | 25 +++-------------- litellm/litellm_core_utils/litellm_logging.py | 28 +++++++++---------- 2 files changed, 17 insertions(+), 36 deletions(-) diff --git a/litellm/integrations/langfuse/langfuse.py b/litellm/integrations/langfuse/langfuse.py index 29038786d06..6ac337d99a9 100644 --- a/litellm/integrations/langfuse/langfuse.py +++ b/litellm/integrations/langfuse/langfuse.py @@ -25,7 +25,6 @@ from litellm.litellm_core_utils.core_helpers import ( reconstruct_model_name, filter_exceptions_from_params, ) -from litellm.litellm_core_utils.model_param_helper import ModelParamHelper from litellm.litellm_core_utils.redact_messages import redact_user_api_key_info from litellm.integrations.langfuse.langfuse_mock_client import ( create_mock_langfuse_client, @@ -292,6 +291,8 @@ class LangFuseLogger: functions = optional_params.pop("functions", None) tools = optional_params.pop("tools", None) + # Remove secret_fields to prevent leaking sensitive data (e.g., authorization headers) + optional_params.pop("secret_fields", None) if functions is not None: prompt["functions"] = functions if tools is not None: @@ -504,18 +505,13 @@ class LangFuseLogger: kwargs.get("model", ""), custom_llm_provider, metadata ) - # Use whitelisted model parameters to prevent leaking secrets - sanitized_model_params = ModelParamHelper.get_standard_logging_model_parameters( - optional_params - ) - trace.generation( CreateGeneration( name=metadata.get("generation_name", "litellm-completion"), startTime=start_time, endTime=end_time, model=model_name, - modelParameters=sanitized_model_params, + modelParameters=optional_params, prompt=input, completion=output, usage={ @@ -835,26 +831,13 @@ class LangFuseLogger: kwargs.get("model", ""), custom_llm_provider, metadata ) - # Use whitelisted model_parameters from StandardLoggingPayload - # to prevent leaking secrets (api_key, auth headers, etc.) - if standard_logging_object is not None: - sanitized_model_params = standard_logging_object.get( - "model_parameters", optional_params - ) - else: - sanitized_model_params = ( - ModelParamHelper.get_standard_logging_model_parameters( - optional_params - ) - ) - generation_params = { "name": generation_name, "id": clean_metadata.pop("generation_id", generation_id), "start_time": start_time, "end_time": end_time, "model": model_name, - "model_parameters": sanitized_model_params, + "model_parameters": optional_params, "input": input if not mask_input else "redacted-by-litellm", "output": output if not mask_output else "redacted-by-litellm", "usage": usage, diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 5c7ca00fee4..a92f4cb9ec8 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -5569,23 +5569,21 @@ def scrub_sensitive_keys_in_metadata(litellm_params: Optional[dict]): litellm_params["_langfuse_masking_function"] = masking_fn litellm_params["metadata"] = metadata - ## remove sensitive logging/callback keys from metadata dicts - ## these contain credentials (langfuse_secret_key, langfuse_public_key, etc.) - _sensitive_keys = {"logging", "callback_settings"} - - for metadata_field in ( - "user_api_key_metadata", - "user_api_key_auth_metadata", - "user_api_key_team_metadata", + ## check user_api_key_metadata for sensitive logging keys + cleaned_user_api_key_metadata = {} + if "user_api_key_metadata" in metadata and isinstance( + metadata["user_api_key_metadata"], dict ): - if metadata_field in metadata and isinstance(metadata[metadata_field], dict): - for sensitive_key in _sensitive_keys: - metadata[metadata_field].pop(sensitive_key, None) + for k, v in metadata["user_api_key_metadata"].items(): + if k == "logging": # prevent logging user logging keys + cleaned_user_api_key_metadata[k] = ( + "scrubbed_by_litellm_for_sensitive_keys" + ) + else: + cleaned_user_api_key_metadata[k] = v - ## remove user_api_key_auth entirely - contains full auth object with nested credentials - metadata.pop("user_api_key_auth", None) - - litellm_params["metadata"] = metadata + metadata["user_api_key_metadata"] = cleaned_user_api_key_metadata + litellm_params["metadata"] = metadata return litellm_params From ef22144854f0248163ff9250060ed49afff23bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?d=20=F0=9F=94=B9?= <258577966+voidborne-d@users.noreply.github.com> Date: Tue, 17 Mar 2026 18:07:15 +0000 Subject: [PATCH 43/60] address P2 feedback: add lock docstring warning, remove redundant mock write - Add WARNING docstring to _get_shared_session_lock() about not resetting the lock to None while coroutines may be in the recovery path - Remove redundant proxy_server_module.shared_aiohttp_session assignment in mock_init (add_shared_session_to_data overwrites it synchronously) --- litellm/proxy/route_llm_request.py | 7 ++++++- tests/test_litellm/proxy/test_aiohttp_session_recovery.py | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/route_llm_request.py b/litellm/proxy/route_llm_request.py index 96e6f705cca..79e8f41972f 100644 --- a/litellm/proxy/route_llm_request.py +++ b/litellm/proxy/route_llm_request.py @@ -128,7 +128,12 @@ _shared_session_lock: Optional[asyncio.Lock] = None def _get_shared_session_lock() -> asyncio.Lock: - """Lazily create the shared session lock (must be called within a running event loop).""" + """Lazily create the shared session lock (must be called within a running event loop). + + WARNING: Do not reset _shared_session_lock to None while any coroutine may be + executing the session-recovery path; doing so breaks the double-checked locking + guarantee and can cause duplicate session creation. + """ global _shared_session_lock if _shared_session_lock is None: _shared_session_lock = asyncio.Lock() diff --git a/tests/test_litellm/proxy/test_aiohttp_session_recovery.py b/tests/test_litellm/proxy/test_aiohttp_session_recovery.py index 224a4aa1392..a2b09527962 100644 --- a/tests/test_litellm/proxy/test_aiohttp_session_recovery.py +++ b/tests/test_litellm/proxy/test_aiohttp_session_recovery.py @@ -161,7 +161,6 @@ async def test_add_shared_session_concurrent_recreation_uses_lock(): call_count += 1 # Simulate some async work await asyncio.sleep(0.01) - proxy_server_module.shared_aiohttp_session = new_session return new_session with patch.object( From 3ff4ac3de3fc2fb013ed2ecbe4b5b82304d43fe1 Mon Sep 17 00:00:00 2001 From: Xianzong Xie Date: Tue, 17 Mar 2026 11:21:26 -0700 Subject: [PATCH 44/60] Capture incomplete terminal error in background streaming Committed-By-Agent: codex Co-authored-by: codex --- litellm/proxy/response_polling/background_streaming.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/response_polling/background_streaming.py b/litellm/proxy/response_polling/background_streaming.py index 5ec0aac8e29..17ee101549e 100644 --- a/litellm/proxy/response_polling/background_streaming.py +++ b/litellm/proxy/response_polling/background_streaming.py @@ -258,8 +258,8 @@ async def background_streaming_task( # noqa: PLR0915 ), ) - # Extract error for failed responses - if event_type == "response.failed": + # Extract error for failed and incomplete responses + if event_type == "response.failed" or event_type == "response.incomplete": terminal_error = response_data.get("error") # Core response fields @@ -337,7 +337,7 @@ async def background_streaming_task( # noqa: PLR0915 ) verbose_proxy_logger.info( - f"Finished background streaming for {polling_id}, status={final_status}, output_items={len(output_items)}" + f"Finished background streaming for {polling_id}, status={final_status}, error={terminal_error}, output_items={len(output_items)}" ) except Exception as e: From bd5c39c4d1da36f6641ce5ea57df9067b93241ee Mon Sep 17 00:00:00 2001 From: Xianzong Xie Date: Tue, 17 Mar 2026 11:35:50 -0700 Subject: [PATCH 45/60] Log incomplete details in background streaming Committed-By-Agent: codex Co-authored-by: codex --- litellm/proxy/response_polling/background_streaming.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/proxy/response_polling/background_streaming.py b/litellm/proxy/response_polling/background_streaming.py index 17ee101549e..b4d51814e5a 100644 --- a/litellm/proxy/response_polling/background_streaming.py +++ b/litellm/proxy/response_polling/background_streaming.py @@ -337,7 +337,7 @@ async def background_streaming_task( # noqa: PLR0915 ) verbose_proxy_logger.info( - f"Finished background streaming for {polling_id}, status={final_status}, error={terminal_error}, output_items={len(output_items)}" + f"Finished background streaming for {polling_id}, status={final_status}, error={terminal_error}, incomplete_details={incomplete_details_data}, output_items={len(output_items)}" ) except Exception as e: From cb888364867f7d4226c3162b411cea0299d60d0a Mon Sep 17 00:00:00 2001 From: Xianzong Xie Date: Tue, 17 Mar 2026 11:39:12 -0700 Subject: [PATCH 46/60] Add incomplete response error propagation test Committed-By-Agent: codex Co-authored-by: codex --- tests/proxy_unit_tests/test_response_polling_handler.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/proxy_unit_tests/test_response_polling_handler.py b/tests/proxy_unit_tests/test_response_polling_handler.py index 6235dde8475..c5f3d7c6f45 100644 --- a/tests/proxy_unit_tests/test_response_polling_handler.py +++ b/tests/proxy_unit_tests/test_response_polling_handler.py @@ -1414,6 +1414,11 @@ class TestBackgroundStreamingTerminalEvents: background_streaming_task, ) + error_payload = { + "type": "incomplete_response", + "message": "The model stopped before producing a complete response", + "code": "max_output_tokens", + } events = [ {"type": "response.in_progress"}, { @@ -1421,6 +1426,7 @@ class TestBackgroundStreamingTerminalEvents: "response": { "id": "resp_123", "status": "incomplete", + "error": error_payload, "incomplete_details": {"reason": "max_output_tokens"}, "usage": {"input_tokens": 10, "output_tokens": 4096}, "model": "gpt-4o", @@ -1442,6 +1448,7 @@ class TestBackgroundStreamingTerminalEvents: final_call = handler.update_state.call_args_list[-1] assert final_call.kwargs["status"] == "incomplete" + assert final_call.kwargs["error"] == error_payload assert final_call.kwargs["incomplete_details"] == {"reason": "max_output_tokens"} assert final_call.kwargs["usage"] == {"input_tokens": 10, "output_tokens": 4096} From ca8f5cffa0051dfb9f607b4f4f7fe925312c9a18 Mon Sep 17 00:00:00 2001 From: voidborne-d Date: Tue, 17 Mar 2026 18:52:57 +0000 Subject: [PATCH 47/60] style: apply black formatting to fix CI lint check --- litellm/proxy/route_llm_request.py | 4 +++- .../proxy/test_aiohttp_session_recovery.py | 12 +++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/litellm/proxy/route_llm_request.py b/litellm/proxy/route_llm_request.py index 79e8f41972f..f1590b16c24 100644 --- a/litellm/proxy/route_llm_request.py +++ b/litellm/proxy/route_llm_request.py @@ -185,7 +185,9 @@ async def add_shared_session_to_data(data: dict) -> None: "SESSION REUSE: Shared aiohttp session is None after re-check, recreating..." ) try: - new_session = await proxy_server._initialize_shared_aiohttp_session() + new_session = ( + await proxy_server._initialize_shared_aiohttp_session() + ) except Exception: verbose_proxy_logger.exception( "SESSION REUSE: Exception during shared session recreation" diff --git a/tests/test_litellm/proxy/test_aiohttp_session_recovery.py b/tests/test_litellm/proxy/test_aiohttp_session_recovery.py index a2b09527962..f089d10425c 100644 --- a/tests/test_litellm/proxy/test_aiohttp_session_recovery.py +++ b/tests/test_litellm/proxy/test_aiohttp_session_recovery.py @@ -22,9 +22,7 @@ async def test_add_shared_session_attaches_open_session(): mock_session = MagicMock() mock_session.closed = False - with patch( - "litellm.proxy.proxy_server.shared_aiohttp_session", mock_session - ): + with patch("litellm.proxy.proxy_server.shared_aiohttp_session", mock_session): data = {} await add_shared_session_to_data(data) assert data["shared_session"] is mock_session @@ -129,9 +127,7 @@ async def test_add_shared_session_no_session_available(): """When no session was ever created, data should not contain shared_session.""" from litellm.proxy.route_llm_request import add_shared_session_to_data - with patch( - "litellm.proxy.proxy_server.shared_aiohttp_session", None - ): + with patch("litellm.proxy.proxy_server.shared_aiohttp_session", None): data = {} await add_shared_session_to_data(data) assert "shared_session" not in data @@ -175,9 +171,7 @@ async def test_add_shared_session_concurrent_recreation_uses_lock(): ): # Launch 5 concurrent calls results = [{} for _ in range(5)] - await asyncio.gather( - *(add_shared_session_to_data(d) for d in results) - ) + await asyncio.gather(*(add_shared_session_to_data(d) for d in results)) # Only 1 coroutine should have called _initialize (the rest see the # re-checked session as open under the lock) From a51c670f2f759de63ad51736ad0933508c922ba0 Mon Sep 17 00:00:00 2001 From: joereyna Date: Tue, 17 Mar 2026 12:56:25 -0700 Subject: [PATCH 48/60] revert: remove provider_endpoints_support.json changes, docs only --- provider_endpoints_support.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index d2edb0017f7..2f3302bb574 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -471,7 +471,9 @@ "audio_speech": false, "moderations": false, "batches": false, - "rerank": false + "rerank": false, + "a2a": false, + "interactions": false } }, "chutes": { @@ -2552,22 +2554,20 @@ "rerank": false } }, - "sagemaker_nova": { - "display_name": "AWS SageMaker Nova (`sagemaker_nova`)", - "url": "https://docs.litellm.ai/docs/providers/aws_sagemaker", + "charity_engine": { + "display_name": "Charity Engine (`charity_engine`)", + "url": "https://docs.litellm.ai/docs/providers/charity_engine", "endpoints": { "chat_completions": true, - "messages": false, - "responses": false, + "messages": true, + "responses": true, "embeddings": false, "image_generations": false, "audio_transcriptions": false, "audio_speech": false, "moderations": false, "batches": false, - "rerank": false, - "a2a": false, - "interactions": false + "rerank": false } } }, From d0c5f494a8bf99a078561a939cb0e557559c614f Mon Sep 17 00:00:00 2001 From: Kelvin Tran Date: Tue, 17 Mar 2026 14:30:12 -0700 Subject: [PATCH 49/60] fix: cache_control directive dropped anthropic document/file blocks --- .../prompt_templates/factory.py | 16 +++- ...llm_core_utils_prompt_templates_factory.py | 89 ++++++++++++++++++- 2 files changed, 102 insertions(+), 3 deletions(-) diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index 53d2ca2f23f..2b4dbc4a3ac 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -2142,13 +2142,25 @@ def anthropic_messages_pt( # noqa: PLR0915 user_content.append(_content_element) elif m.get("type", "") == "document": - user_content.append(cast(AnthropicMessagesDocumentParam, m)) + _document_content_element = cast( + AnthropicMessagesDocumentParam, + add_cache_control_to_content( + anthropic_content_element=cast(AnthropicMessagesDocumentParam, m), + original_content_element=dict(m), + ), + ) + user_content.append(_document_content_element) elif m.get("type", "") == "file": - user_content.append( + _file_content_element = ( anthropic_process_openai_file_message( cast(ChatCompletionFileObject, m) ) ) + _file_content_element = add_cache_control_to_content( + anthropic_content_element=cast(AnthropicMessagesDocumentParam, _file_content_element), + original_content_element=dict(m), + ) + user_content.append(cast(AnthropicMessagesDocumentParam,_file_content_element)) elif isinstance(user_message_types_block["content"], str): _anthropic_content_text_element: AnthropicMessagesTextParam = { "type": "text", diff --git a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py index e87233a52a3..fb101162988 100644 --- a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py +++ b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py @@ -1,4 +1,4 @@ -import json +import base64 from unittest.mock import MagicMock, patch import pytest @@ -8,6 +8,7 @@ from litellm.litellm_core_utils.prompt_templates.factory import ( BAD_MESSAGE_ERROR_STR, BedrockConverseMessagesProcessor, BedrockImageProcessor, + anthropic_messages_pt, ollama_pt, ) @@ -1590,3 +1591,89 @@ def test_bedrock_tools_unpack_defs_no_oom_with_nested_refs(): # Verify $defs have been removed (Bedrock doesn't support them) tool_schema = result[0]["toolSpec"].get("inputSchema", {}).get("json", {}) assert "$defs" not in tool_schema, "$defs should be removed after expansion" + + +def test_anthropic_messages_pt_file_block_preserves_cache_control(): + """ + Test that cache_control on file-type content blocks is preserved + when translating to Anthropic message format. + Regression test for https://github.com/BerriAI/litellm/issues/23873 + """ + + pdf_b64 = base64.b64encode(b"%PDF-1.4 fake pdf content").decode() + messages = [ + { + "role": "user", + "content": [ + { + "type": "file", + "file": { + "filename": "document.pdf", + "file_data": f"data:application/pdf;base64,{pdf_b64}", + }, + "cache_control": {"type": "ephemeral"}, + }, + { + "type": "text", + "text": "Summarize this document.", + "cache_control": {"type": "ephemeral"}, + }, + ], + } + ] + + result = anthropic_messages_pt( + messages=messages, + model="claude-sonnet-4-20250514", + llm_provider="anthropic", + ) + + assert len(result) == 1 + content_blocks = result[0]["content"] + assert len(content_blocks) == 2 + + file_block = content_blocks[0] + assert file_block["type"] == "document" + assert "cache_control" in file_block, ( + "cache_control should be preserved on file/document content blocks" + ) + assert file_block["cache_control"]["type"] == "ephemeral" + + text_block = content_blocks[1] + assert text_block["type"] == "text" + assert "cache_control" in text_block + assert text_block["cache_control"]["type"] == "ephemeral" + + +def test_anthropic_messages_pt_file_block_without_cache_control(): + """ + Test that file blocks without cache_control still work correctly. + """ + import base64 + + pdf_b64 = base64.b64encode(b"%PDF-1.4 fake").decode() + messages = [ + { + "role": "user", + "content": [ + { + "type": "file", + "file": { + "filename": "doc.pdf", + "file_data": f"data:application/pdf;base64,{pdf_b64}", + }, + }, + ], + } + ] + + result = anthropic_messages_pt( + messages=messages, + model="claude-sonnet-4-20250514", + llm_provider="anthropic", + ) + + assert len(result) == 1 + file_block = result[0]["content"][0] + assert file_block["type"] == "document" + assert "cache_control" not in file_block From b8ffbba352ba70b92c4057c418cce44a8e7fa1f9 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 17 Mar 2026 17:23:39 -0700 Subject: [PATCH 50/60] [Fix] Add contents:write permission to release job in ghcr_deploy workflow The release job was failing with "Resource not accessible by integration" because other jobs explicitly set permissions, causing GitHub to scope the default token down for all jobs. The release job needs contents:write to create GitHub releases. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ghcr_deploy.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ghcr_deploy.yml b/.github/workflows/ghcr_deploy.yml index c317309d91a..344b0ec48ee 100644 --- a/.github/workflows/ghcr_deploy.yml +++ b/.github/workflows/ghcr_deploy.yml @@ -369,7 +369,8 @@ jobs: release: name: "New LiteLLM Release" needs: [docker-hub-deploy, build-and-push-image, build-and-push-image-database] - + permissions: + contents: write runs-on: "ubuntu-latest" steps: From 709581c5f9279dde6c6d941797bdb9696d3cfcd6 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 17 Mar 2026 17:31:45 -0700 Subject: [PATCH 51/60] =?UTF-8?q?bump:=20version=201.82.3=20=E2=86=92=201.?= =?UTF-8?q?82.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 07004f3ae16..37223a3e251 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "1.82.3" +version = "1.82.4" description = "Library to easily interface with LLM API providers" authors = ["BerriAI"] license = "MIT" @@ -183,7 +183,7 @@ requires = ["poetry-core", "wheel"] build-backend = "poetry.core.masonry.api" [tool.commitizen] -version = "1.82.3" +version = "1.82.4" version_files = [ "pyproject.toml:^version" ] From 9fa1809c30c0bc18f70bbe60626966285a172cfa Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 17 Mar 2026 17:37:04 -0700 Subject: [PATCH 52/60] =?UTF-8?q?bump:=20version=200.4.56=20=E2=86=92=200.?= =?UTF-8?q?4.57?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- litellm-proxy-extras/pyproject.toml | 4 ++-- pyproject.toml | 2 +- requirements.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/litellm-proxy-extras/pyproject.toml b/litellm-proxy-extras/pyproject.toml index b65dbe45233..006aad9480b 100644 --- a/litellm-proxy-extras/pyproject.toml +++ b/litellm-proxy-extras/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm-proxy-extras" -version = "0.4.56" +version = "0.4.57" description = "Additional files for the LiteLLM Proxy. Reduces the size of the main litellm package." authors = ["BerriAI"] readme = "README.md" @@ -22,7 +22,7 @@ requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.commitizen] -version = "0.4.56" +version = "0.4.57" version_files = [ "pyproject.toml:version", "../requirements.txt:litellm-proxy-extras==", diff --git a/pyproject.toml b/pyproject.toml index 37223a3e251..0e2fb40d935 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ boto3 = { version = "^1.40.76", optional = true } redisvl = {version = "^0.4.1", optional = true, markers = "python_version >= '3.9' and python_version < '3.14'"} mcp = {version = ">=1.25.0,<2.0.0", optional = true, python = ">=3.10"} a2a-sdk = {version = "^0.3.22", optional = true, python = ">=3.10"} -litellm-proxy-extras = {version = "^0.4.56", optional = true} +litellm-proxy-extras = {version = "^0.4.57", optional = true} rich = {version = "^13.7.1", optional = true} litellm-enterprise = {version = "^0.1.33", optional = true} diskcache = {version = "^5.6.1", optional = true} diff --git a/requirements.txt b/requirements.txt index 2bdafda612a..827986487fc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -57,7 +57,7 @@ grpcio>=1.75.0; python_version >= "3.14" sentry_sdk==2.21.0 # for sentry error handling detect-secrets==1.5.0 # Enterprise - secret detection / masking in LLM requests tzdata==2025.1 # IANA time zone database -litellm-proxy-extras==0.4.56 # for proxy extras - e.g. prisma migrations +litellm-proxy-extras==0.4.57 # for proxy extras - e.g. prisma migrations llm-sandbox==0.3.31 # for skill execution in sandbox ### LITELLM PACKAGE DEPENDENCIES python-dotenv==1.0.1 # for env From cc37bf59344f5a5ba364d6fbc406f17f30e550a4 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 17 Mar 2026 17:37:25 -0700 Subject: [PATCH 53/60] adding build --- ...litellm_proxy_extras-0.4.57-py3-none-any.whl | Bin 0 -> 76172 bytes .../dist/litellm_proxy_extras-0.4.57.tar.gz | Bin 0 -> 31935 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.4.57-py3-none-any.whl create mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.4.57.tar.gz diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.57-py3-none-any.whl b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.57-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..eeed18e312aecb6afdcdfe13d71fbd7fce68ea44 GIT binary patch literal 76172 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 literal 0 HcmV?d00001 diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.57.tar.gz b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.57.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..293c44e593d5147d8cb69b76abd3406c9e55c107 GIT binary patch literal 31935 zcmXt z9q#}8@L~7_dyd)rI?uI!YYjsf9v%kz2MgF*xtQ77+Ce`$Ie2(N%{*M3Oq_Z6d4+g| zMR{FJoG^A9cbsNaV)!Lsy6PG&C2)S>^e1EuD%IDg=Ut?GvYh(VxxZ&R`_c|%Arl+?Zf3MrrdcfzPH`@w^ z@5?pmK<9GbOPOzQ@YNNi=ncH^Y9t`N%{~D|`RO09V|bLh>3%-DNlQnE`do=B4^Hrd!y1wM-& z9`VZyRbVeHA}+-<4+tj*N~=prcUqiS$LHE9;*XbhMU04TH7m0uY0kHeEnqMhuECEA z3Q0+}`Q2gnjteF$Vz=k@WBXAx)d!NRm$nugJ?D9^NSc3&%{1=KxJR1q8XVqflNLC( zt3$Rlu0`%ilX|r~F?9ExV{b0beU4v|*iY2MNSa!CMUloE@bvI*1o<+hccAzu_-BUa zN&`4e4bL&%+q{L`ww@@=B=0s8uhegnBX)x>N^_Q#FiU4!Ol8`IpOvr;j4lZpyI-xf z5EDw=UnKaL>TSABl==WQk((}@vY_^3@zZ_T0ELBq1rMr4g}Tq)mvnrX&oHBk=&1bV zZJ!iHRJY^|epFCsQpA)&_#CLM@i_*h4$r!qyhu5x_9``V=~%#ge26b%(G{6~*An8w z{=WJ7pKlsgB-rC;@_L*GSXjpKvT^Wx`#fS0caa7D;O6*4ahX^5UGPgslB1Gbp2g-c zG$SEr-TiCzGca1{MfVg@ZCw-cdddoX7m9FLJm{cDbWZK5ConkRSN@S{`g8O7UDM-E zN_bTIAn&US6#y$Pz7`c-J@(qzZ5u{KDNLRml=f;NE*uasp-i>f^Q-#Ok*WqE~h*XlA3_ClVOW-9AcYcCygnTp6BDvHux+ z7Jw(z0*B0>u3&asflJzsx|V^

q$qREtayi-m&CL1b=*Ac@53@t}s-VDBL~sLrfi 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

_)(gW}{?KFs8NxqarOl zgP(1}@-Rd@lGIE1N=Sp>sYQ&;QzV0u9x%hx57NVcd%f^6(3Q^arNu1lZodxCFBQGN z*Bq|oLBhHA)D^0V4TS(cOm9zQNOU)t8b$!ozo--gJgp?GH}V*Cs7wSb?iz|72VT4= z2bb+-u$Oj}Z&wM`QX-iY!sv4ePHu4!1$?;PXscqn4Jy5ofx|tJcZI4A;SfDW$h1W_ zpUbsI?!&Vl(m7%cpvALB;suuyJ*%kY)*{UkP&P~j*R_|hdXXFWh=7rM-H!*)LNn-h zsFp8Z>} zHjIUjZEJ6c_->P+!fu~~P?ds>z-R}rB*2q#(S?5mwXlE%N-)mLk?A~-+kn9|3SNr@tJ}fb2!V-> zIv72?hBbm*<(+t>!Qc(8x@9t-`8vX6+x5LM9@e$uGP6|kP6s2s(J=i}5BS)W1B23B zp{IvKaHQbdo~}6Jo}hqVD|6Y%P|tFCZKlW6d{CpP=AYk5Nu!FgN57zk&l#$2Cip8s zr*k(fH9L6?5|6h%U(G7}9QWdUF;?7Zw>xSaTM^->L1#xe&5J0(*fl#(H{x;zAE>5L z6}*T853whN_Cc-R`2R!?J!VuOaeMbX5hnMY0*}Dn!^2L5Gvcr26!E}5=M?%*fu?YRo+;rAs2o{1Ne56fYIMz48q~8(5Ep$ z&Fd~wW0;IH9YD3&s1?-DWxI`oiW~%}yM%>;&KyYfyN!iDP!XWiuxbYPSA5;>3wsTFa7cZIV6VY&!&dr*x_zkJydm@xV?~)bM)I8}eU1vpk?B_x zzF7AU*S{fUjaHZIe(Ohz2jsvfS!y6>Q=^v^lT#YDnrFXhR_E4S!S0RXQgQSSO7gNV zEcO$}`&D8`Ps~M`1MIrF)7qnl^u7%eiiA?)P2%D*docBy44C6RlFLuH;Cjw|CqVZl zuTGFw@UH2SmI)_O0^w)F2fWlTQa`JH{w?&*De2$W!)`ODw_-f}XS$etcA!KROq{{f zG(braO1I=J-*r?(pN;}gmdDrfIIlLDbzYAKS@Q;Q{M3GK-g430Y!E zI&=)YR)JzgOXs;P{`8j1Vdz*X2dmq#iBdAyxq{QQ1DgUcBUsmrH$6vhhO<(;cHj3k z4wfNs??u2kCRJD9GgDVkCsN$>KnShOAgtgM%@(IrPQj*F@NJe=;HBII5GT>{E{$i_i(FOZXdC=Ge`vrT8EnGI0Qf5$s^*$@bO*nU-tvm z6A1e@%nkjl5&(yZIB--tW|htNv6EJ>1*5dPA?1Fjn)wSU3BC&#o?p0$*FTAi0@CcBy; zivB05&0h!Sa;wIigX{vbGS?WfFpKW1j$`xxe@A0+Fjute=e);0#qr=|N;g;Qp}26- zm!wpQe2Y-OMxRZT!KmUk+r1SsoU&a;7EJgg5$6I(Z;bR({yW=V$Ml=nc3hdHZQXv7 zk&*ChA@K6Mqnq{Lef_ZWj5wLT-*hV3RZX5 zcaNEiT`9AAeK|ETMLBoO$)%0vU^|}jjQ=h(Pd8tzyomPJ&HV7%yc(yaoL-K4)b8u0 z-o!mp3>|%cjDtFz<`I6Kq*MkTy0gqRuu%`BM~{JXH<+tC2K3e5N;MPqbh$+S6#dsr zcdBVZ@Lsd6FJKRjy=c+pyH&M)s2MAZV?aF@5f7?v+zrnc{ysKd4YGJY8r);1Hj|K0 z!w1X$Ox?tSP~U)kl0lSIBL^ArD!&mUQ#f%lvi)leQC!&QnN(xX^%&4?Ujx@}7{&jP#Ju`vyy4=?)Zv=teG9Yg@>H*$! z+yL?q3Ddd+-D5=3wK)xtfKT7+nCr}Zjylu%D!DTduwszFjDqffoh7iB{RjcWZN88& z$FclLXg-cn**tVI$x?5QGU)o>-jP&JEkx^eB+L-WZ~#jz!EdblBCYn%h*gswL$I=g z^$IUZk6}Ibrt8N3ZfW}b=&I$1a_@xH&laJi*ojhPvP*Az2T0lQ`8{>~@+-KLpJu)` z*3{@0^GtT>e@6L6z4~*%UUXv4(v-AGzvV`!?5MQO5K)e76kr3;19Tpms$7x|Y{_<0kcg`LIGFLU7-5 z`4|*~xZ6AGJt^6<4jaE(n6Lm<$fFk=KXB(=tZUnjM~ADgGs>S*y2{yG4U=ZCPhPB7 zcw|};z2E1{gK+;~Wph{Dc^OwheF47*ZcWloE!Z3H08r|^`RJB=ay@1~Lrf|?_i}Oq z_Le}7F&`ZCAHp~R`b<(I(-E+n)U*W}FCSIUl=80$O<3a>@VEiUJlIB-*!%-KJ>clx zir7r*D}od?i|LW;nZ?`N-T@*Xq821TCtAK`At1%NLv+EyIZlPC0Db zw8pq&a=*9#c*;b-=^s$~0!l zR1O+>`Qt&-V8#WB#2a;5zaO*d|2X=MKRT!vVN>vu9AdajR3$|?CLAysMXiKswYCvv z<+?=2wa$$sJYuA$g>7~wV7M?w;qyLMxSS7oh{cj(NfhFPJN0!f)!=OUGarR?RM&(h zdf7wMqQQhVSgSh9Kg%E4Y7_}+5)}+glIDLpOQ0?e$$d?F6lXsj2X$*|)8H{&{!4ikZ?z@fMO({gF9N zu>g==a|C=GOqkwE6WE-Lkdb?&zMb={5iO$rKcCl64f;TD3;OnXf4wYujDBCLfN!M& zR^w~2O31F_(5PN*@GERUVeqt(WNxbaKO}vmXdBFc&SrR>{wauhO;hJ|R%$f;T4}%6 zy5`l9syWsC`N<-=ge33oQlKzm`*_|~3F(eD$s1^Ik$eoN&gi5Lk1|ER6lkmtwDL;)N;hf;W3cxK^K;t6>*csn3N)T?= zeC?<4fNY2r@T{ZFCNuzyTr%_nxB!aJ#BUuW1DhLQ@DJSoX20Qjq5T6d-27+aU(7Oz zO$W*ULe8+2Fxo+Qe6%ZYRGXqtBuUyzx8O}UPyWk_cOy+ z)1A;6-Ie*0@5VN^`g3`jtCLXa@y~p>gkVw%q8~J-RC`qdt^D&BVgnzEKHts0!=v?{ zU+Kr}a=`Ym>)RHd8c^wi%Co2dmD@33nl9U>VL8+fYvvgD;w90j)1pW(Y|WT27U<8l zg(D{bGUr-ep5#fS7=s;gGf$d>4VhSEpI4ig4=;HAp)DAN@82_oXQkv;SU%A1l zFZqa<_ChV06y4axE2)>m`(+k1jj&c-ii3>YCVkWg!~$C4T)aDm=J5iD2K;AQuOr2= z%6<-Oj=K;2lgkRo`-<9qCRXQ|6@cY zHY=bRN59BE#1IE@KIEp*`E{iE(C1K}Vp8Omr5surt3o{K3u-1mlrd2oNAVxCv3&+>`)2uZuPU+`am?X zQ~1DViS{Fkd8G-5=e^{uG!yU^)n`$>Wq)a?p4Xv@bRHzL&7IBSk6^rxkrn11^|W*f z(R(0Urir}kuSgmYbjQ(2Yh@xz4$}X8;xm7eVsiJ3{FN8`dFBstU0Q{Tzr2(SWg^YW zo!;p>AQ}f}H{fIoC=2g^;GcLAwB7=vB4BR<5HuSZfxNr}AliZ&d`35cdP#+RrlGuA zBpLVjS)5<&bG*|V>y@YEbLndRp@Och^1_Yrzgxd*`4YL<3+@B*bC?2}%pHPP^&+SR z;RuPwfd^(dO*bXAbgwmJPWv*~Uea4RiCI4%S#B0N0%)8z58NZl5oq-UyFo(xY1JQm ztr@=zrfwm~U;5h&wtE(RDN)Js_i$KJhcUC_IQh$S@b&($>|HFVcWDLPu+(8NHw%nG7 z=eyGL7N)7VS>l;z?YZd&wFLe9n1@u!5t1n(1a3bQZVG9Sm{SuzhRK$GbS&w?IcaGo z4jA?+m(6>SktF>tqwU08S|k0d8M7yWf`@Z%i)&lv6?4em`j1goNr$syM%M;Py_d=S zqxHP6WGVF2pE+$C;ttvxd6I`$k2M6-a@KT}ALFfAM{kiwf6em58GiZQ%ZzYlh1P;^ ziO>6i=#-XVn6xHxOCK1>fgL)ln}exSNcO9nlDU2QBt}%dNd`IGK^bt}Maxcx6?Si6 zV|nACa0dTy&tmE(rB!AEk4sH}!JdXsD0N!aL^3P$SH_t%Lxdb>%P4Sav~Q2~)D2kD zU_~()2B3!lJ>p>W!u+MaSYuPeiy$1uS%&6#Rc6A0fv6tbt*Fp{S0dgAmu(p=Qufg?b^khJAxx zC#7p+x@X4Mj%4Tx#&QGCJ^@lGsD6@WqpZPA&J!dJemkt5Q8gTOB)32PH6%H zxDu0JKqSDzEp0`?eBCb}EWYbMmAPQV8>6!CWxH|rgcjb2 zH9EZSHB$_>t`{~&fWGiHZ0;3r-BP8`yA{GC-jW;CRQuVZL-@VM08`N@FdE;~RPI`~&wF z`+Xv+vs^985hQ2%EPOq66Zr_Z4A|KKB}W3K-YV2Bv!P+%ZB`E4PHs=|5!lzLMmZR4 zz-HcDA9PmG5HI_*ImFo9(W@w&aBv}AZwZJn8|5|XFM64#b^YBcy`1NmLv^5K#N#{u z3gu-ieqPyK2LY3T&0U=&(dUjDBeS3BO^-)~FC*~xKoR}hlF;(&)2u-k8~m+myRo@-`h2lZS%!2D(H}qM z2`qce&g=lDFOB{{+X1D>2tO4Yc~|eQkcRIuGGV;E469pK^9_&e+rziE#^^!>&GmC7 zH)?M$i)(T9{=rus4p;OfIjvcr97~3#&p~B^? z{ycBzE`#4pzMFnGyL>G3aD{4CQT@}9_ zWgJrwtt4ICi+IU+uMLAd%}#ihr?tsd)~_cLz3(m`y+>$ zh|}ie>}d$$UNInk-2YjuR&%m)WZ7Df)y}m)730^xQ!J?+eL2sw)Lnt?{QB%595byk z|CY&*hFmINUgVJD30YQ=n+~UaeNr3N^;Jxbw|%rR;vQAL{!6>WWJ8Tl%%RA&!emuu z$`>q^3ix^YmMU&M+Un)jYG-MiyVLLeJu|gg-n-51{QfwgP4FVs-44Lcui!lBU%c0F z4PDfNTfLb}eWMXq9iF-7pXwur+8a3SZq8YHIf6l78chSW_JC;mkM1YhfTP)>Rs@wv z*2g-YO)at1{>mz&rPiMObWk@fj0fZE zU|4e%Hhu<~cnn;(e*@31m};s$dQ}{94W;l&0!yk*WAlKkh)TV%AcMz18w*XGEb`*| z!SD$BPgp=)2S|H2Hxy`)R%lH>@BPWt@9PKGi+lOfi1SHI4F`YvzMuKn{xzX!7OFnj z31+H}0R8JdqY!ee4Bg2~KIc25Sk33hx8tn?ws{q4&K6U5c528s}nNYFKpHV8L) z!bhMD?YuLGcj&5TG?+4yHvUkF)3bc03K|F`li8k(HAr1u&?87mUEu294BYx| zcp{H>m&b!apl0@G^0Bsz&yoIX#hEl7#G9E8m>J9O?dcnCWiCv)c+)Y05Eeb#D#ctw zL(zwfPfz<&_+%%NCJBU`Il_-X1#$}rwkk39`5@GPNBX#Pq`>6z%T8LZ2CqOQ1- zK%x{zS;M+qya&5NIdC0-2!8U!;mlyhM|Il-e~qO+Jr3G}R_-~Z?84BW#TnQ#N5ZBJ z?ZC|UYY5Y@sy{;{p+}s8i?D(}^Ilti#kN+{6mmgq*9-xo|6>gu8g|I^T0c#W>9l~4 zcJC<&zW;(-2E+gUN8yp+@K?e*2tnsvc7{mkXv@-|`v9e6vu5+-!F2uoX4Y3EeM``U z#6WNF!*1FE3$oRrY77^FVNbPbP6+#Kf01 z?ZQfifVlKN3P64w(u+ypAEaWe2j1C>hV{!a`rR=J5DAdDq3~!iz(e^&dc9I>$&zZi9;S6%}{BH z_$bUvE~%eLIw{qx!>CK@f;GBb0?zMJDCJ1-m-{o@&se5GQQ5G5o`;p{AKWjlB{nye zHo;y*1o*^@S}6JkXhQVxn%uh{`>nmpke}Age}ZN8<*yqLoligV_9UbCU@l6%-WMH7 z2XBHYpAs8>>{T}Z(Q&`MirXMy&;lHcp>ub-><=!UOn;JaIptS(R0aJ<*ciK09(Se@ z3J)$&prFL(D zFI__f;*@fp;Ic3pN!UrHEQ=8qwcL!lDNu(LULuY?9uhXZL9yk}>)?nGgchdt)LXCE z$wxgr{r-ztp`6{{gpKYb?mmi{48HE~s7`+5N#4$0^h3oG#uW_l_N@_z{Y zo<#Y?x`!gF+ksm< z4+@ge8SL28X*`G#p40jaHs=ilZ~TrCN#HbY6=NyUe{|{?=6=5d!7yX@zx7=*NZImd zoYYqc5>BVvry+X0hwG8<5c8{zRrB>M=S5q>ZwGCTp(5iR{`~G3UZ0rzX>q*-aYd}b zk@<|Oh`RG+C#$XqouNT>!r-!8x%Ww^Y=P#y28uEAf?JD~p$&{ze#Rwy_<3YvbC zXW>#Ga#s-Ni}t8OWU8Xw+0-i(^zQjT0MvArmAF7x4 zp0hzgsldcI0+xnO(RW5bq<)EH<2Vx+&Jl)yD3{tnas^_pojNgsAgIIahnz0<)T3KR zFMx_{k+DKWA|OIw0S^V2y@?D^C?R*~2hM}=R-m;3-rh&IddE5e5%=9;4UPbf*MFk! z%2wRiEdeuQz6n5ngf6cvpsN*%yI`6S9r!IHp)(*o5};`!Y|d-UdrXA=F-fXSttzG0 z;pSoF-hY$(8|XR)ZpxjYBnL z{*sX;Xt)sezdU{iW`P9Z5!JxqD=Ix!FPrgj&%(pd-}Yr-YS}mYQD0pB9e+W$y-Atsj}UH) zw~aGyPl8le;w32G-5b;)su_5h+%jz2;JQvCM4s4KCcf-~5={j~ya@P*O0PL$GMqf_ zZ_(ec)OIipIR4jxQa#|e0g|KV@UwZKop4o7-DA4Mr#_!j&j`n|zjRwfD+j}OeuVAiOjxG=q~kh&!a{UB=%u2u%ji+u7X5_AKWo|?J5pk{f}?+2zt2S3izWJ+$Qbz^(G7m10Z{)I0o?%e z4^ez^t6aYby5t7c%9F3rCua&ZF?ql9PqHpe*gkd0bCQ)!ET|&-}r!RUVrCRc5 z^m_>$mzhf#x-+*AGuT4%HG_J6uz-#U+(Y85Ni7!B^EX_jXq!%aW~l2SGm(;c39I!7 zZu930>4rmLFFZ@v)j$KxNtN(;|LPb!>DyCZXAHc}KTL>D^_v|Te_TtECy*%0{J7-& zwZv@o^OZ0|r)#bB{uf~}+BciKYSvZw{}=z?foo~okMh>;!vhcM#I^`TV-qU3v}XnC z^!uonj{|)>Mw`>3|qTi@f(8-JC6KTz!NY4Ho;3H)XVAbDlpHnGdVXy%v zc~R+${^xgwk?@xQ(zgzTDsLyBL#53sC9agEh-uth1g)YR|i=axz z#?Ihqz(`pP?8Rd{Cpk_7>8=JCzhr}$fE3RqU#h%#%F7Eh3=8tNg6PGH2UiH+Hvc`= zV(_zVj${P6@MJ5fJe)shB;=h52^qo=dJfhp{Qu^m{)P-Hmj%Et-?T|)ttjsGOi)>s$tPSrJB#}hfs73BN) z`a{<7A7T3Ib1Gr>?YqC$ZW#}YF<&p3v#TjbLMOmGN&q=)q#miUGD^^!(Rsug@$}-U zcFKxS*P9~qo@;r9qeXlXy@#~btsV)eNvBkp)~acsS8FW!-KMGz+lHY>eA2;oY6wPo zyK2R#EC+>-(pna!b(W}KWv`t}A{0&4pS3VQv(+&9ijI%bRRgewBNy0m z-_o7`!fu%I4L5j z+rpw7yHN@@zRHXx<(8jyXZTzo3|iEsGIeiC#$qSzjXo3ZKauGW5p@1QYkfV4u8z&2 z%a_x3a>Ml%tu4}S&D=ob+uaE3z@u4h3A(=cu3Mi|0bC5`Y*8>eQ1!ps7{?z)ij1Mh zzXf!;ndoAVG7i4cy?^|@qigQ5cx?@C`uwsUn-K}A4bCxR_#ODX0j6M1Mo6<%5QkEl zI`*O6>*pWzdk+@>7aFSO_=5LMkcD$<6ET|;{xV54@Pjy9vP8lt)e$Dmiu z`$IoGR+__^eND8#${5jmV?R374HW`> z=U@}PG;Yf&sS*g-hBi775*(COeBn`a?!W~2?xIV$2fr@i#h~ITGTjS#w;&Hd`TMx@ zx-3aQoa6j9{QxsJnG4HJ`*YTS4{*}#n1k4AL!WG_dM#z39f|d-=E=Be>w%b)gU`DC z=HM{)`sc||HoERco39lS7|4$Ohb;pI;w+EBk%iC`R`;fw0Bl1(9SF?0`FMSN<4qoo~5FFGp4cU1xX2q@mNgSSa zof+R{ySQS*J5QabFPH(Bc==v&LGu^cd%A^uS&aE_SM!BZi+QtPO-?t1uUCQ0zpDkW zQ~1>}jP@&~zZB0Sfh>#?OhF;V9Y2FC5EpRU`WUzZG<0<6!p~K@{tNaxKk{h5=R

mhAdnTtjv@ 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 literal 0 HcmV?d00001 From 3e2845181c5f33980ffbdb96714f6733439bd93a Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 17 Mar 2026 17:38:09 -0700 Subject: [PATCH 54/60] bumping next version --- ui/litellm-dashboard/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/package.json b/ui/litellm-dashboard/package.json index 2a9bb3e5e20..fce2e09b54c 100644 --- a/ui/litellm-dashboard/package.json +++ b/ui/litellm-dashboard/package.json @@ -35,7 +35,7 @@ "jwt-decode": "^4.0.0", "lucide-react": "^0.513.0", "moment": "^2.30.1", - "next": "^16.1.6", + "next": "^16.1.7", "openai": "^4.93.0", "papaparse": "^5.5.2", "react": "^18.3.1", From 62835ff03dbbcd4d02cc49163a99a1e726ff2dd9 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 17 Mar 2026 17:44:01 -0700 Subject: [PATCH 55/60] adding package-lock --- ui/litellm-dashboard/package-lock.json | 164 ++++++++++++++++++------- 1 file changed, 118 insertions(+), 46 deletions(-) diff --git a/ui/litellm-dashboard/package-lock.json b/ui/litellm-dashboard/package-lock.json index c062356ebbd..2b62c1c16bb 100644 --- a/ui/litellm-dashboard/package-lock.json +++ b/ui/litellm-dashboard/package-lock.json @@ -23,7 +23,7 @@ "jwt-decode": "^4.0.0", "lucide-react": "^0.513.0", "moment": "^2.30.1", - "next": "^16.1.6", + "next": "^16.1.7", "openai": "^4.93.0", "papaparse": "^5.5.2", "react": "^18.3.1", @@ -92,6 +92,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -1773,6 +1774,7 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -1783,6 +1785,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1792,12 +1795,14 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1828,9 +1833,9 @@ } }, "node_modules/@next/env": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz", - "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==", + "version": "16.1.7", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.7.tgz", + "integrity": "sha512-rJJbIdJB/RQr2F1nylZr/PJzamvNNhfr3brdKP6s/GW850jbtR70QlSfFselvIBbcPUOlQwBakexjFzqLzF6pg==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -1844,9 +1849,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz", - "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==", + "version": "16.1.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.7.tgz", + "integrity": "sha512-b2wWIE8sABdyafc4IM8r5Y/dS6kD80JRtOGrUiKTsACFQfWWgUQ2NwoUX1yjFMXVsAwcQeNpnucF2ZrujsBBPg==", "cpu": [ "arm64" ], @@ -1860,9 +1865,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz", - "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==", + "version": "16.1.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.7.tgz", + "integrity": "sha512-zcnVaaZulS1WL0Ss38R5Q6D2gz7MtBu8GZLPfK+73D/hp4GFMrC2sudLky1QibfV7h6RJBJs/gOFvYP0X7UVlQ==", "cpu": [ "x64" ], @@ -1876,9 +1881,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz", - "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==", + "version": "16.1.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.7.tgz", + "integrity": "sha512-2ant89Lux/Q3VyC8vNVg7uBaFVP9SwoK2jJOOR0L8TQnX8CAYnh4uctAScy2Hwj2dgjVHqHLORQZJ2wH6VxhSQ==", "cpu": [ "arm64" ], @@ -1892,9 +1897,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz", - "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==", + "version": "16.1.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.7.tgz", + "integrity": "sha512-uufcze7LYv0FQg9GnNeZ3/whYfo+1Q3HnQpm16o6Uyi0OVzLlk2ZWoY7j07KADZFY8qwDbsmFnMQP3p3+Ftprw==", "cpu": [ "arm64" ], @@ -1908,9 +1913,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz", - "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==", + "version": "16.1.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.7.tgz", + "integrity": "sha512-KWVf2gxYvHtvuT+c4MBOGxuse5TD7DsMFYSxVxRBnOzok/xryNeQSjXgxSv9QpIVlaGzEn/pIuI6Koosx8CGWA==", "cpu": [ "x64" ], @@ -1924,9 +1929,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz", - "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==", + "version": "16.1.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.7.tgz", + "integrity": "sha512-HguhaGwsGr1YAGs68uRKc4aGWxLET+NevJskOcCAwXbwj0fYX0RgZW2gsOCzr9S11CSQPIkxmoSbuVaBp4Z3dA==", "cpu": [ "x64" ], @@ -1940,9 +1945,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz", - "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==", + "version": "16.1.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.7.tgz", + "integrity": "sha512-S0n3KrDJokKTeFyM/vGGGR8+pCmXYrjNTk2ZozOL1C/JFdfUIL9O1ATaJOl5r2POe56iRChbsszrjMAdWSv7kQ==", "cpu": [ "arm64" ], @@ -1956,9 +1961,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz", - "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==", + "version": "16.1.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.7.tgz", + "integrity": "sha512-mwgtg8CNZGYm06LeEd+bNnOUfwOyNem/rOiP14Lsz+AnUY92Zq/LXwtebtUiaeVkhbroRCQ0c8GlR4UT1U+0yg==", "cpu": [ "x64" ], @@ -1975,6 +1980,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -1988,6 +1994,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -1997,6 +2004,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -2320,7 +2328,7 @@ "version": "1.58.1", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.1.tgz", "integrity": "sha512-6LdVIUERWxQMmUSSQi0I53GgCBYgM2RpGngCPY7hSeju+VrKjq3lvs7HpJoPbDiY5QM5EYRtRX5fvrinnMAz3w==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "playwright": "1.58.1" @@ -3425,12 +3433,14 @@ "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.2.48", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.48.tgz", "integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==", + "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -3472,6 +3482,7 @@ "version": "0.26.0", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.26.0.tgz", "integrity": "sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA==", + "dev": true, "license": "MIT" }, "node_modules/@types/unist": { @@ -4332,12 +4343,14 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, "license": "MIT" }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -4351,6 +4364,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -4363,6 +4377,7 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, "license": "MIT" }, "node_modules/argparse": { @@ -4734,6 +4749,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4759,6 +4775,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -4874,6 +4891,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -4997,6 +5015,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -5021,6 +5040,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -5096,6 +5116,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -5156,6 +5177,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -5569,12 +5591,14 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, "license": "Apache-2.0" }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, "license": "MIT" }, "node_modules/doctrine": { @@ -6488,6 +6512,7 @@ "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -6520,6 +6545,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -6557,6 +6583,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -6717,6 +6744,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -6867,6 +6895,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -7364,6 +7393,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -7416,6 +7446,7 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -7476,6 +7507,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -7521,6 +7553,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -7569,6 +7602,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -7845,6 +7879,7 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -8130,6 +8165,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, "license": "MIT", "engines": { "node": ">=14" @@ -8142,6 +8178,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, "license": "MIT" }, "node_modules/locate-path": { @@ -8595,6 +8632,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -9167,6 +9205,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -9180,6 +9219,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -9294,6 +9334,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0", @@ -9343,14 +9384,14 @@ "license": "MIT" }, "node_modules/next": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz", - "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==", + "version": "16.1.7", + "resolved": "https://registry.npmjs.org/next/-/next-16.1.7.tgz", + "integrity": "sha512-WM0L7WrSvKwoLegLYr6V+mz+RIofqQgVAfHhMp9a88ms0cFX8iX9ew+snpWlSBwpkURJOUdvCEt3uLl3NNzvWg==", "license": "MIT", "dependencies": { - "@next/env": "16.1.6", + "@next/env": "16.1.7", "@swc/helpers": "0.5.15", - "baseline-browser-mapping": "^2.8.3", + "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" @@ -9362,14 +9403,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.1.6", - "@next/swc-darwin-x64": "16.1.6", - "@next/swc-linux-arm64-gnu": "16.1.6", - "@next/swc-linux-arm64-musl": "16.1.6", - "@next/swc-linux-x64-gnu": "16.1.6", - "@next/swc-linux-x64-musl": "16.1.6", - "@next/swc-win32-arm64-msvc": "16.1.6", - "@next/swc-win32-x64-msvc": "16.1.6", + "@next/swc-darwin-arm64": "16.1.7", + "@next/swc-darwin-x64": "16.1.7", + "@next/swc-linux-arm64-gnu": "16.1.7", + "@next/swc-linux-arm64-musl": "16.1.7", + "@next/swc-linux-x64-gnu": "16.1.7", + "@next/swc-linux-x64-musl": "16.1.7", + "@next/swc-win32-arm64-msvc": "16.1.7", + "@next/swc-win32-x64-msvc": "16.1.7", "sharp": "^0.34.4" }, "peerDependencies": { @@ -9505,6 +9546,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9523,6 +9565,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -9867,6 +9910,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, "license": "MIT" }, "node_modules/path-scurry": { @@ -9913,6 +9957,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -9925,6 +9970,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9934,6 +9980,7 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -9943,7 +9990,7 @@ "version": "1.58.1", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.1.tgz", "integrity": "sha512-+2uTZHxSCcxjvGc5C891LrS1/NlxglGxzrC4seZiVjcYVQfUa87wBL6rTDqzGjuoWNjnBzRqKmF6zRYGMvQUaQ==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "playwright-core": "1.58.1" @@ -9962,7 +10009,7 @@ "version": "1.58.1", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.1.tgz", "integrity": "sha512-bcWzOaTxcW+VOOGBCQgnaKToLJ65d6AqfLVKEWvexyS3AS6rbXl+xdpYRMGSRBClPvyj44njOWoxjNdL/H9UNg==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" @@ -9985,6 +10032,7 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -10013,6 +10061,7 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.0.0", @@ -10030,6 +10079,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -10055,6 +10105,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, "funding": [ { "type": "opencollective", @@ -10097,6 +10148,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, "funding": [ { "type": "opencollective", @@ -10122,6 +10174,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -10135,6 +10188,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, "license": "MIT" }, "node_modules/prelude-ls": { @@ -10248,6 +10302,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, "funding": [ { "type": "github", @@ -11036,6 +11091,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, "license": "MIT", "dependencies": { "pify": "^2.3.0" @@ -11045,6 +11101,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -11057,6 +11114,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -11354,6 +11412,7 @@ "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.1", @@ -11394,6 +11453,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -11449,6 +11509,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "funding": [ { "type": "github", @@ -12089,6 +12150,7 @@ "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", @@ -12124,6 +12186,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -12159,6 +12222,7 @@ "version": "3.4.19", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -12196,6 +12260,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -12212,6 +12277,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -12239,6 +12305,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0" @@ -12248,6 +12315,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, "license": "MIT", "dependencies": { "thenify": ">= 3.1.0 < 4" @@ -12289,6 +12357,7 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -12355,6 +12424,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -12442,6 +12512,7 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, "license": "Apache-2.0" }, "node_modules/tsconfig-paths": { @@ -12558,7 +12629,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -12760,6 +12831,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, "license": "MIT" }, "node_modules/uuid": { @@ -13213,7 +13285,7 @@ "version": "8.19.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" From fc315ab4af0b05b1b025db6f2a9d9e798da7c70c Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 17 Mar 2026 17:45:16 -0700 Subject: [PATCH 56/60] docs(mcp_zero_trust): add MCP zero trust auth guide (#23918) * docs(mcp_zero_trust): add MCP zero trust auth guide with hero image * fix(docs): move hero image to static/img/ for Docusaurus build --- docs/my-website/docs/mcp_zero_trust.md | 294 ++++++++++++++++++ .../static/img/mcp_zero_trust_gateway.png | Bin 0 -> 301348 bytes 2 files changed, 294 insertions(+) create mode 100644 docs/my-website/docs/mcp_zero_trust.md create mode 100644 docs/my-website/static/img/mcp_zero_trust_gateway.png diff --git a/docs/my-website/docs/mcp_zero_trust.md b/docs/my-website/docs/mcp_zero_trust.md new file mode 100644 index 00000000000..8f431523cb8 --- /dev/null +++ b/docs/my-website/docs/mcp_zero_trust.md @@ -0,0 +1,294 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# MCP Zero Trust Auth (JWT Signer) + +![Zero Trust MCP Gateway](/img/mcp_zero_trust_gateway.png) + +MCP servers have no built-in way to verify that a request actually came through LiteLLM. Without this guardrail, any client that can reach your MCP server directly can call tools — bypassing your access controls entirely. + +`MCPJWTSigner` fixes this. It signs every outbound tool call with a short-lived RS256 JWT. Your MCP server verifies the signature against LiteLLM's public key. Requests that didn't go through LiteLLM have no valid signature and are rejected. + +--- + +## Basic setup + +Add the guardrail to your config and point your MCP server at LiteLLM's JWKS endpoint. Every tool call gets a signed JWT automatically — no changes needed on the client side. + +```yaml title="config.yaml" +mcp_servers: + - server_name: weather + url: http://localhost:8000/mcp + transport: http + +guardrails: + - guardrail_name: mcp-jwt-signer + litellm_params: + guardrail: mcp_jwt_signer + mode: pre_mcp_call + default_on: true + issuer: "https://my-litellm.example.com" # defaults to request base URL + audience: "mcp" # default: "mcp" + ttl_seconds: 300 # default: 300 +``` + +**Bring your own signing key** — recommended for production. Auto-generated keys are lost on restart. + +```bash +export MCP_JWT_SIGNING_KEY="-----BEGIN RSA PRIVATE KEY-----\n..." +# or point to a file +export MCP_JWT_SIGNING_KEY="file:///secrets/mcp-signing-key.pem" +``` + +**Build a verified MCP server with [FastMCP](https://gofastmcp.com):** + +```python title="weather_server.py" +from fastmcp import FastMCP, Context +from fastmcp.server.auth.providers.jwt import JWTVerifier + +auth = JWTVerifier( + jwks_uri="https://my-litellm.example.com/.well-known/jwks.json", + issuer="https://my-litellm.example.com", + audience="mcp", + algorithm="RS256", +) + +mcp = FastMCP("weather-server", auth=auth) + +@mcp.tool() +async def get_weather(city: str, ctx: Context) -> str: + caller = ctx.client_id # JWT `sub` — the verified user identity + return f"Weather in {city}: sunny, 72°F (requested by {caller})" + +if __name__ == "__main__": + mcp.run(transport="http", host="0.0.0.0", port=8000) +``` + +FastMCP fetches the JWKS automatically and re-fetches when the signing key changes. + +LiteLLM publishes OIDC discovery so MCP servers find the key without any manual configuration: + +``` +GET /.well-known/openid-configuration → { "jwks_uri": "https:///.well-known/jwks.json" } +GET /.well-known/jwks.json → { "keys": [{ "kty": "RSA", "alg": "RS256", ... }] } +``` + +> **Read further only if you need to:** thread a corporate IdP identity into the JWT, enforce specific claims on callers, add custom metadata, use AWS Bedrock AgentCore Gateway, or debug JWT rejections. + +--- + +## Thread IdP identity into MCP JWTs + +By default the outbound JWT `sub` is LiteLLM's internal `user_id`. If your users authenticate with Okta, Azure AD, or another IdP, the MCP server sees a LiteLLM-internal ID — not the user's email or employee ID. + +With verify+re-sign, LiteLLM validates the incoming IdP token first, then builds the outbound JWT using the real identity claims from that token. The MCP server gets the user's actual identity without ever having to trust the original IdP directly. + +```yaml title="config.yaml" +guardrails: + - guardrail_name: mcp-jwt-signer + litellm_params: + guardrail: mcp_jwt_signer + mode: pre_mcp_call + default_on: true + issuer: "https://my-litellm.example.com" + + # Validate the incoming Bearer token against the IdP + access_token_discovery_uri: "https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration" + verify_issuer: "https://login.microsoftonline.com/{tenant}/v2.0" + verify_audience: "api://my-app" + + # Which claim to use for `sub` in the outbound JWT — first non-empty value wins + end_user_claim_sources: + - "token:sub" # from the verified incoming JWT + - "token:email" # fallback to email + - "litellm:user_id" # last resort: LiteLLM's internal user_id +``` + +If the incoming token is **opaque** (not a JWT — some IdPs issue these), add an introspection endpoint. LiteLLM will POST the token to it (RFC 7662) and use the returned claims: + +```yaml + token_introspection_endpoint: "https://idp.example.com/oauth2/introspect" +``` + +**Supported `end_user_claim_sources` values:** + +| Source | Resolves to | +|--------|-------------| +| `token:` | Any claim from the verified incoming JWT (e.g. `token:sub`, `token:email`, `token:oid`) | +| `litellm:user_id` | LiteLLM's internal user ID | +| `litellm:email` | User email from LiteLLM auth context | +| `litellm:end_user_id` | End-user ID if set separately | +| `litellm:team_id` | Team ID from LiteLLM auth context | + +--- + +## Block callers missing required attributes + +Some MCP servers expose sensitive operations that should only be reachable by verified employees — not service accounts, not external API keys. You can enforce this at the LiteLLM layer so the MCP server never receives the request at all. + +`required_claims` rejects with `403` if the incoming token is missing any listed claim. `optional_claims` forwards claims that are useful but not mandatory. + +```yaml title="config.yaml" +guardrails: + - guardrail_name: mcp-jwt-signer + litellm_params: + guardrail: mcp_jwt_signer + mode: pre_mcp_call + default_on: true + + access_token_discovery_uri: "https://idp.example.com/.well-known/openid-configuration" + + # Service accounts without `employee_id` are blocked before the tool runs + required_claims: + - "sub" + - "employee_id" + + # Forward these into the outbound JWT when present — skipped silently if absent + optional_claims: + - "groups" + - "department" +``` + +**What the client sees when blocked:** +```json +HTTP 403 +{ "error": "MCPJWTSigner: incoming token is missing required claims: ['employee_id']. Configure the IdP to include these claims." } +``` + +--- + +## Add custom metadata to every JWT + +Your MCP server may need context that LiteLLM doesn't carry natively — which deployment sent the request, a tenant ID, an environment tag. Use claim operations to inject, override, or strip claims from the outbound JWT. + +```yaml title="config.yaml" +guardrails: + - guardrail_name: mcp-jwt-signer + litellm_params: + guardrail: mcp_jwt_signer + mode: pre_mcp_call + default_on: true + + # add: insert only when the key is not already in the JWT + add_claims: + deployment_id: "prod-us-east-1" + tenant_id: "acme-corp" + + # set: always override — even if the claim came from the incoming token + set_claims: + env: "production" + + # remove: strip claims the MCP server shouldn't see + remove_claims: + - "nbf" # some validators reject nbf; remove it if yours does +``` + +Operations run in order — `add_claims` → `set_claims` → `remove_claims`. `set_claims` always wins over `add_claims`; `remove_claims` beats both. + +--- + +## AWS Bedrock AgentCore Gateway + +Bedrock AgentCore Gateway uses two separate JWTs: one to authenticate the transport connection and another to authorize tool calls. They need different `aud` values and TTLs — a single JWT won't work for both. + +LiteLLM can issue both in one hook and inject them into separate headers: + +```yaml title="config.yaml" +guardrails: + - guardrail_name: mcp-jwt-signer + litellm_params: + guardrail: mcp_jwt_signer + mode: pre_mcp_call + default_on: true + issuer: "https://my-litellm.example.com" + audience: "mcp-resource" # for the MCP resource layer + ttl_seconds: 300 + + # Second JWT for the transport channel — same sub/act/scope, different aud + TTL + channel_token_audience: "bedrock-agentcore-gateway" + channel_token_ttl: 60 # transport tokens should be short-lived +``` + +LiteLLM injects two headers on every tool call: +- `Authorization: Bearer ` — audience `mcp-resource`, TTL 300s +- `x-mcp-channel-token: Bearer ` — audience `bedrock-agentcore-gateway`, TTL 60s + +Both tokens are signed with the same LiteLLM key, so your MCP server only needs to trust one JWKS endpoint. + +--- + +## Control which scopes go into the JWT + +By default LiteLLM generates least-privilege scopes per request: +- Tool call → `mcp:tools/call mcp:tools/{name}:call` +- List tools → `mcp:tools/call mcp:tools/list` + +If your MCP server does its own scope enforcement and needs a specific format, set `allowed_scopes` to replace auto-generation entirely: + +```yaml title="config.yaml" +guardrails: + - guardrail_name: mcp-jwt-signer + litellm_params: + guardrail: mcp_jwt_signer + mode: pre_mcp_call + default_on: true + + allowed_scopes: + - "mcp:tools/call" + - "mcp:tools/list" + - "mcp:admin" +``` + +Every JWT carries exactly those scopes regardless of which tool is being called. + +--- + +## Debug JWT rejections + +Your MCP server is returning 401 and you're not sure what's in the JWT. Enable `debug_headers` and LiteLLM adds a `x-litellm-mcp-debug` response header with the key claims that were signed: + +```yaml title="config.yaml" +guardrails: + - guardrail_name: mcp-jwt-signer + litellm_params: + guardrail: mcp_jwt_signer + mode: pre_mcp_call + default_on: true + debug_headers: true +``` + +Response header: +``` +x-litellm-mcp-debug: v=1; kid=a3f1b2c4d5e6f708; sub=alice@corp.com; iss=https://my-litellm.example.com; exp=1712345678; scope=mcp:tools/call mcp:tools/get_weather:call +``` + +Check that `kid` matches what the MCP server fetched from JWKS, `iss`/`aud` match your server's expected values, and `exp` hasn't passed. Disable in production — the header leaks claim metadata. + +--- + +## JWT claims reference + +| Claim | Value | +|-------|-------| +| `iss` | `issuer` config value (or request base URL) | +| `aud` | `audience` config value (default: `"mcp"`) | +| `sub` | Resolved via `end_user_claim_sources` (default: `user_id` → api-key hash → `"litellm-proxy"`) | +| `act.sub` | `team_id` → `org_id` → `"litellm-proxy"` (RFC 8693 delegation) | +| `email` | `user_email` from LiteLLM auth context (when available) | +| `scope` | Auto-generated per tool call, or `allowed_scopes` when set | +| `iat`, `exp`, `nbf` | Standard timing claims (RFC 7519) | + +--- + +## Limitations + +- **OpenAPI-backed MCP servers** (`spec_path` set) do not support JWT injection. LiteLLM logs a warning and skips the header. Use SSE/HTTP transport servers to get full JWT injection. +- The keypair is **in-memory by default** and rotated on each restart unless `MCP_JWT_SIGNING_KEY` is set. FastMCP's `JWTVerifier` handles key rotation transparently via JWKS key ID matching. + +--- + +## Related + +- [MCP Guardrails](./mcp_guardrail) — PII masking and blocking for MCP calls +- [MCP OAuth](./mcp_oauth) — upstream OAuth2 for MCP server access +- [MCP AWS SigV4](./mcp_aws_sigv4) — AWS-signed requests to MCP servers diff --git a/docs/my-website/static/img/mcp_zero_trust_gateway.png b/docs/my-website/static/img/mcp_zero_trust_gateway.png new file mode 100644 index 0000000000000000000000000000000000000000..3955cef0553247c7730aaef9f77dc24219c2a6c0 GIT binary patch literal 301348 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�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

k$ez1@E~m^s%tb)ASor$q>&w5 zUBH!6Yw&2!lsXlU$aBsin0Hnrm&lfvIp5RmwGJ6__!+o2R;z~_ zXP*kUu2^w}BDLUaCFKEAT1%#};i1==Qd5;pX@Ta>WbV@DVF*kz2{LBN6dER)xjQH? z!#|Ft>q4lH8VN)N;T}fTkH8vM)2uIB%bkDaJI#JsEw*xl3p5;-o}0+z6y(r-dvEor zkhqRc!a>IKw_xwxBr)*tqxaU;tK2u>hnK`;L^RDA+Cx|LgkQ z#4Y;EmI-FVgyUU@Jo);XIsez?G>s_d+)Y5zvK(BXWZrDyrqWEE4kM7E$E}27=JfQM zDEn-I!lWkU_FYa)FIr^!zvvh`Xy zJ-u_}I?^r&zbk?ltH8b??al;ug`I-x?LG|pAPBtdROVqEt-KdMn7#2t_#spq=>6kX zz-3DryLC{TsxE-zaOwv>ZcC>+c)F@ChFq5I<+`+B=yP6^yf z-Uh~*;|&-sFc!q#`|aB|u4*|sIa3y(P?;gO{Gbj>L-h#|`aCz*>_vrY;$jkn{@=d5Tc5g+YKS5qkqAVfthS>wOcXf^V`J6E~ zCLiMAnOpp`bb)>6cJcmKlGB5)?-SghJ+9^P-givTD-tJ?8Y%Yn?b{1kCqg2~p3|MG zcp$D&s5d^QLfi1@3!I2_&q@Mg?&OUo?ws0n!o9$Y5yJ{_xvKv~;14Bqxi+~FD~ZeE z6Suz>glwgp5o~YgTk<5|!7;xZ7;o=O_Mx zOZoH4zMk^6&1&eIs)mMzBn(XGfR4H;!*psnBG7pPT?d!&w-06Pr3Li$d0jhiU1MeS z7e^fKmAFxzsJN4ka(o2evF{iaGq*M=xuoUo(#+S(-l}6-{hBO1$@tSOH?`$(SWs6= ze6wpyT_auwPEuqRkTdBpURM028?h!zr>7N;9kpg@$8a(F%}8MN%oPALi2}Tnes&+9&!E|Dv+$ReYP_H9UK8@>BQ19MQ~~9>`h^5RMSo8zJ1yCMvY%nAC!&I{xM2 zZwln$P5BZLTYIW;`%^Un&B)z>(Xy|23X22!*HgD&=9GRQkWYn;P`-Ik6!JEI>@6tjKYFhxab=S)6GjZ_{2D9Ao(lpOQc+SwZe zNbh>UX_-iR(&o0Xhi&biPCqS2mN7t z)=w%3Np_5~OONn7xO7d)E|$zbuJCrDBx3*-{;qtb&9S@LSJw8!TwUu~Z{|U2(%3w1 z)Cr4T%t0(s;rv07YqrJq(W5z>YlDW=$96l5x%ns@6C+ye8cSR?)R=ZCOInAYP9`KK zjz?%Ul3sD-Z&XvnCOnU*`CL?@S{*Kpy-wmJ*t{8`aQhOomr99iL0DZ-p&}NmfXZl* zIPHxvq~g7`f84jUTz-Rch^Uu3sP!}n;y7V=9N1^etA_Z}?YlhA-1z-cZct4YM@_9- z0+Fn|5{oCqUU~1I_o;Y(y0**HzJ1o20>)<43o`?%Tr?)r-&trxu9!CN_&F|`#XceO z>!r_@!4rAlx}ahV<6mOhZ;8|;nP1a*pn;oFOdfchsrL~aa7we<9w~uq;_|)*u1jNG z4xP0Yxtdd$YWdX6Y(7YbpXTt@$LPO}1%kE&uO21L@ zL_wiXm4EA}dm+)r;;;qKUwtr&zXBg!nqkP!>%s}!LyBt+ke+VZj5Qepkw#(9LfN8#ijVcYN#ddm@IYZdJ8Xl8vJ!%z140TEV4DXnwun9H z&&gX+n85# zU(VcYFkO!wJrCEW#9+1ZHeFli(cTN=!jX;mMP{VqILU_tEG%S?Ov2W71GWt7DYg6b zEG&h=QoFoBXH@tAhq;Q6`9>)@-oP6;QnnU_b{>gGxgHn;IBhw?g8^Cz>mn$-oc5OM zaLyN9Pcz)SxndGjDv&zcbQ(r!B6sx`$G`LqLtdL}Yc%5#cuI0|a`wo{CTxH>1+c14 zOb@}#r4FdGJ|?TB;Q5hz8VvaMzI!ovnKuTHU{K1jthmc9Dv_^&D4yIq)Ujlh zD+bSRTgoYq_+(Cd>PWY6J4UTO>IY~H{5}aQiCa~uu?j)Gf~WKlnvKj^#htkvMurY$ zZJQy%`#2e_nL@SQ6G#9&U%9OB4uK8XJox>)GNT>8tbtcMZdeSrA7_%Fibn7{w{bBy zz%+ip7+*(cXC_}}_Eut=Xw)!vlnX3PEWARH1BW+|@0vm+p~*5lRYV-Z$fO%LB$}%ys)8=eiYHGNg1>^L1T&bJMBpv?Avi*ec{IVSgWzC z#yoM$%H;7H?m)UO7=A#3wug5%x4}?%WB>WQJs1e@?+O*4tzW;7T)vXJGb3_Sk(cI) z5}+v##}-`49{{3jzZ{6QtbQ4-NWzv^XV>Hg{9y>sDd~m}a}s2AeR3V#%_IZaKskvt zPLGQez0{A4wR|E%K*5T1RDOKIUixMd8rH!=!MD)1pZx%Dj+z8oJTJ_hU{}8Mu$f>X}f4 zG+KFzOBD^w#zp3Za0uxXihq8aiou|3<_@~Fxtx=*^6Xt1wD6?h2uMl!4po2)IUo{d zY#HB5QEQ!?T{RM)aCsldYGfn+X zg%p0siwU!&;=E$iUZjIMAnXY_kef;}jU}N2yY8eQX4B5HmXHY#o#fbB4T`F-A^VB}2!E1cBXYpLa9Z zNm&=lis&))fxy9bi&H8_B<$}^@`HAdBy0HPx>L5taPsV7AGPKfJ3>f=fm{<7(he1` zoVyN#vqI&Qzt3OqVKHf$K(IAI1icV|nOONp#A8kf;HG8zAr2wE_GQs4xX36e zo~59WkUojr!wmU};K++siP+?GzyR%+_%zBu=GP~2&PPwRdBoV?KP?nVo?h##1$q|NHI6e-fGfL`lT9st58tq%jFMB;_KA}O^!3ny( zg5a^r-G#hhQ6Ao9-K$8HNg(eM<4K#%GsABU^Qqzh#4~0D8H>aKH9%&tBHy;!4gRQ; zFhdrP@u7Wb1=-S3;JqiFdNw_*rRMDn{R9^!{MMc_yQ~9B4K=lS!n_A(e^0SUfAqV| z{lYH$TuettCtmOwB6+cVL4AIH{kTP2If69+HMq96mhX!Q4*BNnt*WNnolTd_4;FoG zn30Y;fOFuHv{&7fuAF|mmRt@sD`Dg7P-8| z4Bw_X{uhQJE$SE^)A7uBLxo4zvF*=De0d(JolJN0Oa3owyO-B3PlWlVyYaL ze%@nbV94?8?CR13R;CXo_?sYTE0@NqB+uLn_8dU^Q(Y{*OpH71fgIGaK5ln291zIwZC zmJP4xf;5 zVX;p9|b>G@7BQX98S;e8_-fV4cD1i*k|)*P%NCu2dkwM1X3yrw#$r zkiHGEnn^%zE_=*`ekL+t2+v-FX@55(x|GH7GjB`|gr<397QqI9zq=JWNJJw<1j+2G z?qmDR$nSPrnuje1C;xk4X5!{pqBk)6fnoHw-ocNM#P}JiD$K`~=?pb-UPi(zS{Aj^ zv1)LZead?AJ6k{BF;7$QBT71D_rp6Rfyl42QlRYl$SXobmg7u8tR6?jf00*CRdsHZ zc;Z|)nj3^x_pTrwbiA$QP>00WM{W4+s33K&reT7EO+Y<&J^_eIRs6a!}YJp z{pVylFX!e?Mn+Gp&3obw{h4b(Yb1Gfl%9D1DtI`^Zizm8SRbTkMCN&~989bc;y5zp z{@QJkxbx?QuR~GgOnH-w>T$6sq;scUrl7%>lqYy*7)sf7c|}D7Agg^4KOypd#Xg4c2%(im#SMkm_&Q1`01iglkSr|2t-oj8!1EIt#k$U84{K!P-6Jn0 zpFc@6-6t^U;+7)Je-Gd(;iuy8R-ayL8yg4GS2RaYsL|tzsKs8zKOwrdwq~F5ZEXqv zv67cC&sr&>4;*f2ke~_;4K-k3VA%im*wf8$&)KlV3K9NRKRCOHFLR3-BFDb0H;UHC z+oLZkDCW+Tu@_73DP5$e@ znht+hPLdCfTtxK0vTRYXjyiAguZ`$4;Elo@*&#vxL*6WKag#i30MO~~gYy);+@j8_ z2h2^nE=(erv;xqDpbfpG#o_sQz!zLroVJs}vuFeQZ1ilZ)FdE59D=+TJ><>?Cy&|l z=dFk<2Ew&>QN?d>b%tXKQG_cfDc#m1MeT(dMcw7)o!dkoY+hJxbCsj_+VGs2HgKIV zaQ;}x|KmP2Em@IP^~u9c@vqkdk+}T-2UoaVOaP(|49znIJ=IA~GcqcwB1nlL&|`fY zfpZT$RLMsb1?knmBGZrtb*l>)$1H1s?oeG{S67Mld=5M&z%e3$>O-quc)&>CdB-5nh|P@9EBN1OAc0rm|Bvyf%h zKw~4>7fR$DNV)qfUCKa>_lQ&x@_R_4HxM_RT`;mi-~a&__VYp=;bcPIkEj@cqf`NQ zor{~6gaTW!AX-K`t007re2efAVB=L_5P#5-kr9Rt!zk&Gw?}Y5t5UZEkR%$Zta+t| zYhg1W`U{I{>wxcLSj~*&ZWrM9Xy^0<86mS^w{{kxodi63CJ+z*^Xia~iq)oucn4!{ zbS&l|cgHQ*_fOibjnWNc2)cLg9I7phK~+cKG@UK(ZM6qp9J|tG%L3{&Wl28X}LqrdWi4mM!I@89jQEFYkn|w-(RwhA+ z=<$^2%mNISEB9G>{;c+d&t=Huhj(Se4UBqvz`K-Hw~q{JuVexCMJOo?PY@lZ-iN(tEL z75h80+_!E$Z{kFSq=0dW6}K{?{f=2cKng8XCXuK9lrtB|r&ndpenAFZOm&!nFaRqE zK?L!5`6$e+-;TwG;!lT3H_B>yAH9b>nQGxF&ydue_E~oT)0{{{GD@TUYK`s|FwYRD zpWCR1`QruzKmYww4nNCgKRjTvkER5al64MCxjW#cy%i<{H3$C{ZC+0Un5W(Vd&L~~ zQ{J{*n8(B}_#^f6R4x+$mjk0u=BaXp*ZTXh>-vGAp_6LRkVo2w-kvMS7+?N!IP{?D zparoB=`6tH1~Uj{hl|o1@T^g=QT}N8ae_)%2;dq`o)xfQYHtKTH}C-7wn=UR#=1@W z3h-LsfcxmaSUQUga*u*t4TQ}9e7SYK{Ih(7QF{1M!dHM2O8*$K6^8D3jW!z*q(8^w$v*<)n#l=)SL>>n?ys6zpkcI1(fQO<9)HB8z*nUJkGs&z(u43o!c8nCfru}DoiM6fEb|hIJ?bb&Cmk!zk3UIg@v~C( zL=&SC$A81FL(cw|5N3NodRQ(`^bI7cEkbpjh~{UB^Qr3MYP!ROHk z*LGzS#9isY={Uv-UH)V#81A{s0rpu2NZJM+>RxBWz^xHxuQ^PK0bpFQJrPM6y?h^x z`2{j7D~GB%h8$;^7soKTqJxQX7y~WFD!861(+iJ;zgYMP32Hm6GV`$ch*%3Pe?E4? zrhKE%L;wL~mP?V_ZqWih7IQ$;UbBQ&LQzRWLql#=Rh8)X*Hs*#i76K(^V`Wi1VQlJ zwr{eqY-9rQ^;@?9>WlPo(pr}h10gsS zE`!codQ|v!4J1nXYF@&epCDsBb_ZJPbK+Hd7p2%b*2t26qHN+d2_Y|822k34ssNRO zUuB!pX?x?8mlR=o>k1bY0paS|ap8zcv*QN;dJ&=`4+z#imelW_ozg|joX ze<+Z%MPLIQAxB}TgBUBE=(BNGl?y`>mwdrrf2(**_T6+9$hQj}@$bV%w}Vp{F>0g= z1%69n6-WLMG#ViuK{KK}=IZ~@Ced7{7CMa0Auo?|5?`Z+um+lZb~-*0;#kX>PKHQU zPidGB*6)53sGO8k+}NcvMiv%D0N7!xZ@~57C-(pc1zC_H*Ylg}R}ovN&_0$Ea`a21 zh*z(cmOv=0#)dTYr3mk%Y)Fd6q%lJJ0W`3)aoO*xy`it`5&gICh0$4Y2f0Ed@cJ2I1=)$34Xn&pGQGeT8S zQIW@TU+XgBdY2jR3XU~Cj>U&EEkxQ=m#Q&BMz zq`da9LF~jd2|V>+$Xk{B{hN%8kOBi+1~k0b|6G`oPjlsVZYbTwry;>* zx3RCGtZxX6h}QD#-?)L;_qcf$0wBo>OYN}0Qpo#%0pIY`@KOi@d_fW+e59mIMOenE zFew+7LjJZ3V8vWD5<}9{(((Z6AP4Ix*aeVaAf|a#m;A(CD*pKUltHFMRphj3&I*`< zb8%8Qiy(T%$OnNZ=HRw1ZzJg~KrO&hZmBa0hw}<_fD^M}0RdXj`4uoneD5J_?k+(Q zL6yAzM)VriG~qDtQ#vo*i;3x$gktr7I48c${Gpbn3sKXGj1V^Fm_Ek$tfHqq?x3ru z_Y7pic0l8y)Iay?#*>)>*m^?Z&?iGMnqtU&>{Dak^i~N9f3JZiQNkA@TB?T zoya33_bn95Lgy7{%ygfBIl1dp-oeJh&iQk+`{`o-1KPocv_fTdEuSMwYA5gCNEaN^ z`;@5UA%FAly(9kzbcRdOc>t$9^TiXEDHl%sgK3zp4|`?m`}2HgYRchf41% z^WCP22+*5uJ2P8GW*s~7A%wCZC@A&og;Yg%dX!}MGe_|xJzYH~`YH)FUcRvrf_Sr_ zkFTyZHZx~GaCUhsf$h}}dwpP0y4=&Fdxvddgzm!V=(2V$nr|p(uheI`o}G@Xrjv=E zi~0O8Pj6tg!h_fKTS`ooGZ%oit(zbs!c9V+vmebuWw))G5t{Mnl38|9o#WkpQ^Q!+ z{Rprby+|Azx8#f7(l`j+#HYlyjScKpb-(m|BYUtI_7>7SN{=aTkz|BMux7lL?G~ z!%jguIwNandbGX$8sjxlk^Y3A{vkWOCPudn_@gpxXM(A=i(e$GV@75*DoX_m=PK%q z7x;oMXA+(3@}bWb>yL;>sZoEW{>Mdp@%vx0L*OTh(t-SdL>L5Gz{A4Z(H9$^tq^49 zADl1&2VZfolNFeSPLq)60HZ|m&lyZZbhFcj>nmG?7_-5s3w2$~J4{8f-zO3DxC zuq3caX5wZi9-b)NV&6$Fch%N*&b>lKDDta%Ew|X|mjeR!t>v|MH@-OQd>27HN7Z0ww4T13wCtitkhYx zsESM6E4{3u=494MF(wb+E%tVfyz}VY!kU9nZOIFm8Jk;OX|hj4cHe;plvF_dfW4&E=<&?1b= z@T0uR#wMW8keV72!Atu2oG`bXd`FO%ri+?cw`<}MWI8j(E~?7T>eFq$^+rh!qcz|Z z^J0Z=*0@Z&?FZ9aeck@rc_*{FuJQXEd1llEYc|h-*Hms&qOU5}rJ$lwF?f^Pd>M1` z2N5~x@_y~D&}nicTc91z!Z+*lM$YfFf{%N&YS_Xst)pspZaHb zYP0_GEkEG5Fn#{TOXUVe-e0|xQ?DhTk1pFVw3&->_=mt1a|nUxp)uKU~bCPr@F-rELo z$2jxNQF)iADhjD(?io2amlY}BvlP<~s}d5ucP^pYz1w=3f7aL{=VhgN+1kQGDlE23 zt5GcO%CU+-f_K_9BI*{?ZBi;s70adM6r_cXHpv+osY88atbHZ&S!$bHy!yoNu0?`f zh1KRuLS^;G;Z!C8Q|r|NAi_ysUpXPT|LSX{dWK&CXXi6cn(A3i1N_!octWxO1VS|& z5?*3|zzu0z0dqIjAZKXk+px~^qRugc;nn?@JknC(w)q9U;wqUv&NaNeOmzD=*^w*A z!q4vhr^*)j91l-&-WYZ7aj#?J_l82BWlM4@^1Mz+>wKh!x`QJkLg+6##%24wi+3cS zS(N;7otdjigi~LF>oNu93gVC+(ja7!T%ays!5_7F%J!4mVDw(R72`8ZFYMm*@s$&V zAhB~AsN+(z#};LkRdiKzh!ZQYu{#gyrAj;Zk3O-h?5=d?qKsxCNHVQQ<>l1vc+q#F94nQ2dWudPd+eAijA{ssinO8vo(v-75J?J}@Z zF^WnvaSSCV#K6;rSkTof-QFeGKdt9B>5ub%RPshB^@s2r^mQkdYm&^Cacr7qkK|*_ z`C2aw$M{|1i`CgO7D-E=yK*esSzP-9H}RrXyH6ghGbNwvzlLS*mnC^gyyN8QRT|zM z9%mM4Qrgs28hvi^){mf^%9Kq2OaT=m$_2Z-9GdoM#A@XCr5jn777J0ovfIv~arS$q zFTIRD>Y5E2w-E%BFf$x-MwDj%D|Cn0a$V6GOfq}n`Unz_b=!hop~YPSOMo0TUlSDu zm}a9_{wf^J31bd^H_=FWv<*8LaGB&`%TjK+@msUO7ybo2JW7N8SxRV`WS3q4N?jhw z5hAT2no)NN)I(o+dvVL{lZe^EDc2be0q~z0)=2pBVQp)+GmCfKA<@HYDz2Q{`ct-b zRAM6Y9N_6GW#LY)i0e7_|KPE4+ZqtsU;O_1+szwd@YTUr@ zAQ^CK%`KY0g{<9Tia!}A^2y;V?Vdz~ZmY<(xe=TeLt%Ms-E|RO?F1Tbc0NO$oPck| z=^ve0I}*YxGf`{E5;o^H3a}A2MZEY@iH9ZIG*{32II8*8h-YNyS9ucSECvW-@#`fvN=@x&_H1F!3t+8E+ zI!3qzxAElXzCK3O!8)k4rD!%PL0DZ0QS$`a6vGJC{Lq~RYBcZ4xBCHEL1 zy2_n;;r0r(Nz0YBgbY|9m z;Td^JyKX{86L+7Lp7iSDFJj&A2IajH71Q%T`^?PD(_&Rpf_-f_`k)N@q}lC=t!5+C@yFae-y8qq&i%&+|Dkb(_w$A)?% z?N>w7o_6tT+-w4_O>27O+)lmr`xKE`IXx)_T1ejpOF#GV}cz3~G<Rr*Sgo;@9RRKout%8CuY9{6Igw7;cxMi z;R#9m!)~+2>A*pBu~KYtvs$E7^=)#ok&{_hBv<`sYqV!r?)H%=7h8vIcA#z$6m_4# zD&nrNjfL^+t7I+}UcNr8Ew3Vt`xvmVxVCawaY%yUrvxMQHj6D$E1zv@xsZ-p|9&mS zg(N(F#dP~QQtpXTL=8jHvZl1gwb43a0F14bgyUc!3~&7`>g8Sz(7R`-6)g^wO_p1M zk%|bnMby)CofH4;nd~e^|8bP{+xMd%gO{={lx|iaZSVW|%uv?=j={Jpvsun%Jf!%= zW{3j0?N*H$TZQE9Yo`b~;1Q&tWcQn9I3qsI!-D`f)^(X!u_Wa&iG zvi2RKDSVuF2#WyNIl$G)2I0$bzRCUnGguu?FJ~n0oxv=>x|w+C!QSVfqexv!NHo)K zX0y)t?c1)pgf=iquco7V$C>8ekWeh9B=xKv^BdqI?&?{O-W%f-$FYpo_xUaUXlg0a zG}-UVn+NwgF5NtFWv|Q4oHvO>RT&c-`9hy%hwj|JaAI!!=AIb$xQ7P%v6bHLZwQnK z`3v{=oms#C^|GpizW3cP24~+%yBm>hb!-+lt-JIMV!h>IU5WbzOl&1fu-mxPO_EMh zQffm4Pqr=q{lqT?NJwVv?>n}Xy+81HJW3riOD%Z$(C3|~#8Pcmj}SHcbvBwrn9Wh+ zIJ{1`3y3L>`$_2D3BB&GeQy$z_S=T54k8n2qo2h^ttDVf7>37bJg~@j{EQYV>EBt| zwU}4~ud2ML4%NI81#sAjkLj-@p5)w2teR|UBlJH%jIb=Ot>>p4LMXwDQN3SuA6irI zp+2i%W@pdxBchld_4TO^It=O{*akV{)0_#wU->wM&sXJt+T}m)&fK>K%$pdDlMn*! zy1#a`+$AjyirT)CQN+P3QhdB?avnIaI4az!*rRDypb`GnGW>gi7k!0ofm>MgSIP_< zEVa|}00{rjq>rHrzy$Mqkmkqw5$09o6qKQqke>jXsmEbX=TEa zo#F49j&D9)f@km9+opfzPV2pN{f_5XRVQCntp#@W$gOb%qE_Mb1_)!rGdTa->jP_n zHg2ASFUXYHpb}h_x_#9+E(?is%PK)Nlu>n7Pb%%}RJRV_TAi|+oa%Qv;_N))&um(6 zV>N2qlq|2J+n-s5v^R85C`hSvA}xaW5B_eL(UkAEeZLR1ObClZ`SYGyp}6W2kIg{X ze7l}$DG13Lv7&zWt0?+S5er|d1q$0oynCG&|2RrEj-OYQcg8&sXrrK7=P&=ZTk261 zo;wBMmO0e4Az2SvvO;kwdo_QdBGkFG_h}^4W}p5puOfUJ5jSk(W}7T2Ofp5S%!2$;nGZbxlmL?&1lH+JgJOo0Q5wvmQhLE6xm`@Mz1?ya}0&OHSmcC*2eBkjkO0gHAuYFW^6`c zFlUR|SjcK$LW?tm=0`^7tC?BfyM~M$_20lB;x_W01|Vqs&v2#qrz0x+K_-jt1F+^F z4g@*qt6cCd)sKxWE@D4f53U)rRf_73O(~tY=kYAgyLooHnDVyu&-upzYNI$1$V}(l zW1pp(B2DaJ;i_K-5S>jBqktlPgU4R?-1{gqQL?T@CoV~W)Isjc+jx{SAxce8h#oGM z1VsXP(uIbtkcincN=IAzj}RPlqj$F}$lH(b9oThAX75Y~n!$72Jpl+L(7;+Qua-k3 zD<&U0b$0d8&nLdv>=*n=^)s#5{5P3jKy>PKlK<%fWu+W&l;7qI&&vMn4yxjhomI1n zsxkZAic843Wmi6STT$-Qle9^ottd)GN8|W3Z647ZHijGaspx-pyZ{xsu{MOmQtq*) z7`LyQ=4XK+iId0pcmMwG(td^7`kWrcp{%OEf-F&$7Ka=y08ZIrVpCg=chxC51Xi~; z+uAW39UkGmc_SGRf*flRF;NbEH*(gMDG#v^XKEn?O%A;K+Q|6+0;MuiR@-$z)T`yQ z*1@I_%obkbt}~mh7Wr-Ig@^w^LECV&7B@-tJ3fNuvE{WeIj4GCJ2;}%peIYac)IUL zPh|9j_+Nz@-oF00%Fn&EJMs%i27EK^sy_ac>ZxaI?N<3+x>ub5JY)E{ZET`Y<4$GdZ!9#Z`0=hkB6Hnp&Ah#hP&eLficfup9d}^)5llt-J75EWj=~Z zrXM#y);^lXQ}YOw|uB+pVUW3CBL(&C0WWg|Du;LW|-FVk~%q^KqrvWI% z>XWmCNBlfQGCm(^{Sw6b)o<~@iML~DEqx;m&!8u0Or+p;(kztoS1*^y@!Nq5($&}e#%82yH z6##gI$B|sp&pHpfQcaZ$;YA7^d#Lb$v*idG<6^hXfTCKm(qWo!7$pDD`gLi}i z2;+hY^f${bjNc%;N4&>_r(BT0cCv?k<1ln;=%s7jnXz6InX$9OnmPQ?5zF?>}50 zIByJ6r@wpAbDo)we?yv#OzeZRE-LlDj_hxkc;#ltp+L54(n28Ltt0Vhq3*8E%Wt_|$CYfki@jduE6v9X+Q%y<%m)YpeP2I}`RtMm_3cm%3z<}=-btvB zTVJ=KRKiIBvzHqo>fQ4ESi$*(ghdgmBV!+!(C3aZyyQ8rz6H2>PnRUO;BDZATf*Gc zSJOWrCF(N&|GSERNDuFi^40vepriLyNWe3Tn@cYD$A>1`Fj$NiI9jYqI)C#L)u;B` zryWHQR!#Hz0HmuaR6+Z2_0SN56tbqoY3&60H7kjn-E;S2>&Ns;o3iu42M&g=T`y3guLb@Jc9g~1~%n$x=1gDl<^PBu)ywzAn&Jc`n~ z=SKv23&ex7#P3H;$9yf4U5ag~_BIY##Ff#zhThYkcZqsKYd=*|Z!-{(n)v=hqB<6o z*$Xv8)*JXn1;z?HwUOHTBB+=<%l+oUU~uvJ8D*#E(;Up5w7H#~*yI!)l$Yuim$LMRjl7f#+v0nf-j)=8ilfQ0!4^v$>A* zKs)B8k>z;iZ5Nf}W7oP)AF)+a?MbgW z{4jOD`STq%r!Vce`e<|T(#{3$ANEttLC`##sT0?~K^h+P#*%0);nd;m((srDYI6kY zug>x4VdzOG7);O9xFqGqiwUjVn_leJd2ez1dU-*FTq=$-To=gk%5p@7*(xGA%-Wev zTWH(&MrhQ+VA>5LCZPISQ205;*I`OZ+RPq?zoyE?mz~`cl8FYv@Q<`Q0Rw;hl`agLO7;RfVj##mc61_76|MyMsjcKvE%e zV*vs17EwB(zHg~LBL-Pt=^kJpZ+feE0w}Rtp8W&wuN)Pz===kV4f*_e(#}X+JC&pa zrz4$xoKAiCLcPWwa32!I?xrVB^oNQ!Lz+XyaNKRjq*crVnc!f9iXu<6XVZ2pD!X%Q z!CkQtvQA#unnN0ROf>*Oo*w_JN&bgS7yq)aqP$$=Vzr=Qz4LnY$8-E>^!#0`J-6k% zlp;?o-C5cm6H`2|3)9pyGR*lk=CbnqVs-kXuiDa`*fj zgeuC;v;O%Ny(d;bISmlBtjZkeerK^tL=Pf+LKN<1P)txgCIH#E#Unylw68Gsc0C&# zGDMNw7WJ$qZ$}wX1oF$zHqs3zPpnFQ)r>Zo2YXL`Re+@CmQ*UHxOhwKoZEGtpYN20jH|S+)t^TF& zUkavf7l7zg*3dY%E1oS<#&EvBZ4P7!1CCC>&DK6_cKgKJJJG41 z*RL6Ob@!66h7Wcd=bL-3coEDur)3(trxRvUyv<1*CKID-g?!mw(NsFt&+Tf@^V?{4 zR*rIE`ja{23Vy54M=?tFaUlZm2%;L={NSi{EiAw*>Yi0qNHDe;WPG$Ou3!JAn4lP0 z`Rc9=S*6g}l<1OzVOd_!wl(Zr-}6*e%?qX`0O8zH%nsjho0<7a)`*%})`;3(KAK^w zs)rIBxamQ$K9u`RZ+3q9@?9Fuyu596p+3JaiKyS3+41SHN5gnua_`)C#Bw5z-j{@h zTv$38Q*P&@gC*{BgOrz6HY$bVp!X-?${HsC58g{MIz{qO(m=2amsMdJbv8ECOMEntm_C5*^aQgiUEubA4SU7V@uS4E4oklZ_eeZKT(pC(36Huft^dXeLs2D}H*!+|#jznLPWL3!2)L>$N|D;Z?tWRANiOt%+70BSVPL*qQt0z%G zmpUb^C3vwpy=*u)-_Ho&;1_VP)jc9KLU4Q{&#f2VWLy|Qq|uiaV(PZHJ{|2mhg|B) z5tY@+`{-g=RXpQt55nCDUW?g^0~+sYGK4gO4Lpwqo3>a_`q)iwGN zZdP97YE`RDtm6Ey&kH^O#J-Jwxi)5$7(a?87^Xu!ZiVBM?&zQlcw z+^M6T<9anJHtOCbH>GrYD!}9^((bie7_|caIE%ss!{#1CoF8i}o z%#G6l0qbH*&k`JUvOC*55hwS`YUCVGJ+E@nj~GvK&Y^8qzbs4_L%pVET-lRKwy8ZC zs}vZxuJ~BV&TaF{@^gHwp#I16sHazi%eW4)i(z zl<0EA+dd5w6Raq)WFmJ6C+RIfjdKbKIoMZc6XkSjan@Nm!sqn-yR%k0CSmkcOqJ%1 zL>7>M@BROC#I~^d`IiF&l&qp&^^URhm#^z``r;dx!I`F$e1CW=@O4xA#|I_a zxz^{l{5 z#N;IQULY?@#s!JiD(>=od*W4LJo&5CmuuSJ(F#&_5o4&q9?_vsv}-6RS7DIsTq^6v)JX zUnAw_|NaNx3BWEi=Ra7J<4##hLXb?*Yi*+gTL8cOeYX*b3FWN7kCYI9_}Bv@$0=$) z@lz0vMc5RkM6#3ky&r;nSr`v2VW!4bGtNo}*E%BJU+ohW7`PF+Kp`)X5i6Vr=|wnT z)%}g^Boa{?Ytmu%&6c*2rQXP^R=r!JoXuofJ8FBMA9?%bCU?7o>w})%&VaA?&e*~? zAy>V_?|a-1pK4z*T0ok|9YBw&-}!?%|)JJXEN)pn}t%15&FGZ z9YiX(k`U*g->h7HEyBvQs-_y-gXD%f;KrI2ZTKN#+MX~UMb{#gFwRgTl(@P^tEKB% zWM+M>9n>2e4{e%@Z_bX#AIQipfX_a$vXBwXD+-CqQW6r*5HeBQNFm2}_7AFme_#b! zqq#XzhK~3#KaYj7y5WsZQz#e_)j`Bm!PQnWHj;a?f)a5yy(~-rmAMLx=j#NUpm4Na zO5}_*B&~A=qj%e@snnhMji43mch3V>51jB*S)8sJnO@O7lDD@qQsva~*aHH~))ihg z6=iRw)5RJE^GJ{B1bppXLkBD9l4)gSH6|ojkI)kN%}9(hB1vRsZ-t;8?Pc{C8(@JC!OQ)oS$F^z%tGpS3O+(13t-Ew}uSCmsFA zhrB!~KtguB*f=|qLswQ&7Rn=;5HCFtjP@mOlf{zGM=q5dop_hT+()PT(I#26DR{sM zr3g~@qx#n(s24kxTuc8v{k9x|KOjoq9?7u>vi)nOZj|Jf)?y;-oe3AmkG}qCce}GG zL=>sRG+Nt$CM1>O(@Xwv?o7hc8!Dxd#OX(#Ve;C#ET0 zSvhQ7G6EeJ${+DR7_~s=>N}|Uks}WlyWe2l2)cg(F44`-xus}ec=z_5FP|^t;m_ME z`t3$M5%3WH5E(&)nGYonwHAPo5-tNV*tlr#%g>srh9`gR=6e#Dy=|9>OS>#@Q`R5$ zp`;@Fg(wRx)d)%1sbfCfnax(>`lHoA2o!K#KZmAe)7zBBpe=T-&3HCpIHOfASZ749 zqX(t04S(&by_r!5+2#vfcL)Oce+aW?xhjtQyhfeo=DS{F!K%%BX zi)-?OG9>ZG2alvV&kYI8d?qJrF3g)dqi#5`w%{3^KtT?IFjzQ7?yef{xgK`ycs@gx zZTnPj*7fPvcbEV{QEG_oEcN?tK0sOP`>cT40rt{2r zDA(hbX;$188a-P-!ddUR`PdHF8vq@DAtQtbEsFQD<2XXZoJ=b1sGY6jFTV@>PnFJ0Zk0dGEU zZ%^(SNh$qP#tYAJ->=IwT1n5=qSaQ1%wvsvvh`>=J47ZVxmOh%M@o=R9_y+Gh7WdG z97z*_J9-vdit9!J_1uxOf7$BtN%OkP^85O(|F(}VyFX7kGeb*A{oJygY}2V2m8Ho0 zjt(PZjht5xjsf2~pD)%mTL0Xj-t&j5r8UP|&EgLT=!zkiIQL-7R(ssveox5gpJa4c z#YCz)+`}7Wl&M24JUVkkrlKg$v(r+LnP;}CoEp~!qwpy}F|@1OuEp0!_Kg(TV+ogn zi7;!cd%t$O8Wm>1Q}e++2;gJ)%`6?gN! zJ;+8bjVF^VqA^`_GZaJd%|Xh7Jxa68mfM$PCuGP_dw(iM=GM4&E`C$gE$!Egc79iO-DjEv=11Z4j_ z8I$vtFMjRzyQc|GcPnJ?sQj5zR11Lwn-U&l+)yXac|-PJl}&wui_{HcanP+<=ALKv zWDIbhe?tk=9FWwb=flw(>WZ$0fAuHl8^N=Z%G zjO8`GcUVV>p9i(e&%hZ7UBc-=sVAOF!yOGW)ucbvbr-F049y2m5x*xP=Y`fD3 zEtEOSxUw##terCJ3aDV0V(|Zg!@*(df z!?>XdueBtq7mX6KY?spELDBl*udy53R}ao?7Q#u0u-m|r9dz21p{VHyRu0?TDmXj) z`DL=z!=KKDO_{PA9XnjO?>@OxmuwnL`Lj0HdbgdjAhZpJ)69VTYzr0LM(h`B`#ze5 z=0oy4(B^N&lGii#%#yepgx*dQ z@d&xabsIYo&CD;t=9k*?>2ChM?JOz`0!`jXso(NC7=Hb?lVm^hr#A*F?<${mp6Hp< zymczZNN0Q0f~f+fE-??46&%Z`Rd{R>7|589IvwNumR(u^oxVcFp=dceMBfy-UfN1F zypa~x)iy^&C=YJxnDbo^@0R7ug-+)*b;qcDG0S-QB%lEH{Q+MhaA4*y?5dYf`$;Zqe*o z7{XA+D4}k?#Mwlbb{1q4fnnJuB-)0wU2?ubCkLG+(~Zop8*F7c8s^i)P3~%w35Yd^ z5Y-k!Mhzsq+uOF2h>4ht>fPxH)U9puWfgr66JlFY+_e5}VRLiPZ0sYzj^wKg`$ucc z%E#2y%ZhT#nPz2w|F)@p+}#mQj%^TmgI_+!xXF3oIUjhDPG?%5g0fLimvtE;Uz4jCHWoS3Jq zr)P_bpekXY8*+$CepcSQV_6O)4LpweA*#=RT9iw4A zk)6+OSRsU;(Kr>;^x^{%LpU#quJXFuQsJny+x=~H+`VEukL@se*ll`!hNgTBa_?vN zm4K8abl+3|dIjpBtkIml>Xmz2>H?UdHtShdtsYz{m0QwHy3skvda8$T{q^^8-18WW z+Y7ydwfdnCgNZD`)Rr$Ew=Y1{84pNhcFeBv@y38?ea|r3`ddcc@z(wTBHhk9ID0LO zKxqYYiFJ})Rz(P6SKz@a_usLCR!MR;EF3oTykilORARdQJrw18rt9OVjN#OjnM@iu z#nBnk|7kd!R=h7OnA=>W-Aj#J9F%OB+WzcR%M`ruREu0-LPvj2jQROA06TGvi^0H! zdtN8>SANmle}_Hq9cEwoh%;<{>^Exbybjk8PKERmfk>NWzU;btK-TNd#W2vZ2LGC~ zivU^c)}Z4nKxBQ?l%1d5PF=r`I}F*jlN_goD%R?XgiOuA>W>|y%@CM-lpzpd;G%_b z<%HmY7GDIn%9s0*B&I9VtcBhUi8B=Lxf&FHq33m|HZ`D|dW{jgl*SBl_yxJy<`{m` z2fcyK?jUmKA#6B&flTh52Te<&sCA^jrmgK#V93_yL|B7_ z?A}>&_V#277AC9J5FQYxKk)3Kqlm?-Iyaf?rbt>`-xg8#nK}f#L69AqY>2NVB*oY$ z@Qwn421QAr0i2mml|rnW&MKUPYRfzyH4~b^p%p3D%Ac={Ah_iQ4bCr&RjP+GV7!69 zx{Dy8b^?6yUsvRxlH&XRjfdZAj~f{9&zHX{|Mq2_tU?T0yOdCMam0J{HOVl4M6?BN z1$7MESW|4t`r{q+ORC)lr%Oq{*%C40f5vq3^+-nw!{;s;UTYuZdJ5#Ma`kMxYqcA~ zXPix!;v2!FYCoZDUd=XS+uCb#tDH&KQ1JTs%{^!e=&h`yQEU43qvt&~dmWA!fhH=|m zFECbC=QA8Y%1wQzQZNfvQbQ>N#wxLP7lZVGl=4JAhUUXM(wH~;zLDUQcCbZgkeV4s zb~bR{u7-H%7+9@whlS~&?8T0Jp$?6|od3hXXchGG8V!D2mN}`!0QKWpv@Ztk+g9?| zzMa*Yu2s!$dOC0E7^-hBqrkCG(ZB6M+3RDh(&R$RL!mRNSKbrScIhPw-h%`0^J-son#mA2 zClE}Vi;J_f%IeAz^yDM;idG9t9Ta|hd$YwBb`B4eP2k)=jAyI;MHNk<nH2HWkAHPZ0l(>d|&?Uu|&C?6W*DK%;|CMh}`M{A!5yj7_S>+ zQaqX3l=(=`HAy^?>`ZN&Y`fZ(^tzjJBZ^(`HL>CQVhd+L%=xqccwTBRUdjvK@RIN9 zMz2i}kcAMgPnW$|cc;ZogWjxWMA?;4ki2g7KJy=q!B*B56+D!D4zeFfM% zphhE`mRAjra=@vrxeJQa%GuUjt!UKd7Z>+y99BxgOm2pD85_+_keP;2?EVJ7de_I%5yp#zF;?01CU<`J<%d3{&8opos9!CLcGrItQM4owq zc6xCm-`(3g@p8_uzIjWIlfQ2${cA1%`a60LmY4rGpaa~EN!qrJId-i|(HOusm?@^^ znHMg^^#oPG{iMfG|N6D*6(%PKiV=mcMgZp|=?i?zKyGB2+1`tu?wO+XVI`1L*&Oow z-jSK+kd`!@y$%CrWG7-jUNe9{KJhqf|8!mnHEx0I{tI99o+*$472a2EW7HHTbGC)9 z-(_r~zrG2IaBG~kUVREktW>%|*H+l~CJ(H~(=^z&T>C6Z$XbCiMDx%TOYcaEL9jkH z4~g08)#HH#Piji;?R$UmpoB%i>QMW3cs{GEr)S<|!Q-KTGk3=1BmCbQQPi(@{;4f-qx3K^hQa)jR$J}jXG6=QyEIlgdo>#C6Xk? z!lYb1hc!5Cb$SE6fY9jzYJ?coUqx$m4}*ipXWa?tDQ5s?Y#0n6uj~NNMuM|Y@#8NC z&9aXnnep*){+j2sAFnpVa`9FHx0fi$%buD~rtJL+j9cizS@R-g#8pWS%)!zIC#PV!QH;z3JQnBOTcve zg=Hl5$w2EnncyG;fZ}Nue2vwY=T|$dXcx!4f03v33aQP<_f?M%Gz9f>i8AYJ`YuJY zK+#4wo&lkB52+hrR(mD9m`wIK@VWipXXH*bSYm){(xc9MRA0yO?|mEAwRZ!Ik{qAp z!8yxKOP^WP5`eG?YT|>{ys|SAHZul-k%QCY{?YpNXL?0N`1$Ub@%OoB9z%A_D}yG>+M1V~>p3LYxsF5}PqR*0yvoi-j=2bb@JS1A&eF~W#Fak_ zD6)h@p0jz8^^a?%1f1p=C*>!J9`r9)!p4{kN#xjKAjl+PyvY#>@O!wTpeVRtV`J_lN-x$;aDv*cj0TlTv0P}Qq+Qf=% z5~v6LU|G~ZG9si?_wOb7are5~C;1~fe~fE`u#~GEHYU}W9X~d1v^_y!L1#y^xgJBF z!|5+=j=!m(7n=U)_8?YPxL{{_dRR(rJB3{#lE5W@O4r&l;aQt_vbEU- z@Iv_}y<@_gB|BksD{yGN&m1m-Ip51-M76A6+xDH%jrKlACxLp~_b$5t9m%n@0tN{BLoh846NgIsfRSErDZ@}<0@M)fB#PJiCy+q?;!)e4V^o5h1kKl9!BBR%8Q zlkxh1FxbqrdcbXWYQpM>cmMjFMa4>G@3kDegWywFGR<>t^4z1kBQ`d6W8r;cKa^rC zdu|4eP`+^CISH`A<^m+&zn|caf}D_HBKO*h@Xr1*IgkLlmESDYz*4_j5PX`CuZY*k zGNfqtm99dS=yoAGUoo@6Nv4!M>Sr!E1F>jr4l>xO3T)Ls_A`3;oq6Hh%_aZ~nePG` zBf@Um@I+JngH4Izt_sL@*JC>&yTRSjdv1sbd?=~XaNG+fEc5h~5783?L<15(25+oA z37V3GpW|N^uSgXX!Le;|Wxe#N?5#k>iP&KMoIJVlnuEMbJi|dY=x`a1;wKGE<@6`l ze{0erh_CgLRE%q2 zig2EajrptHe61p8+HgJKWW)i5ue`MfRqJF=A+T-)E=9_ZJilBo1^|BMm=7Gch<3qx z8`dux8hMe}aLx;TT_fRf>YpPcBmaC9aL=OO_!mrZN`oiB*WhO2drl|q;=7Z-wYlk} z8La85GIY7N7SpPar{TK@k!wKDnKqx>GnN`nw7!c`Hwec zc_K0ClRs~?sM=Cq{wpv#KG(uutA6tTx6R?bBJY}v+7}R@>{x<{ED@=$EU+rI#YrlZ z5Zz^~^*sq&O~pe~JkQ&=;6*CI-P02RuuCR?(GAi^9U{ZDvgv03tpfR0qz8}8Du;s( zuP&24UEv{dXh?{JxoX&pnCuDtgeJN)B5 zDWwiiwVu|3eiJ~E47?P~x=hNAemq_RY|wPxfBPVOlS;tFc3SK56(oJw!B=i`t2Yw? zf=)%rNCjZ2j|8MC>aPz)$klkjBdr9rK*`NiPdF2)E5y4~?Tgat^FgOUHx%zYPyW*E zP=*DnRUTX=rWSyrP<#Y{vi?+>#zttjbAZ48@2j>qvP7tQABn!*MGJ9;g=@L?X2#bS zwx?&8T&t**E0crbpLceQe3GfEHK=F=r+zvd0C`@6LZK^+Uwq|mNLF76x?sxf+r&QB z{=`9N-s1u%;a^v@tvWD*2|&E&nf-9ujV@CY`4YK}bpsddO~J+nr)goXjl&3gF4?Rv zRgy1Sv^&J`nS;jA} z-2!IqTC?Q=peo+<;U$)@Am^D$nMnR_@FRDI5C5lAh4(APeW2^iPN*ZreVN)zS2$z; z4E&>Yvsm>%wgF63-Kz0wKf?R*qyLF%?xYlgC_-I5&5&>UlR3!U>DeYSHP?Bc3S#*~ zfBzsT1Mpd<0nV2;-lDKKCU)@YSoa*1M0|5Ln3-X!fHeV^9qRIrzyI+^tB584k8gc= z^l<15XbZsU2A6N8i6mUxq-ou?tGoLzQS|*+_&0eLsIU*ie+_rGcE8HbK#aW#;JrBb zy}ZA>J0@KL;2>a=d`2=nQ*amDzs_$eN;EfrK)j$QNl}5d;z{^Q+%kUDj~_n5*UHXk z+CnZW*Eoe~^)>~Ndh)~gq6Ji{h}jffh@U~ zZ{rqKwIN8?%x3f$Ktoi%croQl2G3=(%E-t{s@J!_TWaR^K2tX{EfM7>lUA9R z$BQ)lO+oqvSd6RDz$%@)FkW}ZZ*8GJ9q{Xb6U7V9YXBT0Q7g*S)Rgyg%Y8PASd2IF zXFgx9cEqZ+A`7>UdB?#YlaT1yAtI~l48R=$fOJN4sPcybQ-kF$@-g-!LTBf@gK#&r z8qwiL=0eB%KixL1=Pcqgd^e(VciGYBBuojSxa>);|uM#?? zrKRP6r?StL?@s;y!>WN$qh7uyoW*mzojXJ9oV99X(ykx&zh;j zKT;Lg71KF?x)Qf!fxXreEjmmfWETKPR!o>8+`q*%hOu+$Cq_y+Ds0i*1`-yL)^ySNTji99x9qtT5Y2N?-{c`{)vIKyw z#~lz=`2H` zj1J%c{qYEsxjcCi6-C^y1{q->MMs~Y-KmSWD~h#rL50@2A3p}fe_Rcs4w{#p!#K20 z=%i9WFrFE(LZ;aY@Ch_9Z4_eNPah$KIinj!5;O9KCWP~YqWbmL+#N5DLppv7BN)zv&<3QkB$!T`d<0I)B7A>)N1gbs1<5WQ-p5$J_ndBFCS=N3DA z*;zz4;#?ApvEH;)z-<-|U~W;$u;0J?>d@MbR6q$W?{A$0s^r)Qw_XhJ9RJI#cK#tT zb{wQ_7LJ9hj)&-p{Kuw7|9Bji$VeUa=<^SOZYoxg)sPw{{s=03)%QzA$c5A`3uo=% z^R(PjF#U8Kz*J{a#kx9c*x4LtbicYT@_Ti<-iAq+ zeZ#R~3%Q-D|7sBgXp4CvRB4`(**y85d|mmCqlWqU-`6)v(I0`*B)5aa5DlX$1j4l+ zxu{}sa)?fh+pPkOldrF`peG0?(s=Ie%N^F>rpf_ZZ1n18qcTKSypq(@HaT>!t9K-K zbSwpzq7H3$$u~1~pF00*{>lotEuZB-<^GnNfx*&vwOlV$Jp5EzK~i(dBNY?f)vNna ztg8n{jz!+H0uc+gFa+5~+2hdu8xL{Q$pJ8sezVg9?2}>lM*s;1%NDqL`Y>t;eR}~F zdIt1bq>dbAZRLG*mg$9Z7@&!!2P<&|tRNqsWG*Pn|Myk?IE5Fc_yDNLu&Y3O-SOrT zrSQNQk$~(YveCS-t+H3IQI(h2RxfIvJdA`|i%F3J;B-h25za+&=^7XPPUD#?6u)mv zzWWa@Kl#M%+q=$JF4V+HB*gb)sEo%Oab=N2Ik^$`Rv9;35l*Rv$pMT?;Hf^!xmkL( zcQAq;xjqV-^N%6%*_&Qf)JWA13eJF)B+h$gyX^nP5?Lmy+N{@u1T|}?IRpc0-E;cM|2uCP;F@`4Y7qxdfZ9*YKOU zy3GSJU2Sk=rZ~0}!AtGr3-zrMMDzm|%CMGo1gLcuLZbvbh%$Bf{d}!EXFxPqFFrp# zeFvaqYJ;T8Pt&fvfoSV%=5CD@V5;WzYY?j3S-u{$c`FTiP6*LII0(!zk*K;GUvX1LkD%2 z7rU|#^M#%VwSV)FkSr*OV!yw+jX$W8sUDEDzwY)AWA*?r+s!>{dbCI2hbDNcU+H)C zh6(W4EDKzlqkx`E6Bie+0HiTGTfp{xp9bWSwptE6#rNdd<$B|h5c#tnI_tC9iN@Ml zaa8sMv~A(#w{&1s^9Z`>xC1~gMm|OzLb4|Ujs{-234Pkdk^{H_q2BNaCOT0!7C7Rj#u0KXvZ$z!E35$qu+`( z6`$kjHmha_M=gizc~WG)_T`V?ZbW0G_1KYiS-0c*D+^{Cxf`OOHX=lg1S)Jr1;Xu2 z+&Kg+eolNv|FK4m!v#s=@zj=$L>V>MXV%2Nu&E=aI?pI^fC;Z-hlmGf@$c@4>G>ZU z7USyX7VrjyqU8-2Da(ZccINUz0a%U9Ss=1?NB9*W@`FPo_(Q;K97<$vEzKw|5)o1r zeKLSP$jl?{?GC<>K`(C_BlNz%JVq4NPG7ePtg612)3@neD$jX}1lIjRrj-flIe?A# zkK65O3k13bfPHFVP*qPLLtHCVhTvVvcI~dd`*D)onoDEeynF zaj3W!nUpmD5ru+e0Z#iwUS7RUYNS>vxM|tb@#_u|7HlyJVdK2*5SF@Qw}DpAc^+>nHmsN0zfWHJxix9!LIwr+G4#l^clj-^L3l1+gC z_)^ro#{~f)X+@{*-*^-aL$zR;3)c8lYESIKH*y;B_i)*skBmK z1KVJ0%4CN%Fh82tf%nrWG(Yd;U<=&9x)O832Umz(bOtX1xpVY33Kq3AS+Am=r2!Tm z+`Y)Q60fJGmfDHOgvOgjjn*O3~4>)n7e{!=w3ecnWgU)b`tEKPZ;QOsGWwk80ZHkwk8B562|X= za!(pSk46CVjQv~Jsc*tp>d}O6Rr0&s050rq;KJHzZQ68Qgt3aK5{Y90Zq6;1hJZKPuv@Qa` z$B#3?0dtqgB=Aa;2U5(hLTwG-p1s6tSRd;#T|xlaEx<>~Ve}1F0~bf10lzAKMQ%c8 zYp4Kt8m$LzpbFasI5XYHnh$3whr4c$PdNNyZ$d(-dyPKbsRCv;fa7&BYUb*YG{$)w zB;ew9>&O7Y>vY3Rn;p^GIb#Pc?044L^BlQi|L&ciK-%#&(D!_kww*hvYCe@+yBX9AD&jl2PFBB*2nUF2qx1qQ<$Jjtn~uxx-4aM7lOg}*;!6P!gckS+Pfc+Z5g*b9qM z>f8yv+Ev2F7=}t1<_4>AqmD_6x1*32$+PvZ9fJt~jr%A=Hmx)4wwBWChZj;kvy@Cw z*+Af)KU8^M-_izCdpz@$xr?@Tk9Xkaa=iNEzt4+9Ev5V+r22ZQkOJSGnG$9eaM9zbDO6!32?^FW>*1CaWiJ5v>Q_{#k+ zL7(+S0C3IAhM$#|PD^cG?U1YA2GsGdaX`))7?2yu0<7k|5={B{dPvn(XoO)`k3w7D zR$NR>T<361Qzrp zMY3~+$S|m}cpFLP58w}p*7zVNJnnREU}o{#5skb&zkW&$CStvBIjYQO*rJzX%6B`{?-xDZrh%TloPY&4A<4jqyZRegsx6aJc&EWC z#07NBc#4PqXIGl<7{|)UVmp2|U#_V*`4i9t zWu*}n@`e>vNC3ga6<-XRsDYt`{4ntVkqZ2(4Ls%x$n-4DG^ZqKHa-tyxxD*<_-8(p zqm;zzwS~pS-Hj;wJiH2H%#qCtHxw#OA&yuESyh4j-XdK%_%<99{R8}_tZGqfwS$+4} zTm%!$q2EtaQ&X<1Y)EFA7j=M;K33H1vK_TlU_DsqJ~F?mNw~l@*yrtASzN;}o7B*XvpsY5SHii8?U5uVSpVC*PD#zP z%S}lzvSrX^tmtm_f_!z@?nY&vStZD<%2Iufg3z*5W7E395`lbbil>QSb!IwQ)yE=} z@Q(PF{z-Q!;*&>MkK>Q^S*k=2^KD$hgxaz40@)u2=RU;9elPI4^b$Eld8rd>kGt8$ zb!kkJOQh^u@qSnoDpt$0s)IYgY;iKC5YoU2W=7rB?o1`c>}XQ%dLEGl{ZcFEhRc}> ziAH4^2c*_uVFbYFheYmF#~c2J8-UM%_gPb+G^4L|t-YpJExFI%y6jEI6%Pb{?a6zM^YcvzUdk2`nFUXmQ?u zr3GVn5)Mnh^0RawpDe6P#NxfNCk|`fj%8thn_x#HRy{;DN!fUYP%tKKKjJa?u6IwN zcC&+Laj0XN+cwm6NKqA`WlxB=CVjw?mH0BuDAdeC&seXw^muesRj$Rv{X0T90&Vb8 zp^3!Az{#upR_$KRQ=y@ufP$~58#`satwY(fY;0}o({l*HM?)ILHVkLr%DJFbm7;OE z4JjKz4cX95<2^V&ug$LK_4<`19f#Aj!k|T0RakuMNDM8h5}Di&Fj>sa`Xdt=}t-a@85UMY06Ul^@`L%{cqTc=VkqX_kixeaaKoM z+CZYPW{9gER#>ASNdQ$+B+2eC7AWY^0o<{GW=Y~Fi5(JPO`^Khlg*P{_ihIgzuE2n zZR|-PNB#>%1ewBVf{0sAN%URpypgJT3S=jb6@64bHXyzaknIhsCn;9*B%l!dOvy-- zOc*IkRerHl>dM%Wbz~Dqr?{+)l`1$g)EK*x$8JmF*yesB$%wtX`_auA+@32G^RVI9 z0RMw|7}X33|A~mucgn2Vbz|@|VnH2kc{w z&}5>-c-iQPxG(@fxY9XQ-mHcX5DIXlBo}+kE#R&<_A_6Io zPJ~q;ElqQQL~UqWfq0Kg_ z2o~~<$F;*y<8}V^Hz@sKWKDH8HHFuLO-CD*W2B+g;N$42&O9Z+(Wsu=N@2Y9!>z44 zqS|!XYbVizdVa%tu;}}($z<{@^bQ)@W#UjyyKUV5_t!!jtb|JvTCxu$kX|J~R0uaO zv_fH4gbM@}q?0+Sht>P~c2wp8XrBT+R|MPlEs@HOyMPv$NE+|95*QoV2pHz96+=7S zxNVNWb!z2hUCzrEmy#;bVQIb(s;2`GU?LsY`pz-!g4!Nbj^bJ!aqTKXNjrV+e~V6O zz=#pnoQ(^h12b?{a18giCts#QkBK4LZQqiUZ6!-UL_#kam@c>+VH@?_7u@$CBAXw* zzB~vwP3SE1@nEgiY7Xh=13I5EGK=UbYZN`T@D)o+pS%b43sb0a?au)D8QUZbtV@v;=2LZ?9!v zzKDN`ISAQ`dh#qZAYxPrle!(mi$@gv(8&4+`u=nCYcbz3wW3S}y2&!BE&k>^rm+y$ zdCa<7`wzuO>WX3nI6!)yvg?vEO{7v*Ja%W=s3SL&s()HEp6HN}2SN;F03i(!v@wW^ zV_-@x`h^?)$M-!qP#zk2WM^uRg$&zn&~!HATU>eEycmL0=k9bPG+qT4D2{wjh55gnO@Ss* zuwDX_Lxz1Tx>wcJMekWBD;B1owa?XWtEnYQ{rE01S>7jwQ26T z8Nvoq_6<}yg>tdzd+sFrPsXY{=qcNjwN@Y8t|pEAc>JVtrcU#%^mim9Owpj!$V2aSptmR9Sv4y|{KT1;dP$3q#97tOt8J|VZkA}aie^n@R& zIx;p;2bftm6YLt?Vz!?3Xw(TF3T)W7jsx!lG4a1zT7GZ>sz&+K;g;&KYLG(*uQfoU z^tM4Dk)6$~;x1a~xlXNKP3Qxyx-JNWb`zDi{fR4Iv0kgi(!>VmhHHz;uDBSuVEcl4_xwXSpSONy1I?WnfK7fYm^32}9j&(bJ}b^sCId#wjv zd8|e_umbEl(Lgcy8@7SY4q0kb;}d&Pm^VPHjBqpT8;r`2 zn`Vm!Pi@JFle!G3jwuX5zGJYP69 zGZU7Okbp#-7Kesz9+7qyAf~DNk8LlnDng+EvpXslqgqZ!l=&=;RAoYiACMH>NCS&i zXavWGb4=(JqjUl7w}mFj(2CVIPL8e-22!_W{b zq#d1u*wbv5gRd})Rq1RMx0liGW=r?)uX_9VgkKIEcXfbpeS3TzG!^mA((;+dS<6Gla7>k z(}#wJmi8}aE5-cI;#TjOEbrVW1!LQ7@8w^`xAvBAsKE^y%fYrVoq~pIhQ+(WN4Dvo zgpe-y*(;Sza2KihAWKg<>xN#j%w$Ybw`E?la(py`I$m#@3$CYiVkXZn)I-2*0B=F1 z)FXlfT&6EUNRIZ6E2xD}pwk+D8}k*8$$=dR%D@nqaJ@{M z?tHl#NOJI^-$%8V4^KsEB+AFvwz6I-xrus~-sx~MUcoA#d{tW}?XO8^+qZAu@X7cm z_U`N$Xb(7d$71~Qlhk3sG{EIJ1ktf+=~p>079A)w6UKsxN$xa_k{E_q>Z05#2$Lh& zPF1(L1IEnJ7GNDv~f4g|h_F;mvdf?N|H)CTjy6RJUg6yGps!h?-9RFVf(x~5t!5T`gj@%tT0+{#bqz)fBQiRr*w&!8 zz5Ve74hfxcm|)K0pmMp1C}?))iY1U!+YbI828w#n90lqbdmRVJZpO?~0MSJK1Upbh z#s-ham$;?ugV)zV%jEeG0RSMDc`~6k2E|Cj6nINKW*R1*vhDO|QOlEm*yVjbMP4O6 z`(c;M_8u!_LG$j{G^FzPUv>Z|*jlO%C>8%Ev!FnVG^=*}+UsrpEQZ!; zPsi29j#$QO$(?MAhoCJHqI%hEr=z<(QorlBcg#P8>faWnu9NnLGK3u2Xb^@y_`O=9 zZ}Q0##btGV_p}}D09??2dRU`=K7AKxiyWQm>SDKa(& z?el{Q>w`3R>VWF2Bvm268@ds$ta3apu#sJFBsE=t)pM=h=7+JBE7~R|Cgq~xU9Dia zKR_9Zm~Pvr(k>@@eV53knVq}5V7);;2t&(KaXis=Zs;RMHeT#77neaSOQ9iR3enG+ zD_5CF#}Bl!(cp8|Jcz7z{lq}xCFR$r`v?kQL-Esz>h5WG^%3aUBMN9tT7X{~PSxy) z)cZfA|70B-$}1N;@yuswy1pAT?UHD?oA^*2tX8HX!o7jH^rY5HQP0*xPe)jd`aW7L z4|UQ^cpns)SF@4?Q7Ep($TiDI$zU2{IT$T-5+)GFW~p-B@3Ua(m7;YLr=cCog7Qx`#!>u(ldt#L zKCXAGZ|aVPvOd~n;voZ%$1|T@3ggeTLtroGe-q7OyPz?@uMOR~KXn3kl z0ZXAtor$!@Sp`Ex1#1A3h&PX^uYYslsMt3?I{Lv*1~PBVfZG(a$rrcJ*V~1HMBcs? z9Z?3^K^^5`sFtN`_${jNN>*0ZwjKpzpDFwW&(Hl2Zh?C@$MV~`N3Lgl+|IdE=> zs^}D#QKrNOeAjaLY5RRW6JN>oCI!frdQ6XI3}cJfSN7%ThWO6-NMB$kMIusOP;@HJ zSjcnVbCxbvSf51|9!~fDfOM1`p=n@OT8c1ZDbbI2t?ocg^O-}NT%PRL(jN)FCX=);4?LoLy^U8=zO613k0u~4W0Zx(TSu;S`W55Dl zaRt5JHkHNDsP_Hp41qAuS&KB29+5GHosb=n`hIpqxefuM6I$eRZD-qlK(t105s%Sp z;fo6Ke6r0kaM=u;MBd@|KqP^uY`-Am6O`QNumU2H00WG774M?-djCa6*mS;DW=5NbsPuU-GyXcIep+Uk3hw~Ff zX`rL>rkto4%TsZ7YH(lG%gf8nGrmwOS{=3Y01<=Of0!nhxU5jYKaV?jY+}J18le6Q zCktrkK}1*j!CPE*XhiZL%EpT>9Qg4F&Qvf8sI8Yx*AWOGhp`3En7nJsI}IT||#GqxG(ltvg>gqjI=5n(E^=J5X7g zN#l-DWc3;d6hv~f@@+dGky37{tdteXm}V7Cdr#blm1OgiOnWB*i0RQ8xU@4CJDigBF=$VKT9UPcpD z*NSZUyd^-*Ka}o5nih(Lz_}DIDakyYu9P?JI`knNEy?n$id*`|JmM(`+(d{rV%qua zj{x-NX8aKHtVazwXbO*|A8?3`)e5MQ>hO^>+Myti>VC%oE0XFRxQ z&8ljAngzUC9Nz4Or*YV5864|j^9xeApt?|Q163^codfN*xw4RTYSSJ@8Jte5}c zmdody1pR8$ta(Y2FT{e33G!VYli&IqYMzRE+{JdD7$l0*PB~wZ&V#6e_2ZiTOTgP4 zZ^k_G>x&oPrn`e_1UKPcjaI$gpV&EQ@t+h_FEmdoi;=LsWm=GgdI+7cnko^?Se1 zEyU&C_BLy+J9gZDY_;r$kI!nrE?&9S$vlNQ>eZ0LHGJph8Xt=oA`67n4S219Jr%IT z=cB6pqN(ODBqYP8Vt);+59fr<-IjLvS$Ou{l!nA?H&g~0llA!Bxkr$CeEKmsijc|q zvK)J=&)(|BVAfvwr<9T0Q%s~+5 zE}zIfizA>t&@XG=`Sgv+JNiwq+oH%3t2}tLI%<-2~csCWdrPfa_jCH!gT-SFMW#{`Y zES&-Mc!B~|gFz_D>|1P2@UR%$RNuD2_xb{#zNtvmyjh*bw6YT2h8cbmp2sRo7sgU zH0WwR+~?%q_FqJ$aC|;NqD11yp72EcJbkaFi+- zs?YrZ1N3ExEk{&lyZhGNd2>&OWIs`~funD_41v4Ru;qcxT9B|Vwvnr_b{QZn6BI$p z{v__nA1fgHm&mTvbKg7cMi*PVAu0Zu znfa_s9%?d8Fx&ugbipQYg-bHob4l(XI9!%`;i!DjoxU#mnN>do$&-fI_9=JwE4Qle zqx@jy-dXJZML>VrTyejC;<43!e}Q`z z$+yO&&^wK!HMVEoMy%+@HGK!x_wc!pjz~qkJZ9O^mRu|uW?AMj(+j_!&Tc&*&{i7_ zou_MfE@*$3!-ZEU7gmMbWXP0E$<9{n#Irv|eDdgfRYkk}+fKx@*d_2^gGvaJ0T#)c zGvH|QFc%X^vH+;?f&ry%7+F5WB_-4ur`-)NFI1s)&my*O z<68PL@6!_Eg1-laIrUrh`X&xU++9JA-Y&rkC!^W*-3`k884y<2&0v4lBy*NGs@`x; zXL33;yP9{8f19XVDa4yBsF=K~6x9~5W(oR;!}@LG#V8!+cr=`yNTpYgMlcyv2N-Zi zfZf7S$3<$TlEb8qqJ(PSzU5YOJ>U#VX#O@R{U-y1gc^YmSG#WjHy6sry3Gx1(a|B9 z#h_z=SiJ#h3CWMDp-;GYD(05Sq1s=$CP=iEY2{o$6>*zB3BAdo=6FkFLtH8~4OLOE zLMZqMq7ivPBzm-FShD6(csLefDF;wC_r54cVbl6N>+H|Gp1R8lqKQ>nJ3bEq@_U5* zx=>9kAbjN8jBDUN2O+}=qp=Tr3yX+7$->tFz8lLZtxOQsCyH#_TVlJDL(9gkP>wEI zrd8qmN^@%vHF$bST+1KP9n3jQ28oqFA^9$3nC$b%r~U(=#6uk79#Dj>U0q)$sv<-! zNj60@zWLh+1bR22%#7@?R^{+TPdNlMkVf*;1TafRlGpL8Zb8HqF((KcaX>$5&%JPxMsS}ob&SJ~;fy~aA1Nty&>JOel4hR~nT zp8h>Y`R98b;JWh8TA7+2g>g4r0)ljS#nXkIs>7kd|M~=B$%hbK%d`vN11`10%DDkS zuZBbgJ_K2SuFZ2WMCx(7Nc6Y^goohBH-l7CuTCJwlJrlHP%|KA)qvmb*cTfc`=BLF zgF-{L?z#9GWkNgv722B8$~DY*ItZIx_q~RUm%fJi)hF!>OG~ib&&taBT`aY?!Ox8?R6a zm4(9AZ5OQES<4N^_m;wjM>FPIG$`G`duV2~&7GqdY%Anzn^-t7eSU{TvqN7) zylfJHfdrrFqSEZc&$c-dzBYNx*=^fXc$UQy@z*YjNi8WM~I9l znCIOR@6KxRQ?RpQ>sO2)bG=L1{XqVIow0e^qjEH^S>s;)W3$mie+Tg_?nc6cZG@=SRaZb?onR(LamG zA*Up+$Zp)VwCm#D*o7O@4z=&h{aKWX)ji#QZe|Ktz}pgLjHDb*M~=^q5y)7nzg1jb zi2iX$zuyU78!t z^_p*+sCjt(mu)W~ff^zXL$^{IvQhnusuWdRHc@iN+2*3QVK{f^BJ1kvPTS>SO5UuG z7n^I#Sga;B)E^3}yg@A09LtTvO(|kJY=+m*NlO=<2s3P7!S5xRO^dY&FU&?3Uvim4@C85;s4LUI86Cv5M~oiE~%dDU%#F{!Xh z+}`hgc(r%Q*|F+s`9V-i@$?bCw5f*H4v&dJlqG+36xNG4dh>8N=*Pw=C3Py2wZ zO+n(DlSbmUyX1_fV3v%We1QA$QYAWx6BJMxZIhFe`|Kcd{ogMAcd7uanODUtFl=T> zM$LLOmKIHY`3Dk);&sc}kl~M&PR^{832_B9;HmF*K$-b+B6#+&0+yd8DTR%AFW{4l z=aC4oq#V~d$C-NLVs>%JS%uO=Nd$(ESLb}UL*>P+LoRr0QA4PEzD`&%GcFj8UiTK= z6XgsNhcZw|2-_3u>#vj<6X7Ixt1L$5Ek@j804OzD>138N#mAvFLPI{rr0?$0GNRQ$ zJBG2^7Y1IZetokI)JMwI<&no+D5T3T=7wDX0Mug>C*{!4VjbR~QMx=Z;B<*9WernB zllkPV$EZr~mZFyIa3p+fewG6o{~-B9mVYM!IRnZ7lIfBOSM(V6DQD;q zctQ0meXR(UOJ+u@y5UG8b|ax2jdXF-SgpY#AYI_l zL4N=}Xu?E&dNvx3ewubEfHkY*M99U2aUyUefzO{m-&sh@(iU|-P3nexMX zLI#(hy95Hwgv&;>ZKx%XKrq+Q>j za}K3E{G)CEjqr=;>01w#b{{lY=TVkl_;4HbR#k8FV64chlWvaZwIJAl0n zuQk)ep|@0;#iFNH^Y$J5El31dS@ul8C7{C{G|JorZuiO_aDgBgRE`IV?NoO@#jcC5 zFUp}nu2jwdDG-6&M33}WM66pxN$?TXILV1bhl;3u2s$FAJPd|eI+8g{p7E0kOCYzj zjK*^@D8!Q*@FDr$*m}bPWJs0vOe!E2Mw!^h$Q0<^dd;nmLOQT{e;IyAcGi zbZz}EqhDe9M>0qM?O(ps6hLLfef3yYmAS6V-ZK@b4=;*~XN|UTKYcZRw|1doY;5=W zyHc^W&E~01rQpFQG-CpqK!jPiL(5kJ$us~71$zPlq*L7VCO`xNi6leC)_I~&A2a9h zv#C{joAsV;sDhVxq-Z;G`L_*|{H93GF^c2HywAdaJBzsA##DKw&?A8eylx0U0wc8v za8hFu*^6@lzM~;71S`5gTf8podcZYpan9*?Xt33J$#U_=U}<| z4{S{Pe@=ZyH%vWZJA@x20|}*M4k3;X;?e+{fh1KyO@kvcu9lVgRuZ2_{^-AELaWi! z?NJY#g?#|F5%UD0HAzy4uoLkozTgpK#0@c`%7gPzlpTXvX-RCj_3GU;q=GVUvQXj6 zB!?LkL17d;R>K(ky<2*`Lm5oO=KO{(VKOLHI}0I8>lUzfp=8amR_ z(o!@?TM&w_1uWuvnNqw_on%RUx4@S@q+0-5{0j(MjRaAgjpZS$UQSNV>zmJzF8?Gh zE(pBW(t04L=kRT=BImDJ#YtFu&fOEk9E%=vY7rV3gGP2D5O@kjH@<~YarN*pq2eVI zlc19R$|?jq{PE_HHmaT|UuM?%_AZNyT;9nx{xCR0DPo{La!6Z9XN^f6$xqLYk(dYo zkHt%Mp$UW(GVJYl#m07eqLdpkvF@;TVH$&UrLsEkXz4ftA~rk??@YUf9<1P?Rp%KXCoRjFO#mr&vt_MdTbRQ6ZsF?d_e9&wXcdt&hEC`ebO;)7PPu^fl{Zx1{W+YxXWvVlk)y8u;_KP#*T=DM@BGU&Y4QvK zG9Re70`>rtQueE;?C+j4Z>uIN2H3F6@AmoZzV|2DG#J3d@HTFb?Gn>R97|otn(wa` zks$Os!|PY-(lI+Yy>_@$9-%RkhSfiGmA$3}byBNjUq1|njVv;)x7hPp-LeW&J_|qx z-XG*@Pk!)ItYW%x*oI@5V`KYm;|ISp@jUX3bFX(yc=+Kijbgaym1oApn9C03FDAGL z|H0U$Y_Q>{N@H9!_MGQPL%{d2mP!hiX8IDtW{yPGE%sfHd-}A_K@7@rxda6TL8U<| z$pX%8S~@mtk{J8s$sbVmiMY41@L*1^_DvHt^XMZ(T-@BWavB{_%I_oUN-4U%c;uXm zpraim6=MCK$eEC#pZ82kg~fPQwxR=l^(LRH^n?xk4Y{yo9vBP@8X4%Y-1EqvPy?uh5K52j`S3{ z%r+_Uk&SAr9Lg4S<<~!I_`UwLbSr=xg8R8+8k_{aPWtU70z73cBnclV`RUM6q{dfA z*7T9Jou2!zbN=KWQVcS{ka<$L982+%SJTUtWok*lF&~Fsm{+N~TYda_6~VH^W8y@) zr5h;QsxF_ID?Wbm%@fqz75feKF05}EN+zYnJBun;mK8j#jIr=+N6B>s5Ut|I@;s?ww~2+x*=xm;HXE$?Ch+_ z%rEjr;C=EX^|$m8&8hC^6%+{kylT;pAKzc6JCvXg-NXh=&dlZ5j=W2HO|j}^Q74B@ z)_*jaXT(oVtStweIj-_OA7j-Q{quMOwInd`J3haiwgv^ArA7)XXJpj&G?~~}_D8dv zdN2|86MezpRoe>*&9I9DXA!o?R7xn>N=E5#k=#LzkKPZl+SyQ1Ksap*T>YWKMSBy1J@lZeW1u8&@q z=%P|{=ctJzq%!NYgy4(E13yws{^$yf2(PQVJk^nl+>q%T+r=YZxqUMZv`^Hlk6YzA z_x?t{hCO?A1>}UAFaN90J8fB^c$gXIYl#Uk%BD*-@?%%9XHT!#_@pxn7$7g7H0})e zVcxsUv*R@>v_NZ75?0?`t|~(H1dhPV)(w^Q{Ol}9I1{4S2dIe#hCeq#jy4oQ^D%UP z4NY~ba%>ltLgKB#Ep=L3p5^?ddpP&#s_ac{bighBuzgFbYomkMb$idNyLc1q^A2a| zWe*K55g66b zAT&mfq)dE6>SA=kz)l8nPN&_F?28R+f<=oMH#Pyaw;>7!O?LWnlS9Uym!t*2FW36=d zN&A(cUz`7~t&02-uA4QT$7bydxZnLO8h!UambLqRSS>Z&ILctIc{Nprwzfx8*6EyT zh+?ZoJoDj}(Up)K|4u7i=fCZVW7k^V&doWymXJGCRUTc=(%sX@G}!XLhi=>f^5^GU z3wnCyQ)CdK!ia1ix z+y2G&$!A{fQK~86%xMa-Z$GbweC@=iuQbSCpAaRouMu3#%@v|em-Kxp@uR)PKMa5T z_>xDO>D%VCo#%e03E{`TFaK+tt|d(l1^nJ+$j|(zo*{+4|Gt(!wF@f*8h3?8CDJ9j zNZE>B)-qMB3JJLS^#XrPZ>fImwMU z4qElAn-8z{*BDK+a*eAW#bsC3#3|HLbQy~`=G!Ue`!y3{Lu=MMU-Bp%KtA^K%df45 zkpIgc+e6eZ!$=pa1vxEX*AvfY9uMFR8|n{-Hyn=W!yH3i)UUa9s4f%8jRdqVjueeD>D{C|JFRA1dhfWo?Frg8E9 zGDEc{zoqf}az2dp0_y&dF%yRF)u%GdjE)>5_P@Aby#Wtno@rF{6n8uz zbO`Uhc2gkFMKdNLFgHml>4|`*oU??9a3s$qD@nKwHYa6#UBU>+d)IRq?GbK#Ffso1fi_nJ}>Be_uIv62!8@Sqs8tesTu5wF`-$zCMRzVe~`b|M@%S zl-dex8K(SjzY-quZE^3c+sRTR)S@}_RR>&FHgmi_t8Cd~{Ra-ud*`}O8OR&21YvpN zQz}PKi(a<)_1FuY2SbhF7`r(d$DBc|`2S%~stu z2CU#fy9qFHilq$>pmB0 z)#z@rJD*h011{2*i zBgIjsD?CnoT2FaIUroB88)U{Ta|f^f|HD??vYjT)rnsfd8YjqQMqGShHN9obnF(gy zvPLJZr{bi{AEtUZ2#SkuY#7t`TZZ411a@-1dkXWk_X%;LD`gK21H=CFb;9lgcypwS z01Cy)vt_GwNH(*{|FO7+nK#JtWUyeL60;Jp!n@)n9$gAk@|w}P>Ni-_;j-At9D7Bj zv&_FY{bBlh)D~M<$hKb3;EAuS)R$hO>KhTxk#)s!A3yPHW(wp|iWvW@3Y9z1R&;mc z@eXZ$JWskr2>1bHHis(~R=>VOV4^8@xo`wnf{1sxsMx+;iDgnVSC9e^j3w{^Y+ZaqI7(=c$+)v$h4tiY@has}_H zJ|fl3Up1~>Py~ej*ZDe$-txqp($`I^)|S?nFl(Po*1cTqZxBj!V_zywwMNjE^%$SA zOcP~F5#GG@nA0AdXFU!5sc*=~M z_-Ac7M*T(FG`eS-CUC=pl?pUJa7#T%r&w+?i&ui(+iEM>ZZ=L-KI;zg6AS^gR?yb9 zKQ{cNfC~F5mB{fBAt^5A=;uUjS<|0?)WsPEHT-!o)3|g$-+Eh^S)+>i!WwwLt$qvX zoY)m%O+Z6*F+V}(2;^)1FOx8>4Qm^Ymbux1M8pXfWS|Pc${${32) zqG%N7`7K5X^BnKd7szJfd)&LWwy7NB^v=L}bXI64zQ$(IrVq~K_wPS4DL>TboGlaE zFOK(HSDe??Ghb-FH`;4ZL#fej@#2wnmH?34#(&+v%2mgL^5UHM!FyUKnGej%Oz&m> z6Zr#^nu1+&FdrVcH(HA2myyCLS-s$G1K$=O!x;BS(s8&vHQJ=i>vMYJ%|C3t?bM5p zhV%|SJpe9hCMoHhTm^sO7642bIi!R(T;K8*Ktx zbNtI5u5j1-ht2d)8!(5O9JJ@GWg2i6LfIzpOsa!}U*;rU-9me{cGI_^5|70sFfEM# zRQ5mMk}V68d4p$~|4)9l8Rt|euYSf80>g~4B7j8Sh#A#U7U>QDF`n+P{{f9}IkNe0 z(bj0C=V4{Y1O??THndq1T#myzvR7A+iuX%9n8LjA4xTMH9{j{UFNV~Xq4%#cdA|51 zhs%_ag^kx`ZF!xodAwo4i-OnRmkq<(EqzGD_j=0VY_J|@0i1#RcXoYV??tQS`}ctn zw65iY8ke0i52b9lC#a6GfBUHG{)&K||0a5Ciloj@e*9DXbp{}$Rg*P!<_%1q-?DWO zv>s3Dbr!~vEANe-7P*Y2FFtGeNP+E%8}=S_C+A5au4jY*^Ud!3TUKB9Wrb@=P&Sda zOy|RhA%K4ao8I>Y=j-FdyaR#rCE8>~Gov(jECA+(4u4bEy>g;tu5O+hReN*#QrN&v$<@Q2b^(n&aWC)nOv3D zlDktL9c4bezmoFiQ=%eG-)E+5^z`ZXl0LkhS3HV=$=~^d(TqLT%idvfdJpKs3z!INq#( zvKjy2^_#YyHo1^!LI#xVH`Rv8_Vty=-7`D6yDb+vmEMb+guwLD!OIenJ`+u9UiM^H z-LNL6Hf}QU*QUfadEynKZQyPJI}^`MN?NFz;BH5dI~)~J^gK;jzjJJjoC;1GkK5t5 z#W0j87Uw)9E#ifnG+Oj&-GmT)ftVt;$jyV;0jYd4vq&=v1&+4PB3Rp(pX+Qe#QB1o zcX~y3t~brun8U>HB*I^NF%*F=?#oGA?~wvlXrk{ypmrM5>+qk_*gQ`;Nq~A*rrhPH zPm2t><;Y8oy1w}N^cANUyeY|C_XIwYz_u6%wQU#|&V8FLlRuB$0;JNfHL+1=S+MhB zQkfwN^r#N1$-3@l?;543{Xc+-DT}bibV!wU8r6xxN4Gb&AYIcCrws%wVXjX{=rR=Dh3+dr5ka~_#qBX9z9xt1By4~! z=GP#9d;z+Q^le<7KT+G-+SdAMbmd0^ui9SS9qr-R;BA>@bpCz|{3GWV-U~mP)7>+( zvMNo_bkxx=KmYjy+j9#J%%P2UsBNAj#g$v;)%fHIdhUFkKe-o1|KrOZvslir(nX{(eoF2rxe|>Jn?o3X-SE#1ejiyA z*>>ISIMwN!m7QJn{{3bDtsggeM9O5PrBswrpV4Gw5IooF8Y!5T*zO77J5GDJI z7iR!*1|icylQ9W|)aJ;8=cSl7;6j$RjXgxJVY%B?An|I<>2^S9F7m!1i zc`Kj2YTy>`r;sLgmc(s+kl^e#VJt^2hv@sRZjPjdnlP!%U$DcT@HBIugIDw#6w{eG zAE4Gm5C6*h{mjzpf5cgmlDchsvw=F&Xt6XY<3gUq10Zrj&7IED_E4U%<^(y-c-bcb z7LnF?=I!_swR!jMI(wS+S<=>sP13PFb^7vHc|HQ;?#vZjTkw3D4GeEjjswDRl6=M1 z-*LvtZlE?t!ao_%8>bH?_03eDff$6TM+*`{VcG!XYyGFJ05y%Chltd_QDiNi({Y+m z?>0CQeOO#DBfZ0VcrvDr893E5s2Q;zDfyca@#A)IG*k=`s zn)VX*CN1Kc*9otko*UF}XfrrdFUF?cm6bvQLS*~uyB}B+p*HwV%u{5|l9Ke&b^e~s zjX5wChW!PHQlAgM>;>3hbC*W%zCC)Lf7Q416ZMs=rTpEktDX6-is@g#R2&qnhwTaW zi0r|oRCFU0RIv~!>kncY1UZ?1V9NibCmbdWYVVMk{=oXe!lPr#J&80?Y3k@>OC z;G^Q_V!n`hC{C41%9TE^Wz-V}RpLn41eH5L=KEN`wPgHV) zG(pM6ZwAYp+iIad(a~#z^ci}FroEQG(y9pAvlIGSZzf`oU)#0nB!+!AoM?Y zHGY0+Pk|~8+!YW%JXssr194_cG~IP$^zC*3!6zSP{%+%s{Iafa{6rhAV$zxQ?maRw}zu)Vh)i~yQ9#8mx*(tXTG{mh5=mI~ph|kDf zlfo$ic{|xLi<#=@Tg&5=b-lp6L;tL=8g%$UzOsUy5|B&4e?mkl2?qR)#mp4GEo$R& z?j!ZM|GqHafj5z&x88>-orBLOv;;0L&_*TW&gv(svGN3(WgUtj5+2T+ye6?>-Z-~# z@1Kv)x438CD0~>k!62J3QF4}qBv0F2r58FlrK$HHyIm&Cncro4VKWM>@YOGJmfmH> zg;2yIqKa7G(#1t6!;b_b9$g-;u#KDufhgmZh0jewH(kWVX$cq8*_oeH;kelu@?UL~ zS-Sm(LNOnvDAeP8+SKI^gO>}pydQs1kRQ4R+@NDqr!x#V9wkA`cBcX({?Q z2rE`xupKek-jkljOBi~Q5G3_OOoy;rMe>w(%h!XhziCS-h!6<|5;$;&C){Apj5CyY z!)`9RL#5iccQ+o+mT&XU?T-fP(|m0VheV}rdCMebC{Wt~?j#0lmGJ_X$Q(%jsVke; z!J5`B#uT(ADLViS$wpivHYyzgq+a}*5e4t_OYgC~40o)GH;8Pn-k1?fL=^V_R0MbP zCt+1e{#s7}7^PUn~d;7`u@2jf)p zH%3FD!amH=s0a=&Uy0B!@sW7h{;5Ltk*SGzCg3%fq{ZhVD2nkp2%tkUi2@PQn!D#7T~d1S5F%p}#9^O} zIMv9f!^2}^AS&8;B1f^!{x+H&iFa`vn#&1BDCtB)Ifm|DnK3c~0A5bZ?uX-%%Vbqi zo^-+G#DgzA@KBg7F*EpxVO`#YCR#szH0v}4*q1;EvMqQchG-`K!O-VHO#{AYt`LmI z^GOjL^O-6rRO?NckL~{=?7IV+JiE7PeU(8DS4GtPpk*ASC(D6N1wI`o7=uhs2Egxu1KS`;6;c zrxKhi2xhd&+$x$zide9KsFD8j-3Pdst5u*19-hR%L3cRK310XY|1mJ6fKh;Aq14 z^_pJ(n(U`l>#HK0sMicgfdeKuOll~~EvJXtT-@``Ex z18<9jR{DI=GRT~*){%G$6K$98{Ow=BkF4_gl<6X@OabIRmX;U4fH>Y?yi@@b#shiumaMe)6<~BKd6`#(#x# z92Cacs{s_}pW58hA|jeD2XAE>NtG7G%@lwWz^XcWN0i20O^^PghO`E)5~~<)_%&lW z&LS)H>cuy4k#ikRc4^iKAe*N32x3f9TiOKY@Sy|BSmz|y#Rcc>|$Ybm?0HUQ3 zor8ACr`+BRn?V2Mi-jWw2}vb*BTMUy?1p%J*7A8SXlXV2?J3&71HVCZr5IB-pf<~7 z@!q7nNd}hLkh1vh*7N$hN|r$RauNxT@|pjw#fOMMFjYi{wZOg|Lrx1m6yCafeJm6@ zL#_(lzoF~G(!s@jNKsb!>+bR?MNB=&kuzLn)cJXpLR@5cIOVLQmcNI)nsG;&0>Z`% zm#j0#oeb=RDg62Ad}+?+ zjCdIx2&vQ2Q`N$4zhlumoh5X&AzrqEWZ(&@`~QTeb5i3PunpuIncm2P4C9ot{LK_! zoEF3;%8G9zNHWbQuu5#u2oCP+n9O^uUTvoE(bl^LshyQd%is$THS z&#eA|N{F5F>e} zorT13hRpkFi0fm&xb6G>kF7PmFPF^|+0#~0-xk*V_xRRW0EB%R1Te7=g_>-brFEbT zEX5Z@aYb2f*Huuf^(4Yk(desR9MzHlL5>@QWcUUJu#F-hqq%Z7*8&pV_?^&_4vnQ& zRZp(xQ$f@5RulW~o|g`Pdg6}Ho$;IZd(Nbn?V}vo*WmMc;>bOoi1(@ybK@5;G_dac zYUUcj&vm}IPFd~`bEXbJB@g|$Ik2=k^5;`NyY*$YYEbvW zZPUXqwr0gM)aZ&AWBo3j+*K)=Yo#XEKlNz%(X7RbrzxKYvH}SlQ1S8t`O$;qv9-F| zpp14(FD+p1-K{-s&dWnmk+Rw->ff?8qd-tGSf$|9mM!V^+tzcxG<4hLa3Y>3B+O6} zp>|t+*{J55W$`}5e!Cm@)Al#3SLO1XIjR50qXNQU)jq7?Z!7heGs?D#(hb@<#G)1% z^wq>hAgaQLa+-NAj*IGU?_J%txf!ncmRm}bOr2w0-7^C1BXvZ&&EPrLeCxul61q0c zwHgP7Ch_r|r`!oY1D;T6wkWym<&GW_$+72E!lId6(eQmf)t|xaIvGRZK2xFks_jwn z40vFkr5OZH^sy30S_G08CSo1?9+V9)-H};3x4vNyW#zoe*|J*79mdpuI=8+KY{sV2 z1HwJQc+^0ad>cVAK3gud$@vS=3r6X66SCXRsm{H-vnM)6C)bGUWd6tP^E{}|`2zg5 z3rpr#$qfWCypdd7171-!ZE1dszDlvgjkM_MY#FxMms-;EpaDo&M6QbZd;c;)tK{v} z4~L$m4K5b3&pVg1)mirf>p#TS9Pk(uFbJH%F^#sqC=gWh*j>ZPEJqsrvV1xtFS%D? z&MK`#MS~*jc@-24a!d9O3^0e{RD=Nl3(M|+*OZOslg$fHp2!;oj~{)pL0p`>Ik&GK zW${**!zt9>f)-IlYD9YWj^JH23_eYN+g&$eM3;{fp7|rIHfe~CQg50K4!A|tsVAJkooypHj;JStBiLtv%L+`r@}trToq$I?%OkdPO0`1ltI>`ZJ>CdU$8C7v!-Se2%%kt-X9wwgg z4sP(9>%Wb8V}g57?n-NITl)M|qYgP_>p1u;0&Y#qfn_u%HL+cfI@L057{e-27pTPF(@#Q@ zTal~%r@wW+8O3Hg*40ED*|vzD0^*48%OZBf8vz zP2Or(Uzdf8+u>Hc{Va{jkZURhjWvQgQef6}xp0@UWLmB*RaaK~$>x&a4}5$&PN-ep zBuTCxC!}t5jMvo;vh}*yRJM3QJRp$qMzh017YDY#l}I{GneLl6yH6LpS#xa-vPhSE zQDJoZe{URDSr9*~NQevV;w;z)dQ`)g`&i`_K;})IdMJelWRLL&|X-Ox8N?3jb zi)f1`ou8K1d%Mz-*S9cI;S(#sH*1TR)@jd~)wRTjI((T}-}pYpFeTq%*IN7gRT5me zTPV3|o8)ZV=2B1)&|tlEDqVH)OZxj*bx57mkdx%yq?vtoWh!bs>g@9b0Hh2K?&YU~ z*xUjnA(;443R<#(I^c`c*U`%6?1fcZxy$T{(JAlj5xeWlzjf6svCr#ZJb!J46JSn5 zWUGndWAKLp$pbPrlH1wG%peGfQWKkcoGEkoE|uC*%L@9(k&zi@ltDc@I|=N4BJ$Q& zR=T_mlZgNRWa{T%mFSg=w&JGNG104}Hs{7RconY@ZMG6*WhN8Js_)5~`{PfAHRgRU z#{EUy*I$G%ie@ItnjR!Y4;73)U`!P8R{1a`%K6_`N9+7egtXrhl|P!|N7qVeIuX{O z9yMOEIELT!#8akCK={MrKf2vs(gwNebwg=ts9=S##u5Q?MASv#*j>JIwtL6L*^O9b7tKnb19ukw;_K~S_e%%^ z4yKoHhw)Z2ZKr5vwY9Yepg@I9Q0|>f8E#WNqN&omQ>J?Z*96tkv$bYBy}ZUYg)a1K)jXf zR^G~J8IudtFs{xrPbKQq!KL9Dh7gCy@dVz+%wb!9sJ8T>zwbx zq1`FP4l3(g&F<4CV6=F->u`R2>+Avw$uIk%#A5&dd8TuUSAMNRQx-HC9^={%!2I3*m8b=le}j z^Z>l0ywJ4lOQLDR+i_REzkRcvz~6v5MSa89py}e~mQ|ityje=UOjfo?QK5^Sx?O+2 z4r#>j3xhhro|F0M<91c*iXXlZ`?*H|=mzf8l0NO68<$YW`zNe2lr z22{Jr{+9!g)&-*?h9~{?o~Vd|dI_qKV3LxWY@C?TG7yKyhVkK0VjWi5sh8qocj?k^ zu%o+UV7_7G5DBDqi!)rJm{r;m`N<&#;UX3@p(Pu3wV{l)pOg}HQFS;*#b0uUuOU2MTceo>TxCnCo2rv9__2a)ToM7|*)p2ZYd@Zapi2jaH z16&48_d@79-EdcY?b>dcze!ftAb`7$`?7Wy#P`mB;A~ZWMWxuzGAz4K=ZRUwfVfWb zD_?i+jg6Y#P-0zj}BVPjoDO7ev1LQelImi5YFo8aI{LaB4;34EwZx_wUktGO;Cv& zodX8TxPQ?Y{^bbfc9?q_&aNCJ+_wRaZSqa4?B61vZ5>0aOKf^Yb8A5_4Yn97`bf8y zmCw1}mw*BcoU0;bmD(4g*J26|hc_DX&v2EW7CeiTb#x2>?8V)x{rW4;e)#FpyP+5J z-FhRf^0r5~6YeZ_=E*g|nG-~Aqil{U>2R!j6<=jWeCFP_XT|yM_imMXciRh+gAb^R zU;sMpG9nn1C{vwWQ;(A0!$kJ_-@=8vxyvate zowbXsVyrS^Z@`N3nkgeo>Q`awYZv>xA_C1cX+wD)X} z6dYo{4U8LlD%(rWEnFw359X3<%i}uBlKg+?j_l#<|Dx$5LDBX%L&L*^Z$dY$2~r2D zHM^@2*^Xq3kx|e*}hux^PJ)P80!+07cX5d4$?Z_>0eP*o8#*gWk0Ld9& z=A)g)k~#znZj?WyP7@g&^WHE%ZHIyF9Xnggs-ez|+O&7J6<#Q&23W1nb?Azsm!>cO z+@ACDV9ApuQxFKUL%h;%-y+jC$PSb zW|fk{!KhXYwUWz?dno0`o#`@~a3t&0pL#7Q-nxg9AUGlurT0zwchyF;#SJ`uCx8ov zr|p!F3Giw~j!!K#1k>_bpmvk*r(+`}B_)P0b2ABDVb}LeBzXcg_a1)r_jfW{oslB# z1%1A%mQaeUpFDr0L%q32&kcRH<8>?0RaB-F=I3vnBrx2Z9jx^rRXWQJ*aFtL*k^R^ zXR8(_j|a9fgJo=(mv(NZ_$d2bNxVuCRSobm2-2%36xpM7yJK8Mg8dq1etj7d4tzcD z97u23lsxLxfb2u4oc0Xo0%S_R%1bfa^A{&cz^r#>FawJ34vwZ_IpeKnP6<$5>)5^c z8e3Txy!R(Q9se0x!+Y(;$+Al`DXs5gYxPHnBqrdXVnH@O!gB2_9&9d0|1Dzeg@Aq` zX1rpk=K_Dng*qTTct(T8Rzmv^UCKDSdzNL8rn_-R%PVuA2eT7vk~((Q1A9%S_7OB| zsWi+3b>E}p)n3r~j^}4s&pJ>G*5r5sZ-$@2T1Oz8e+LYGlYuNCR2poqp&jlJ9nPD% zF1BRTue9L+V+u<_7=vSDWAy^kk9dtV9yRCestO03NtX89sfGT*19J;@`p?^J=h~;` z3&{x%TqVRt8L`SLfXh6s)4KS|PO1YnjL$KZOhtxKh^0Ye?;NCw{@%QLogfgh=Mf4ilc~if( z7p>~-mjqZ#Ek!~WaTLnSyP=Os9wpa^AFLEMtTB)6$XVOT>J z6m@wvGoaS~(@Sp@P;qdc17TD0lSQ~va)J>?A$}hoY}FWR|AtR~z-YCus{WDUy&C+h zsW%%CZrK8aL}MPvJ+~v>Q$Ek1l4m`9gJz9`Wqb0P0u;`67?>G(ds?E5iDan7qYz(+Cr=$jy&$K_K>LC3Y1feIVCxo*=wbAkD0h^2sN+S(TUhRh)l*?ae1wSt zRWZc?SN&0pop&v_?PFk5Y4GL8le26bTknOA-lLmpX7xI0iUH3Kb5$jbQwqA-r-|6f~@(2zNzKr&RS@Q#yX9EU~)mqM>Bu@MT{60d> z&1-^+SaXb~bYjXOg)Nu`b0nXYC}Y}_lqm8bQ7lV4_@eyD!7ciXSeaA;9z80PRw{e& zmlBPg+;02a1s}adltiDQKhhX*wYRr#)SKTV{m&2uj<6@gWCT$Hal0J$MWdS%5p&sPn z`PGxonrSt*K0_4PZ*emrA)!FgbaVA+bDDwjhSvArAKE0Qw&Zz%6Q99`c1#80FcrL3CQ*bQW4Z_ zVjpN(e)of?u8X}x$X&-;Gp3<7ZYrT~0n_&fl~~TA!eMlF)eUmI|7&ysabAlZl#{!6 z680|?Ok`ggt4$igSfWW%*0+E@Hci403wP&WmMS2py(Rq!5PL$c+Ekw*Agiw3k<6SHUEis!Q3myFX@QD07cVP>uv$z7ad zu;Ig>2SH~vPz0#FvV&Ia^{Ea`pwQVAKxxJ@k`$U4qpdNI_y zoayMe*tJg57*!d9D{Fgu=n!ySz;E_Eo}ceK-wt;b0hB2{TC6 zEYcqS2&hyQ`$leu8k`1lkh$Yj{?LCQRNx$jaX<6Rbd>wk#>iI0E7vZ` zdJTmXop8#_1h#sN4)yu#>L=`=2AD3x3L3TXG%NKxUS6c;D3C_ z5~(Vuf>(BNsBA9zj;G!ErRrs)PKwX^(AtxQ^t#N6&<&w~;`c)%uHW}4qmAAB9-Q(f zGZP>d^fLuSw`C^OYwyMIogmea-XE&%upSpD$}DIcXw9%o_%z=Oz7>R8$%5@*nZe}t z#Px3iG>iN(K`x!eK(i;)Bs|67yxAn8bbr%!7Ss{~H-ClC6x8Q^htR!h46>&9*s@gS zYq6zcopjwZXR<=8kr9tUvb#fxR;MUZ>;|@ynXiVt+DVe9fxck6_Gm6szZ3l{xQZfy zp`yx1b(V`G;>jr}m{7492wt5lzT87joF+&7l3owt%7BJ5n^Mj!G6(`i-*0g=Z4kE< zTYeXO1g;Vw%T5LMy2`v76Jw^J?`N08e7*zHJ^_S282ES*qjSnJiW%Amc4LW;Wp{zH zBvfo$@c8XT`ITK%(oE=0jSE@z5QgH8jEw_ga^_qIKc^uSOne#uS|DUa#MMA0N@OTN zi#Ye;Vqov-LV^14<@x#fKe39NN0Io=)Cfr@qe&`g^J9I%u5GERTlW5%vebT!+inOS z5^>8peu;N2!Zh1bpY#s1K!HN&h%7;Pkzsk1)o92ga_AWivL_8fw2fXer{RP`&M{~u zB&HBWI;okqB@2w*Rsq|Qir0dw!Mj9_vd`WeNYODU%XiS5Q=;%yfjiB}#ffWd@pMPP z+71zs3@oJqNuEI<6a#DRxbPb2(qU{|9l&b=rr|l|F1G!^cK33o&-w)O9%01(7R&OU zwV!)fXF@>j1;wXL>S&6}fjgZUTpugcP(XbA%BOwyOf6E$h%9ZXM@T}`>o@)KJpeNf zBAcJoi)?iD`WiZcj+ZYiMQ#cSvEU{Ryl~+$?kefrn04%0doMDz#flK{TL4Bke`qM# z()#9%2{*#Rr3=b=i`b>0V{9?TS)HMLR9^~X%eE#JZiG9{B1IEgmpiWvLQZlyNH@L} zLKSf`Ry#%#!MmOUE+#i6uJ zSX8lz!;Xwtw+{7>{vbh>3s|No@hA+GF^74V2t+C`gU-ZJ_*m2Gtm^bhkp8DRhfO{j z8M7=P7pi(g#--6f*VG4(xQiVyP&itsuyg4GT(@x2l1urv&@v7Ank`$nVvr}sOpUt& zzy7h=fW)$uHE6pjNFg>=MMkKA)d_Ff?#rMi*@DS`Kv8i!xJX+QpkCtD%PL*!$PopF z?YBV_Ik6B|9&F1{51&Ac{|W3<0)#IN1$<(3uF{j<^KH}u58;?FI|{e4+dApN&6!?2 z^Fye-3{};~#zvRGraN)_u^c!Y-;A8Ypgwv{O-{fWrwK(8o+)*SD+ook)YBS#iL?gz zzN;2xQDc1*1kCLg-k7hkTWE5A;Tfv2bs|43Et>CR`i>);kMr`uLvb!wA0%*Dm^vYv7 zbE%!RXHs3ZF@CLmcb3UA6%5jswacwb9|evWx%4345QHQY&aH|D;qBZ1_$^Q9F-KkC ziE4F$ueYCs$w-GV77Ptvk%_i&{|&WL0~O-{ehnwjw^_Mr^&d;okKxpF%GOQb!Af)vG_o~f0o zMs}Aq?XPj901&KaMqVj87^%_@3MexF^Y}gnz6DFcLGSwAih}1-3}t|&MTr{fXqgz@ z7D!B9-n#0vUW@b{5DEn)jdS%k*M192aNl#i0NS@r7pNQvbMRB7iZFb_$0^1Ng0BjW zvK_|R7Pr6*;FJZaBCx19q!s0cH^R8;nbe4;q{N6y9pH;(&SQ*TXk@6R)Vz&S!LL(&N1w=jc3^L|%M1uYA) zh;$)FX^SJW0KPnKk<~#*ypmZE>0(OYg2%fx3{zyyL`I~<)P8wuP)*Fv<*lQJ}VKLClc>o0$i3?sUr~b-GZqdmuoT zEPHSV$R_!;e0uIR#*LUAw?4gl$BS$J3X4(Mfk?uxvo!hfc6cYaxY=MRTffmt)%I0)YAUcG@-Eh;6n@_Iua$UDP^T3Y_%U>G;Es|1F zQZ7NJH(`TLO@XT0Y3&d=ao+Sn)tHG9^b*i`zAQo;+3Vb`2}~(Y)(kKP7}i9bj4>pQ zX%S2!C+0QBYO{^eF&0MQYH=$!!2YJ5&I>nS^^`Mwk-;!L*fBtG51vhg4ywFDYBRvR zce*2$uesvX7>xWQP|X=`IyKVLfQbNKa&5ef7DVlpn4gM06r(6?;Ucuv!GO>2%o;1- zwll{)!4{fd zoT}IS6-eD(=}e&*XQoa-7zz~0Kjjb@PL3K_yOO|!V05s9rF@T43FBab290sjtkvqd zj*FWQMQKB$BSC?A3)k$@8;p^*SQ*RqB+jxDM}#r3YHxjX73wWPMO*-m*#uo;jDts{ zg*D+@o;bDvGB}?c5Bp?=T9!(FSMvH6?g45-J1tlW_**$H`Pre0M{p{Dyyji3i4oKF z9_@bu)}g*A_NGPwB)9LY36%I`LYl2UXPMc`6<0hfeJFD4Ik{7)!yqT`cwmNJB`lgY zJ*dG6yzI|>3fWd!#$AZijmm~rI`{~`UjQ( zqZFn0cRUV{jRRs7^#hu}@l+g^2cJ9epTd}Dmc!Wq73kn) zMb(}*AFYd|{rAeLU2BM>Tek$&0+8NwE~EdZ_mc2XK<$9Kx)=y&FidRhqN1WIp&+Jm zBOMW?NG-JnbvA0Lu6#iOrVw0V04xW3-gLYlD+}OWZB<-ln-fuJdpp1;_dz?HD@0F9 zjQjHIpPbNbz$|_l;bw;b-e)LZ34M2cwQ@$l_X_{y1ZmPzh@J-;LxBLJOQL$8=c^Kz&r8{g>ugx;(-%U1;dI>u&&V z1KrY_|LGl5HME=T(ZQi8$5);UcGhVoS!=m1ECYpXgpsnz>l@xX!vhZ@1C*izRlC&Xca%JNvWLkc_!@kl9F?ZpjyOPlabW?y6#87db zp|je;_?;UkAmh1Yl=_HBRamg~R`bE~b>Z zduS5PX228Wm;$yaf{7KmC}~iwK7u2rg~9ND%sI`PcN0#X`uIG)yi-bzWSHA zRNN}yrJomJi6m1U3q*VF%&hWl$U;4>t6SC5f{0yg6qHNti$XlGl*$+p<+!mvHnR^p z98&2y4ET{8pPqD?cb^xE&IOK2;GUHp^oFCwIT91B!gbdeT}5us zd>B<0FFa#C0BO;b@Q@pZ24N$Dh+Cv#Czu@&>9Ba)Suc4ahsMW`_RWw^?bS?amWhHK zPoi_xOo_rI{kN2yK)pGkx{>|gm@Kg0;6fpKjgJx|+Qu1szt|3_FEOs=8lf}6>~X`3 z0s-DRYmh+|kGdPLY`qaCZk!D~C})CFZ{6zhl%Q!74(>P}Blds02ED`DEPQUTWwjc^ z8jKNUFQvv{5~X78=HK006O>v_({W;7+XD_u$XFZeqJETv$>&@Zi{C(OQ$Koy+J32t zeP3jDM=a|ZL4zG|^hqO@C+(ncn$Vr&^N^F{C^N;Ta3y! z*xDYc=f*NHD9O-EnRN*d5GxhNjdO)GSpARLBi)inx@odO!| z-wbRJlhzIhqluXrGqi86Xq0dEQ(AWUR3dC$J9>Ol6vWyGFPsP3C`9$P3>-^09Iho@ zQ!Gi2xxl?WkBX!kxLV+vx($aW;!%g5ku!*HIz>%_BQ7%1-P!Ivk-B(7TOaUo4FxtA zbx}hB0>BJR%oT<6Kr#V^X0PnCilNWV-JF)mU!WK)a)O#zSb&1sfA5fkLrPxG<~o;V zNc_oqo$E5GiR+4*m^tSa7sp#==9!mX{|2E8{o+!V40`-hW`{!Bpvx>84|X|J8d4S1_Oh4K)UpeaQ{20WBRVH@p#6q7nT3=j>aP6hX$BDcM0 zy+#H(7TVMmlsbLOP{4OG+~SE~Wv2;rdfiYw-;UD*+x@@uU1?eQ&t=OxB>rR(1V&okqVz>0#wBWn0R*4!no;R}_k zU|!p$g@`yE#!%fgLgno6xt@SwXRmnUw#723S^zibI{S`MuLS}OvRpppNTF1fE+}`1 zK8;HKg`5gZ2P3z(fWH0p#%ch(=L`>@lb4r#E8t{c?Ii*OY#iFTtMH}F0w?HMYjCx_QTi(yJIKtgdPmmoW``g66 z(Dp?HUk-x*SyNr<*|;df(??C!fYv;yxi9MPKDe$iDs7*@zyIj`x}~ z;rKg9)wF8hY$aV=K+uU_k>@a3`)5`LX{DvFxD*p(u(puUR3~_G0lfJOXdQZ#&`=W< zU$@8Z3p1&J#@9}>7Bb$?7mW!-wqi>X)5A~=Ev+3b7IVv*aZ)7{4nhQ(QDBT1^qi~x zkN1iParQ@Tw&UPjZ2{aW5= zf{1sMkPO4#9FTw{Gt{n?`-FsjOMIBvxuL=_d!6Ib)W-@Fmm$2PWx0U@?i``VTENTwu=>u0^I5fMkYSxY=#zi z5kJ}S@(znC{sJeB{EGXK=WlJ)x|eU0L(i#qduHalqwn*HijE{MW%Y>`@7Fv1ZmKEe z>z!RWckWuYPksh;Bj+Eye9~tgJ+^U>ws)M7nM_Ih`JAgzCz>5JzCvI^*Yp(&cpY$u%BIh7RJoCEc z^4S1l=Bi%ztQoZjy!vf2<1N`AuuvY3d$lk9uQ}|O`&1mP`jGi4TjpI2=@h_)#)a2- zDs3oAR%E(4g9qkXu2q^yk8YZJdb7JP0JPAuG%PEC+xCBWa>Tzb<$k$lmm&nwg|mk3 zo{l@Dpe&o_Ijb$-mq@@7$yK~Nh4F67wTj^9A(%fxz!c$J!fvcs zmR6JM02wpf>MSq*NFwWdvwP|mWGdbJy$<_bLO4qm&u0^%>CNj z*PN5~YcF#mLG@d=C`5c_P|`Kv&a$>u9S^3J7~+;bH8(vFT~b{YFaX0o@?Z4NuVfL0 z`QOw?qTNsoyF+!@$F28fm2&*x;C<09v9e&Mf}QwJA}SBQDo_;xHHhoyZx_db#CgZ& zR4I#sjvZT2XIka=VNJ=C5SBusq4-a=&B76EY)FqJJV5Vnd+HXsWo<4qZv%1S@cjK(e~0}&--{ONX@tj$fl#wGxM5T zoeQoJESh3MSK1AsG)SZ(jlwl?Vfszz*6>_sZh{fIp#;$8$=*ye)FW3EsmIq~q>_MR zI^>GBk-e9H0{<44{@dbNDn_yd>g?qp<_5rJ!-D`^UJu^oh)NF9y>_7g^)k%6{YOx} z9;pT%E-*GSS{|1MzVcYterObP&Q4-9s6iF`v$Af6y~fyYg*hnt_w zjNe{h)=W4Rn2l}@FzQb+S;4TkODkb?kuXaEQuJpX&(x@VZ;(PMlRh(fiG;~$qU77n z&EFDKx*{uTatVvPs3wTb2Q2>1qAy+BQ6Z*QNqkq4y&*fo%u^d06X zh6Xrb1v}BQpO32KznQusIeJJP?P}vXe@lGL1wqhk|0b_;{4>`&$|u(81VQ{Hw%0ic&pEIg!yE{srjfijSp1__kn)iHc? z=c@pQo0Vlbygqd0K_DLd$9F01gN4CH6Sw$eE3_+X|dlLcY(%%UdoE*_ssh)z75K@ zKyYN;>eg`xmAJOJ5@b4YGY-}|dI>S8+e$wNXc6|ha>zAxbK}hVXlViI`6R1X+uY%OZ99VONNnyxyTSNSZDVec4 zHwU^OaynRic|cy*+porduRPkpN}nEUC+GVO7luoAX!2`EL1^ z_1HM90#~aPyQLiP!{=`W{qLrg>jUY{;!>vbT22k`DomUvFW#q5d{v8i`~)kl+y~Wu z9OhB_hLE?BFe7;Ub}GTDYyxsB4=5ydX4S^?4*78OJM~85LySoA#9?8kKXs2`hQ3;k zI+fy2L&v|_FWNS8%NlaFuDcL97MTy<$Gt1bt;neGt?(99#5F-`X+Yf&FLBwlah73z zX?pKlEGQNOqfi=(r#Vk5F+T!PyCF^N#8-ue=djrUY=cfwS5(yPTGF4GI!=xf zXVAxY!y_9iWLt#m;#tSoOIe^iCUrv532d3nhN@z7LI6X_I{T-;E`DLmN&*d(<=0#* z_<2ay#p7$_K_celeM@%hS?E_sQlaPa5~EQC&4!L+%4^tpM{?QB5w5(@!JaN2@$k6Z#umJ zZm`%vGzeU%Q%DB*wOH`3@WCwy03&&NsnhuL1yU`k9XuTA_&I7{v`^4Zj@`nVyjs-y z)dJ?pY5w9_VgX{_#GT}!IJ1CS{e0hg6LMO~Urj}22Q@%70B@BUHhwk`!o%;s?Zc2T zK&%fk(X$@TMZG0LJ*3#cdh-%LwN?y?#FD`ju&l~^Osz_pW=wjfTxd^O$`Q4ng)b}- zlk~g^Y*GKODR6Mhe%%mlHLMx?n51_u}j+5*Lrb9=#a0H^|Yb_KHp@+BrZLCe`$bC0pEN*lXq zd!J8D6im2p<8;_+dLja90lz1Cj5c2K5o8{_NLNJFS1)HARb1vePe)R{*N{P!_6L94D}ehUu!A9PKB-s;-irJo`( zsIjQG{F_RglZ%>J_H>1KH(!z2DS~{2(F0)CF-Us+cR(*?p!M%vuSRhHDN12fkx&| z4c3qXdx;PIWuR>L# zKDQ4%Q9}?u0|s_XLc&O!Kr(8)7;)WA?A?HW4+V0;GuLkN6CYpo^%sD28TGOS-xY*! z);G+{AKSs{mie`1S07Kv888ZOk6C5-m!&k{vMNV%ZM^M#u1qXtfg33$@8u_K-{O1X z$Jm;mLp8n}9yvZ!Vtc@&gLgXU2QkRHfIPd{mZb88F}Jy0M3Y++Ci~XvU-USBJU4;b zNy(3?^>Y81ik_IrLM+V4i8rY7?%r7v5)2JFui)O>y9P>R0|YLXrFL+`YlvE!Zus>6 zjqF5fD;k}Hp&Xy=#uh-9#`?zRR63?WIZ*Ey#~D}Qv%+9$bMGN51^sZWDzqrs0H?F; zTj9qVIrXsw)uu0QeaTm=ClV8NwxdX98-RLS695~4W@1{b7A4qqlzNQ=%D)Mctb_Ll zM6nNsI{d^Qi?@{{@7t#}SeKV>$#!S;HDLnPlY7EP3RC=-4CQ64bqBVWp&9J0JUq97 zq&|3(9xPM9o{wQ48DrAd=>gD&7h>ta-S%#hH8J@t*LJ#BY^^l_H{~4apVImmfoK`1 zyKG0F=-KNxNJKMN)BJnRU^GvR2P{|1;T8kC$pX?Hd4emGr>sKGp z#E)?hYJoLvD5}1-(z2{=ah7z_5Iyj&qT7P7S$|KrTYL?eh1(C+6Mlvikd)balF8yQ zTb@b{z;|7z7`=GNo{n|2Tr&MakJMShu+7IZP}322#Dv1@l7lE_<)?KRZ_M@ClWcT4 z&Xa;+2{coR+|a6+2zM$YTQj?}ubSkM?mmuNYJE?v`W`$M86Y;m#?5Q1f28cYf4@cX zW=L6@P5~S+HE8sdb?Gn@Km`Y&vho!+)aaB3I&?CVe#kmtG_}1IEi;W{Q_CUL1y|Rt zl{KpOq|5Oq3;G0VbC7r{Pb3QPXR>Bd;3lffwbbvF%WcDkI^P#l-V6%L8Bo>T@JT1# zS#6m5-u_1<(ulVkul}5nRPOdmjJHPOz${&wPJa`{hga}dr~5+5-zOb-D$A+qw`y&sU+dS`($#0FiM ziee$WJcZ%P1AwIen+{0E?YO)6-$PN=LfB&8nN#>bBD z!r&+H5)RBP`?*ev$$~GZ_=HBbMYM2IFN{0Nopn>-xi+(c`;IK|vri*BOFo@3%$l z{p)kyCk$54d$=amv7ufQnE{Ls`tj^hTr;_knAM<@#-`@KNMYhG?Fqlfu$8M#`je2K zf4?&$Ofkch=LEa_Si0fKwe5s4>S8CG5yk4m%VqSfkz`I*n|}B*y8wZ@i#vs|Pm6@w z+Q>``bRoKy;|5!`7-d_ZaR6bPCUXs}rXynIiv-pz6vbZmbR`FqqS>3D~AF4~d zI{mrQ>k6v?0LDQ`!o*J-H&y~!x?}|{FW^^3nmW_z)#6yjs4Tr~l90HF1fbI2izXVEDoV)KmF>5<*Zxy zheMmvK9dx@I69|^L*Oq|G7-@_3jh4(YvhEi*9{@~I6E&9tdLc(D}cet?BQz!juo`j ztM~3Jg@w)FEBvdwV^3nWzc4Q4-;QxEzu3^_7W~d2Z!(qmJS|`vy(p18r^9CNVb8?d z&XL)b?5bY$(GOQKzAU=j_KGjZM8E9#@q3%!1+~Jr?)|h&>kq^HeTv!m;S*)O)T@i; zo62Z4ci%c&n*#bV?vj7jQT>Cle}wOP{loXWkMd~NU)}usaAS`riK#4 zr)%jOqh)u%{HcA<|HdHkh^a3YM7Qx{Ycr}W~x0KzAhy7ckJMqyV-L^HCmaD{Y5 z+SSCvmuG^{pO4+f&o3lcDHS}>iAa^q%Gohn%9&KKuDK)7?7Zw(#OjnB) zxuY3fYo0M@)!DNl=mxBMANl%TgJh7t^#l%`zFNx{yhoxtd3>!f5)Is65<3}$qw5K5 zGwJG(;;WdNeYmOMrq7je=I3n1 zTMZ2CRH@$lidR2AEN7h;6L4~wzz9Z}d^$yFuBrWy`)Bu#=IX@7!dz-};6Af+_Hu6J zwUS1e`AdJ?bInu=TFj?(mox*!HakID}ic4=pLqkz_w?#%~ zW`211dZ|5oayAMdp3euEI%D_{6o390k}E9~)d{t|gXZP13y~OQ-}=tZ&dh>>)}eZg z3!nN@j$UB@HD1?UkWmwT2CLT7(EQqw{R&`C!M)|IMY39UadA5v@7T0!QXcD)T~d{{ z&%6*?MG9-TwX|H6c{TN?Z|*!F+Sj*8kCCFjD4wY3`Rq)Db%dmhj6&q;GfVE-(b1dw z;>SEbqm|Rk&N+5zmCJot*TuJ}>)?>aSACatq}7+I@L(m@_`vaN41$N}ZSquP($El= zU6xU+)R06SpdII3x6V<%#e8^li%-cHe*S=ANl658YNyljsH9h2&X=GyyFPRHV%4%J{ooZ zt^eaIKdg0pejvzrsXU@`Xf1U1jUgT$u;<=*NvA;Ziaye@EUqDIp zq95@IAlHMD)uZ$2)#V89M@M?ulP0@uEd}ZsR~jleuqHLv%Gqz6_YJ-jOWMkBX7xup zNdmw2mFrvWNdaAqGqh5C@H9-~Nr$?^wiEFMRcsL#xBR%EGL+c`H}~ToPK;-{H|k38 zN3m5qn}x^PSaUJ2u;yoN7VwDsMs`>M(|U{1ZPk) zOO(-0{=7oRkIA80ZC9!5u!qT|0j)Zs|Cec{`yl zQGqcfp*H>F?*vRx40OPsE#K1cBTR1EXDH2e75uzSeQQBN8YG)L}XHEcKyD9#lH$B{NGk@8>Sz67Ri>mYZ-jw7kTNn zp3&OVGCnA~oEZ@TgI@dr2L<>lJ}WIz1Y`J=R=X!1R~L5zmmML>;<@;{!682Ase zEga9XA^j|X4apU~=&9GdmjMyndxo;118NJ#n?K`;f&qN{`i{GP#-~GW{MC2w3s0mgj9THz-^QGrXu+8&Z-r-nlYG=6eD00{tgA>3@w}y)lTr^9 zrPOe!+C&xqiu~^R@@c@F0Cop!38 z0`tY$d1OZi>BlcR}xZWor^+h;s#KY)O~d-qtmn3MBNF#VPt zY^yqvtg?0n-3LYJz-djEW_)->Xcl3YC;hpLoS;~j{2R&WutPNZclX;>x9$G$3-q@& zsc>vg9BVym8s$QL7O>Xw6)V8~cHb-iS=W_ds``tbf0gG7V-#tnYb}XCt6h1 z`OLJE$+6u}vQ3MxJfYVPQvHq{yM`CZpXgLOq)jaR!vqUQj5VK7%5ja7ABGvR*?4?U zgUH%TqrMi5VMwJ{qs&ha60jPAx5lbAM{fIG{0*zc!P&*-a%a91^T}p+;sRPRG(axy z^JMSqH-G&??lJss$9kzBul>fECSZ8IbA_~p`#R+63-V_z@_3+~U1NSq(N5Fhf$RKE zPNQO2<8!vnV*V{*r-<1iu{=?qU9yXo#{7-W9%}u)!CKblVcZ?%XdxENl+E~qLe~=C zy6JLnI*F#%`Dv=0RYpHWEgXNeBjTOy;rs7zI65TKyI9Ob`x5`d3mCe2envH5(p_gt zy|K*yS|lyqlCT*nS?bqBv`*b!vV0B8@*B-)0l*tLz1tc|PN6)i|O&EE4X4QrJjB+lf zSE?<#s9mn168Yv`^@1?oBI<5UrDhrIvWEHM_X&g3>hDqlHWpXDw6J&6jowzJ>=dqf z2Ax{e=auFyV`95a>i@Cz-tkoT@&9l;DN&(1#!0k{l#J}ml)YEhRkBBMvd^J7l8got zviH`pIVTiFIL2|zbCPVwu@C1s$NfHiuj}{yJ?{Jd{81`-IL>>#UeEP1A1(45_n7LI z!R+XQOAM;2VtEyI^G~ZeZ1vnu2$piinD$s;VSJ#KkE=mRww1O1c!( zpWol2hbAUSAuv!8Z-GzT+`xp{jy3=DjTOpxp}c(%)bt-Q_iSmr56!KuQz$T79d(d!VPTYurxQfvd)<6%VrjHaf(|X^yQ!ad0Aas>m=nBvJ8w=1UhKg>*Ht)3Whw( z2%$_kiA-%4ZY`EreqJ)d%GP;|NtdT=k;Z1HR3|8Z-sw|i3q2TLjw;!|yR2Jv{?_Aj zWja)`fqLw~z(ez@a`TH$xNQiB zGcqujin3eDdTSI@r+M0?OPBsnGw?r}y{QkXlIrY-3kTT#2CRSC=8oE{wJj;`3jAnY z(pXl5XgVO-(34g@`KleGJetW}AE7~DF1DomhAxGahq=uy$KSNxOUzz*mCDPbC#veT zedlKu$s=)K;$dHdaP+f0m!BhNDlK-&>(1s)#pNU#+(eASqoB2+s1PQJKH1l*3Q9#w z+a?i6B-=dKr%cTiS0UbI^TW9z9qzy_&OejUzOmpdu016buJC7iI^dy|)QX7(av8=p z4NpR6Uza>-n!PzQ^EgGTb!!j@Cf)hWQad6;L|i#}~p+m>>V18oG{MQS*b%lb8~^9PCUaxTxRr@867 z-E9F)x&WiwH#Piw%##B5$mz10>8Tx_Kl3=qFYv~ z-s(gzMs%FW2YEbA*7fx{vYa0oHIxr`?yHfGeztW)@~FGDq&xCL%NpL2w%ol!y&HUVF`Etq=-zfmGvX53 zTN6gK-;D0GW7)XJ;E@it+x+ha){*=VR0F!i8_~;R=|8SrMYS%3 z?S~w`A+?Xq_UkJm)b8Vmz-Z)b7WAM&x$(dBpZ6@R#TQm07{72c4 z1#3`%JRnu$F*YLrk+29ws@{IKJU-v%`u+MMzMRayc`lCa1Hkd8=_dOj0$BC2k|PxY z{`(##b)p`)?1xA=c5;$YB-V5B*a~?A!EG)Pv+b2cF>53EwJ+wYeU(l>o#2dFq#Ztd`2TgtjsxY(H^@%iOSQ zZn-EXmS|Vx$jP?*^XI~TVwZ?GnS{C?Le$??0AKS)-}~-QTzh*THq{$SHaDI`KYM1? zQTakim+cr%T@I4|Y2z(vLqRKBCNO&ncQ&YFddB7PP>6l`x4;HbTJmOyB4Z*Iy*c-C z+9D_CXMwe5|@N=ExhkS5y-T1^7VOy|T2^lChB!PRQM3_OecTEji z8os&rnwpvUw2GrFzeOVfW70ao1>l?3Y#3Tdb zF5$|B$*e8$l%`olf--=!$bxAd!0~;aga2L0CAgx-ELv6MQ^3r5s2@mOqoU0OSKT2g z`NrU$+71WIqKIb<`Mvp`H`~VzY;g_ahZjy zJDz}F5q4Je#a^K)pI7b>2wk$F&4o1WY-BH_&oXNfu=iBAw;jyU)-)@5jrtgap!GkC9_;S(G zyx-06xvTE?TR5l_CQ0Xz9M9izi`y=>fqIW`eLrNBKg~x@9}0r)T?1!R3 z&|EN&TIkD#BN3`mTWQXi+~Zw=psH&s570@ugEv%L(a76(g7lfA=zUbDxp2`HQ{|$6 z@8{4cCT0IW&zEq{Xm!ycyuC>O=-3Ux#ElAAPV+>=qTA8e(KXT?joe@hTa+{gfj+iX zb%1u#=XVG!G07=gthV5LR^US^uRZ1$?eC1+Q?xa)a+R%osnI1I{2-_L$NQAbJS2bX zjrSMWw4HN4Ozq9F;VYJ=w+<_-hbYN1Z(R$O%;|*0Z+O^0;pfA>qje;tws?<8m6+xu zzjJXlcRZLp?W$A05>`_)=y1jLb@36BYcS7{SPJQ!2oXy_J1tRl$GVP~S`bX*ocan?jH=ZwAIbpe9A^DpK=~ zh;(;XW`S2G-MpGIE!p<2#~Mjp9JD6n|`y!78e(jK!LOJ zWM}GX#fl(w=Ou+gCX!>`-3pk`OZX7sl`yId;O_;~s z5VF@O>PPRp% zf2A*$W0a;379J~OYq_OdjhPh{VvNiMnZMS|MEO6YW@h3Mlx z|A{Pg!R|euCjJIalh`ZzpO)IzXSzRwX22Ri$+|*v%3!4%>2=K)yO~nPf$>eL$rUH8 ze1u6}@F@lkEK}Lp{bX6+Ta`ZulQKvcaN~dce;NDJ&^0{9zkJk|L|m zHmTDXpRkqM3^rbmmCFQ8QDEA>cLMv8eU_V|JgiQjANHh;M0Pu6ROq3zSBd&JYCh1mrP9$)-%S?Es8yhf&yTsfC?=G0r^_$#S2 z);fdy_U&Xyu9KBG%+;MAk}5KM2Oy2j`!Nd__;{r5_g)?s8jQ)z3}$)6;=op?!1*at zufuebBPqJaD{WTTcpmN}E^~PBYKF?R%!F55Pf}-Rr$D4sI8l!D!gmbHDEr_%tFs;C zSV3HO?^JIHm}?Y%cxKh_Kikv)axkx7m5o|~8+U6@9>7-i`u)i5BzvSyKxY-xPM}LS({Rkka@E(F@Uif0Il_z)pK)pUfHQdp*D{C2=xaR z88%W~>*V{7e0LQMo@_k~9Rk4cGw0Vu(}gM+QJaQAp1q=*A;Zg)Sa`oQb4T+SyAeUC z%4mKX02Je1v>xjG?TDCYfITY&sD~vJmD2yDFhAvcLVc_(#Nfe$rL-YGz&I$09b$WM zU-+yrl+_4((=ge%AS}Epb1GvqRVgG8@o>3iNSAHiE?}Aqx~x;Td=sL&65W&HGLW}Z z2Z&7SZ9bob^ST4*!N5p^BP4u9dPlZ_SNOgtbv2itr9Rs07M1@*r2Bv~V*O#U1Qm#v zzAI-xOdk$?Fg?{sq?fTT2EXMnxMkw7CvJuM-lci_=-`U7jSctJpHr0`{OgmlAAgXuCj^VxKB#nZiJmq2+OwJZ5y~W}g_?iV?^CXM2F3{Xj z_K!|15B=$z_z;4_UMx3X*NXg&hwUeb`f>!TyQErgc+zHsSD_x>$%J3^uZuv`$Bf(%QLN*B@^$@n_xSoV?wgCXYuraXx-~9jm^`36EUA{H6Lq+M;k=6yo8f zoTuuK_^a-PhkhwW<8Wmqab{gT}IXkR-i)L?k_Q^8DL#8JT)9#qaX30BUM7zg3Ju z+@m1~!_xI(qzOMFDduH)V(vqcadx28qoi8FJ8Fx$$X41GX9?!1VwAjoN2CF9HyZACP+q;A*Rx*97IJogSb)dA+9{>bNTP1GA!bL~f zB&tJj#DE7Ai6qJq2j@SCd{uFrWL7aQZ^G}G8{TO3L$C8e8SHzL%KmoykGHv~^Dh*) zv`QEm&V2*XmoTW3?_7NP0PB9kkWA{IPeIG8E>WNp;XZ8XKGA@yg@MjRNf#C$P8KP+ zGkte+Ar(y&jF32S3A8(<&z%7$H#NoVfXmmp9%aDHL%3DtP`w({CSB~)R3n!C5O4`^ z`q(TM(61CFsgD({KbWoS@xo~c{80s!N2oPzn-EBp%8Dj` zsZJF--))4YFNPGz_K!cR3_o(bZ~1zc2>)>11*ZcF<4mc*ueB5tBUeYj;pJrZG;m+v zdZL&gx2fM-`Js@_;V1HXZ8^ONJ$5GD*o5*puY2Q(fTZ9aYn@&8k>nq2H(y}AO=E0u z->qWYapx3FS3+CQsmFZ64O`kcFY(QkqvfPt;}ixtHbBe_08u8nQs0!_L>D+zK?K-0 zyD4Ear6kkrCom8jLkH_J)TULg;HIm8c91i+EdIDu=t-IyW&Lqas!O&Cg+*iCIQwaY zNWrB4Ic13Nl6p*{RPf3#74|Cz-uH !cQ_M?K18la(+*6wh?&YVlu2i)UN!Xx{I_ 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 literal 0 HcmV?d00001 From bae2eddd73998a767cfa4858310a2c47bc5ec62a Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Tue, 17 Mar 2026 17:50:58 -0700 Subject: [PATCH 57/60] docs fix sidebar --- docs/my-website/sidebars.js | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js index 1362745a91f..79a0279bad5 100644 --- a/docs/my-website/sidebars.js +++ b/docs/my-website/sidebars.js @@ -631,6 +631,7 @@ const sidebars = { "mcp_openapi", "mcp_oauth", "mcp_aws_sigv4", + "mcp_zero_trust", "mcp_public_internet", "mcp_semantic_filter", "mcp_control", From 88f59e1465ae55097abc3520e1f6d271b5f39bbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?d=20=F0=9F=94=B9?= <258577966+voidborne-d@users.noreply.github.com> Date: Wed, 18 Mar 2026 00:54:23 +0000 Subject: [PATCH 58/60] fix: use AsyncMock for concurrent test consistency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review feedback from greptile — use new_callable=AsyncMock on the concurrent test's patch.object to ensure the mock is properly typed as async, even though side_effect already handles the coroutine. --- tests/test_litellm/proxy/test_aiohttp_session_recovery.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_litellm/proxy/test_aiohttp_session_recovery.py b/tests/test_litellm/proxy/test_aiohttp_session_recovery.py index f089d10425c..29bd9a491b7 100644 --- a/tests/test_litellm/proxy/test_aiohttp_session_recovery.py +++ b/tests/test_litellm/proxy/test_aiohttp_session_recovery.py @@ -167,6 +167,7 @@ async def test_add_shared_session_concurrent_recreation_uses_lock(): with patch.object( proxy_server_module, "_initialize_shared_aiohttp_session", + new_callable=AsyncMock, side_effect=mock_init, ): # Launch 5 concurrent calls From 19f82c229be550901a2941f4f48ed75a5f246d6e Mon Sep 17 00:00:00 2001 From: joereyna Date: Tue, 17 Mar 2026 22:11:43 -0700 Subject: [PATCH 59/60] fix: update full changelog base from v1.82.0-stable to v1.82.0 --- docs/my-website/release_notes/v1.82.3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/my-website/release_notes/v1.82.3.md b/docs/my-website/release_notes/v1.82.3.md index 15df33fdb80..7552ad6bc95 100644 --- a/docs/my-website/release_notes/v1.82.3.md +++ b/docs/my-website/release_notes/v1.82.3.md @@ -371,4 +371,4 @@ No major secret manager changes in this release. --- ## Full Changelog -[v1.82.0-stable...v1.82.3-stable](https://github.com/BerriAI/litellm/compare/v1.82.0-stable...v1.82.3-stable) +[v1.82.0...v1.82.3-stable](https://github.com/BerriAI/litellm/compare/v1.82.0...v1.82.3-stable) From 8a4ef0bd050e9c688fddb0dfc25c5307c051ed44 Mon Sep 17 00:00:00 2001 From: joereyna Date: Tue, 17 Mar 2026 22:17:12 -0700 Subject: [PATCH 60/60] revert: restore full changelog base to v1.82.0-stable --- docs/my-website/release_notes/v1.82.3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/my-website/release_notes/v1.82.3.md b/docs/my-website/release_notes/v1.82.3.md index 7552ad6bc95..15df33fdb80 100644 --- a/docs/my-website/release_notes/v1.82.3.md +++ b/docs/my-website/release_notes/v1.82.3.md @@ -371,4 +371,4 @@ No major secret manager changes in this release. --- ## Full Changelog -[v1.82.0...v1.82.3-stable](https://github.com/BerriAI/litellm/compare/v1.82.0...v1.82.3-stable) +[v1.82.0-stable...v1.82.3-stable](https://github.com/BerriAI/litellm/compare/v1.82.0-stable...v1.82.3-stable)