From 75a7b397002aea3b9cd6e05915b8392d52f91ca3 Mon Sep 17 00:00:00 2001 From: frankzye1 Date: Sat, 30 Aug 2025 09:27:39 +0800 Subject: [PATCH 01/73] pass function tool description for databricks provider Signed-off-by: frankzye1 --- .../llms/databricks/chat/transformation.py | 13 ++++++--- litellm/types/llms/databricks.py | 2 +- .../test_databricks_chat_transformation.py | 28 +++++++++++++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/litellm/llms/databricks/chat/transformation.py b/litellm/llms/databricks/chat/transformation.py index 908419f7193..3963dd4505c 100644 --- a/litellm/llms/databricks/chat/transformation.py +++ b/litellm/llms/databricks/chat/transformation.py @@ -170,12 +170,17 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): if tool is None: return None + kwags = { + "name":tool["name"], + "parameters":cast(dict, tool.get("input_schema") or {}) + } + + if tool.get("description"): + kwags["description"] = tool.get("description") + return DatabricksTool( type="function", - function=DatabricksFunction( - name=tool["name"], - parameters=cast(dict, tool.get("input_schema") or {}), - ), + function=DatabricksFunction(**kwags), ) def _map_openai_to_dbrx_tool(self, model: str, tools: List) -> List[DatabricksTool]: diff --git a/litellm/types/llms/databricks.py b/litellm/types/llms/databricks.py index bb59b692ef7..e820cede83d 100644 --- a/litellm/types/llms/databricks.py +++ b/litellm/types/llms/databricks.py @@ -49,7 +49,7 @@ AllDatabricksContentValues = Union[str, List[AllDatabricksContentListValues]] class DatabricksFunction(TypedDict, total=False): name: Required[str] - description: dict + description: dict | str parameters: dict strict: bool diff --git a/tests/test_litellm/llms/databricks/chat/test_databricks_chat_transformation.py b/tests/test_litellm/llms/databricks/chat/test_databricks_chat_transformation.py index fc44d44aba9..55ab4428617 100644 --- a/tests/test_litellm/llms/databricks/chat/test_databricks_chat_transformation.py +++ b/tests/test_litellm/llms/databricks/chat/test_databricks_chat_transformation.py @@ -90,3 +90,31 @@ def test_transform_choices_without_signature(): thinking_block = choices[0].message.thinking_blocks[0] assert thinking_block["type"] == "thinking" assert thinking_block["thinking"] == "i'm thinking without signature." + +def test_convert_anthropic_tool_to_databricks_tool_with_description(): + config = DatabricksConfig() + anthropic_tool = { + "name": "test_tool", + "description": "test description", + "input_schema": {"type": "object", "properties": {"test": {"type": "string"}}} + } + + databricks_tool = config.convert_anthropic_tool_to_databricks_tool(anthropic_tool) + + assert databricks_tool is not None + assert databricks_tool["type"] == "function" + assert databricks_tool["function"]["description"] == "test description" + + +def test_convert_anthropic_tool_to_databricks_tool_without_description(): + config = DatabricksConfig() + anthropic_tool = { + "name": "test_tool", + "input_schema": {"type": "object", "properties": {"test": {"type": "string"}}} + } + + databricks_tool = config.convert_anthropic_tool_to_databricks_tool(anthropic_tool) + + assert databricks_tool is not None + assert databricks_tool["type"] == "function" + assert databricks_tool["function"].get("description") is None \ No newline at end of file From a27604b67a98576d105facc21553a0f2e69b729f Mon Sep 17 00:00:00 2001 From: gotsysdba Date: Sat, 30 Aug 2025 20:57:57 +0100 Subject: [PATCH 02/73] Fixes #14090 --- litellm/types/llms/oci.py | 6 +- .../oci/chat/test_oci_chat_transformation.py | 76 +++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/litellm/types/llms/oci.py b/litellm/types/llms/oci.py index 52c1b2943f7..e3ea07463e6 100644 --- a/litellm/types/llms/oci.py +++ b/litellm/types/llms/oci.py @@ -121,7 +121,7 @@ class OCICompletionTokenDetails(BaseModel): reasoningTokens: int -class OCIPropmtTokensDetails(BaseModel): +class OCIPromptTokensDetails(BaseModel): """Prompt token details in the OCI response.""" cachedTokens: int @@ -133,8 +133,8 @@ class OCIResponseUsage(BaseModel): promptTokens: int completionTokens: int totalTokens: int - completionTokensDetails: OCICompletionTokenDetails - promptTokensDetails: OCIPropmtTokensDetails + completionTokensDetails: Optional[OCICompletionTokenDetails] = None + promptTokensDetails: Optional[OCIPromptTokensDetails] = None class OCIResponseChoice(BaseModel): diff --git a/tests/test_litellm/llms/oci/chat/test_oci_chat_transformation.py b/tests/test_litellm/llms/oci/chat/test_oci_chat_transformation.py index 547d4bf807e..cbf2ca356e4 100644 --- a/tests/test_litellm/llms/oci/chat/test_oci_chat_transformation.py +++ b/tests/test_litellm/llms/oci/chat/test_oci_chat_transformation.py @@ -305,3 +305,79 @@ class TestOCIChatConfig: assert usage.prompt_tokens == 10 # type: ignore assert usage.completion_tokens == 20 # type: ignore assert usage.total_tokens == 30 # type: ignore + +def test_transform_response_with_missing_usage_details(self): + """ + Tests that usage details default to None if they are missing or null in the API response. + """ + config = OCIChatConfig() + created_time = datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z") + + # Case 1: usage fields completely missing + mock_oci_response_missing = { + "modelId": TEST_MODEL_NAME, + "modelVersion": "1.0", + "chatResponse": { + "apiFormat": "GENERIC", + "choices": [], + "timeCreated": created_time, + "usage": { + "promptTokens": 5, + "completionTokens": 8, + "totalTokens": 13, + # missing completionTokensDetails and promptTokensDetails + }, + }, + } + + response_missing = httpx.Response(status_code=200, json=mock_oci_response_missing) + result_missing = config.transform_response( + model=TEST_MODEL_NAME, + raw_response=response_missing, + model_response=ModelResponse(), + logging_obj={}, + request_data={}, + messages=[], + optional_params={}, + litellm_params={}, + encoding={}, + ) + + usage_missing = result_missing.usage # type: ignore + assert usage_missing.completionTokensDetails is None + assert usage_missing.promptTokensDetails is None + + # Case 2: usage fields explicitly null + mock_oci_response_null = { + "modelId": TEST_MODEL_NAME, + "modelVersion": "1.0", + "chatResponse": { + "apiFormat": "GENERIC", + "choices": [], + "timeCreated": created_time, + "usage": { + "promptTokens": 5, + "completionTokens": 8, + "totalTokens": 13, + "completionTokensDetails": None, + "promptTokensDetails": None, + }, + }, + } + + response_null = httpx.Response(status_code=200, json=mock_oci_response_null) + result_null = config.transform_response( + model=TEST_MODEL_NAME, + raw_response=response_null, + model_response=ModelResponse(), + logging_obj={}, + request_data={}, + messages=[], + optional_params={}, + litellm_params={}, + encoding={}, + ) + + usage_null = result_null.usage # type: ignore + assert usage_null.completionTokensDetails is None + assert usage_null.promptTokensDetails is None \ No newline at end of file From 1ae5466daf021883c8e6cd8ed17820363b7fac51 Mon Sep 17 00:00:00 2001 From: gotsysdba Date: Sat, 30 Aug 2025 21:15:54 +0100 Subject: [PATCH 03/73] Test Case --- .../oci/chat/test_oci_chat_transformation.py | 108 +++++++----------- 1 file changed, 42 insertions(+), 66 deletions(-) diff --git a/tests/test_litellm/llms/oci/chat/test_oci_chat_transformation.py b/tests/test_litellm/llms/oci/chat/test_oci_chat_transformation.py index cbf2ca356e4..3151bed1090 100644 --- a/tests/test_litellm/llms/oci/chat/test_oci_chat_transformation.py +++ b/tests/test_litellm/llms/oci/chat/test_oci_chat_transformation.py @@ -306,78 +306,54 @@ class TestOCIChatConfig: assert usage.completion_tokens == 20 # type: ignore assert usage.total_tokens == 30 # type: ignore -def test_transform_response_with_missing_usage_details(self): +def test_oci_response_usage_handles_missing_and_null_fields(self): """ - Tests that usage details default to None if they are missing or null in the API response. + Test that OCIResponseUsage parses correctly when + completionTokensDetails and promptTokensDetails are missing or null. """ - config = OCIChatConfig() - created_time = datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z") + from litellm.llms.oci.chat.transformation import OCIResponseUsage - # Case 1: usage fields completely missing - mock_oci_response_missing = { - "modelId": TEST_MODEL_NAME, - "modelVersion": "1.0", - "chatResponse": { - "apiFormat": "GENERIC", - "choices": [], - "timeCreated": created_time, - "usage": { - "promptTokens": 5, - "completionTokens": 8, - "totalTokens": 13, - # missing completionTokensDetails and promptTokensDetails - }, - }, + # Case 1: fields completely missing + data_missing = { + "promptTokens": 10, + "completionTokens": 5, + "totalTokens": 15, + # no completionTokensDetails or promptTokensDetails } - - response_missing = httpx.Response(status_code=200, json=mock_oci_response_missing) - result_missing = config.transform_response( - model=TEST_MODEL_NAME, - raw_response=response_missing, - model_response=ModelResponse(), - logging_obj={}, - request_data={}, - messages=[], - optional_params={}, - litellm_params={}, - encoding={}, - ) - - usage_missing = result_missing.usage # type: ignore + usage_missing = OCIResponseUsage(**data_missing) assert usage_missing.completionTokensDetails is None assert usage_missing.promptTokensDetails is None + assert usage_missing.promptTokens == 10 + assert usage_missing.completionTokens == 5 + assert usage_missing.totalTokens == 15 - # Case 2: usage fields explicitly null - mock_oci_response_null = { - "modelId": TEST_MODEL_NAME, - "modelVersion": "1.0", - "chatResponse": { - "apiFormat": "GENERIC", - "choices": [], - "timeCreated": created_time, - "usage": { - "promptTokens": 5, - "completionTokens": 8, - "totalTokens": 13, - "completionTokensDetails": None, - "promptTokensDetails": None, - }, - }, + # Case 2: fields explicitly null + data_null = { + "promptTokens": 12, + "completionTokens": 8, + "totalTokens": 20, + "completionTokensDetails": None, + "promptTokensDetails": None, } - - response_null = httpx.Response(status_code=200, json=mock_oci_response_null) - result_null = config.transform_response( - model=TEST_MODEL_NAME, - raw_response=response_null, - model_response=ModelResponse(), - logging_obj={}, - request_data={}, - messages=[], - optional_params={}, - litellm_params={}, - encoding={}, - ) - - usage_null = result_null.usage # type: ignore + usage_null = OCIResponseUsage(**data_null) assert usage_null.completionTokensDetails is None - assert usage_null.promptTokensDetails is None \ No newline at end of file + assert usage_null.promptTokensDetails is None + assert usage_null.promptTokens == 12 + assert usage_null.completionTokens == 8 + assert usage_null.totalTokens == 20 + + # Case 3: fields present with values + data_present = { + "promptTokens": 7, + "completionTokens": 3, + "totalTokens": 10, + "completionTokensDetails": {"acceptedPredictionTokens": 2, "reasoningTokens": 1}, + "promptTokensDetails": {"cachedTokens": 4}, + } + usage_present = OCIResponseUsage(**data_present) + assert usage_present.completionTokensDetails.acceptedPredictionTokens == 2 + assert usage_present.completionTokensDetails.reasoningTokens == 1 + assert usage_present.promptTokensDetails.cachedTokens == 4 + assert usage_present.promptTokens == 7 + assert usage_present.completionTokens == 3 + assert usage_present.totalTokens == 10 From 07d9d14b49183c334e3d6ba720a92a904d72a701 Mon Sep 17 00:00:00 2001 From: gotsysdba Date: Sat, 30 Aug 2025 22:02:00 +0100 Subject: [PATCH 04/73] Update OCIResposeClass --- litellm/types/llms/oci.py | 2 +- .../oci/chat/test_oci_chat_transformation.py | 56 ++----------------- 2 files changed, 5 insertions(+), 53 deletions(-) diff --git a/litellm/types/llms/oci.py b/litellm/types/llms/oci.py index e3ea07463e6..75d13192c50 100644 --- a/litellm/types/llms/oci.py +++ b/litellm/types/llms/oci.py @@ -129,7 +129,7 @@ class OCIPromptTokensDetails(BaseModel): class OCIResponseUsage(BaseModel): """Token usage in the OCI response.""" - + promptTokens: int completionTokens: int totalTokens: int diff --git a/tests/test_litellm/llms/oci/chat/test_oci_chat_transformation.py b/tests/test_litellm/llms/oci/chat/test_oci_chat_transformation.py index 3151bed1090..9ff93dfc1a2 100644 --- a/tests/test_litellm/llms/oci/chat/test_oci_chat_transformation.py +++ b/tests/test_litellm/llms/oci/chat/test_oci_chat_transformation.py @@ -213,6 +213,10 @@ class TestOCIChatConfig: assert result.usage.prompt_tokens == 10 # type: ignore assert result.usage.completion_tokens == 20 # type: ignore assert result.usage.total_tokens == 30 # type: ignore + # These are not handled in the transformer, TBH no idea why they are here + # but, for now, they seem to be always None + assert result.usage.completion_tokens_details is None + assert result.usage.prompt_tokens_details is None def test_transform_response_with_tool_calls(self): """ @@ -305,55 +309,3 @@ class TestOCIChatConfig: assert usage.prompt_tokens == 10 # type: ignore assert usage.completion_tokens == 20 # type: ignore assert usage.total_tokens == 30 # type: ignore - -def test_oci_response_usage_handles_missing_and_null_fields(self): - """ - Test that OCIResponseUsage parses correctly when - completionTokensDetails and promptTokensDetails are missing or null. - """ - from litellm.llms.oci.chat.transformation import OCIResponseUsage - - # Case 1: fields completely missing - data_missing = { - "promptTokens": 10, - "completionTokens": 5, - "totalTokens": 15, - # no completionTokensDetails or promptTokensDetails - } - usage_missing = OCIResponseUsage(**data_missing) - assert usage_missing.completionTokensDetails is None - assert usage_missing.promptTokensDetails is None - assert usage_missing.promptTokens == 10 - assert usage_missing.completionTokens == 5 - assert usage_missing.totalTokens == 15 - - # Case 2: fields explicitly null - data_null = { - "promptTokens": 12, - "completionTokens": 8, - "totalTokens": 20, - "completionTokensDetails": None, - "promptTokensDetails": None, - } - usage_null = OCIResponseUsage(**data_null) - assert usage_null.completionTokensDetails is None - assert usage_null.promptTokensDetails is None - assert usage_null.promptTokens == 12 - assert usage_null.completionTokens == 8 - assert usage_null.totalTokens == 20 - - # Case 3: fields present with values - data_present = { - "promptTokens": 7, - "completionTokens": 3, - "totalTokens": 10, - "completionTokensDetails": {"acceptedPredictionTokens": 2, "reasoningTokens": 1}, - "promptTokensDetails": {"cachedTokens": 4}, - } - usage_present = OCIResponseUsage(**data_present) - assert usage_present.completionTokensDetails.acceptedPredictionTokens == 2 - assert usage_present.completionTokensDetails.reasoningTokens == 1 - assert usage_present.promptTokensDetails.cachedTokens == 4 - assert usage_present.promptTokens == 7 - assert usage_present.completionTokens == 3 - assert usage_present.totalTokens == 10 From 4278c3b596496e055ec0d89197d8930e242d9825 Mon Sep 17 00:00:00 2001 From: Dharamendra Kumar Date: Sun, 31 Aug 2025 11:14:19 -0700 Subject: [PATCH 05/73] feat: Add dependency injection support to BaseLLMAIOHTTPHandler - Add optional client_session, transport, and connector parameters to constructor - Implement session ownership tracking to prevent closing user-provided sessions - Add comprehensive session resolution hierarchy (dynamic > instance > create new) - Include transport control for advanced HTTP stack management - Add 29 comprehensive tests covering all injection scenarios - Maintain backward compatibility with existing code This enhancement allows users to inject their own configured aiohttp sessions, transports, and connectors for fine-grained control over connection pooling, SSL settings, proxy configurations, and other HTTP stack parameters. --- litellm/llms/custom_httpx/aiohttp_handler.py | 94 +++- .../llms/custom_httpx/test_aiohttp_handler.py | 426 ++++++++++++++++++ 2 files changed, 514 insertions(+), 6 deletions(-) create mode 100644 tests/test_litellm/llms/custom_httpx/test_aiohttp_handler.py diff --git a/litellm/llms/custom_httpx/aiohttp_handler.py b/litellm/llms/custom_httpx/aiohttp_handler.py index d9fc85877c3..c7a04a49fc2 100644 --- a/litellm/llms/custom_httpx/aiohttp_handler.py +++ b/litellm/llms/custom_httpx/aiohttp_handler.py @@ -17,6 +17,7 @@ from litellm.llms.custom_httpx.http_handler import ( HTTPHandler, _get_httpx_client, ) +from litellm.llms.custom_httpx.aiohttp_transport import LiteLLMAiohttpTransport from litellm.types.llms.openai import FileTypes from litellm.types.utils import HttpHandlerRequestFields, ImageResponse, LlmProviders from litellm.utils import CustomStreamWrapper, ModelResponse, ProviderConfigManager @@ -32,8 +33,71 @@ DEFAULT_TIMEOUT = 600 class BaseLLMAIOHTTPHandler: - def __init__(self): - self.client_session: Optional[aiohttp.ClientSession] = None + def __init__( + self, + client_session: Optional[aiohttp.ClientSession] = None, + transport: Optional[LiteLLMAiohttpTransport] = None, + connector: Optional[aiohttp.BaseConnector] = None, + ): + self.client_session = client_session + self._owns_session = ( + client_session is None + ) # Track if we own the session for cleanup + + self.transport = transport + self._owns_transport = ( + transport is None + ) # Track if we own the transport for cleanup + + self.connector = connector + self._owns_connector = ( + connector is None + ) # Track if we own the connector for cleanup + + def _get_or_create_transport(self) -> Optional[LiteLLMAiohttpTransport]: + """Get existing transport or create a new one if needed.""" + if self.transport: + return self.transport + + # Create a transport using AsyncHTTPHandler's logic + try: + self.transport = AsyncHTTPHandler._create_aiohttp_transport() + self._owns_transport = True + return self.transport + except Exception: + # If transport creation fails, return None (will use direct session) + return None + + def _get_connector(self) -> Optional[aiohttp.BaseConnector]: + """Get or create a connector for the client session.""" + if self.connector: + return self.connector + elif self.transport and hasattr(self.transport, "client"): + # Extract connector from transport if available + client = self.transport.client + if callable(client): + # If client is a factory, we can't extract connector directly + return None + elif hasattr(client, "connector"): + return client.connector + return None + + def _create_client_session_with_transport(self) -> ClientSession: + """Create a new client session using transport or connector configuration.""" + connector = self._get_connector() + + if self.transport and hasattr(self.transport, "_get_valid_client_session"): + # Use transport's session creation if available + session = self.transport._get_valid_client_session() + return session + elif connector: + # Use provided connector + session = aiohttp.ClientSession(connector=connector) + return session + else: + # Default session creation + session = aiohttp.ClientSession() + return session def _get_async_client_session( self, dynamic_client_session: Optional[ClientSession] = None @@ -43,15 +107,33 @@ class BaseLLMAIOHTTPHandler: elif self.client_session: return self.client_session else: - # init client session, and then return new session - self.client_session = aiohttp.ClientSession() + # Create client session using transport/connector if available + self.client_session = self._create_client_session_with_transport() + self._owns_session = True # We created this session, so we own it return self.client_session async def close(self): - """Close the aiohttp client session if it exists.""" - if self.client_session and not self.client_session.closed: + """Close the aiohttp client session and transport if we own them.""" + # Close client session if we own it + if ( + self.client_session + and not self.client_session.closed + and self._owns_session + ): await self.client_session.close() + # Close transport if we own it + if ( + self.transport + and self._owns_transport + and hasattr(self.transport, "aclose") + ): + try: + await self.transport.aclose() + except Exception: + # Ignore errors during transport cleanup + pass + async def _make_common_async_call( self, async_client_session: Optional[ClientSession], diff --git a/tests/test_litellm/llms/custom_httpx/test_aiohttp_handler.py b/tests/test_litellm/llms/custom_httpx/test_aiohttp_handler.py new file mode 100644 index 00000000000..21df2aecad3 --- /dev/null +++ b/tests/test_litellm/llms/custom_httpx/test_aiohttp_handler.py @@ -0,0 +1,426 @@ +import asyncio +import os +import sys +from unittest.mock import AsyncMock, MagicMock, Mock, patch + +import aiohttp +import pytest + +sys.path.insert( + 0, os.path.abspath("../../../..") +) # Adds the parent directory to the system path + +from litellm.llms.custom_httpx.aiohttp_handler import BaseLLMAIOHTTPHandler +from litellm.llms.custom_httpx.aiohttp_transport import LiteLLMAiohttpTransport + + +class TestBaseLLMAIOHTTPHandler: + """Test cases for BaseLLMAIOHTTPHandler dependency injection functionality""" + + def test_init_with_no_client_session(self): + """Test handler initialization with no client session""" + handler = BaseLLMAIOHTTPHandler() + + assert handler.client_session is None + assert handler._owns_session is True + + def test_init_with_provided_client_session(self): + """Test handler initialization with provided client session""" + # Create a mock client session + mock_session = Mock() + + handler = BaseLLMAIOHTTPHandler(client_session=mock_session) + + assert handler.client_session is mock_session + assert handler._owns_session is False + + def test_get_async_client_session_with_dynamic_session(self): + """Test _get_async_client_session with dynamic session parameter""" + handler = BaseLLMAIOHTTPHandler() + + dynamic_session = Mock() + + result = handler._get_async_client_session( + dynamic_client_session=dynamic_session + ) + + assert result is dynamic_session + + def test_get_async_client_session_with_instance_session(self): + """Test _get_async_client_session with instance session""" + instance_session = Mock() + handler = BaseLLMAIOHTTPHandler(client_session=instance_session) + + result = handler._get_async_client_session() + + assert result is instance_session + + @patch("aiohttp.ClientSession") + def test_get_async_client_session_create_new(self, mock_client_session): + """Test _get_async_client_session creates new session when none provided""" + handler = BaseLLMAIOHTTPHandler() + mock_session_instance = Mock() + mock_client_session.return_value = mock_session_instance + + result = handler._get_async_client_session() + + # Verify new session was created + mock_client_session.assert_called_once() + assert handler.client_session is mock_session_instance + assert handler._owns_session is True + assert result is mock_session_instance + + @pytest.mark.asyncio + async def test_close_with_owned_session(self): + """Test close() method with owned session""" + # Create a mock session that we own + mock_session = Mock() + mock_session.closed = False + mock_session.close = AsyncMock() + + # Create handler that owns the session + handler = BaseLLMAIOHTTPHandler() + handler.client_session = mock_session + handler._owns_session = True + + await handler.close() + + # Verify close was called + mock_session.close.assert_called_once() + + @pytest.mark.asyncio + async def test_close_with_non_owned_session(self): + """Test close() method with non-owned session (should not close)""" + # Create a mock session that we don't own + mock_session = Mock() + mock_session.closed = False + mock_session.close = AsyncMock() + + handler = BaseLLMAIOHTTPHandler(client_session=mock_session) + + await handler.close() + + # Verify close was NOT called since we don't own this session + mock_session.close.assert_not_called() + + @pytest.mark.asyncio + async def test_close_with_already_closed_session(self): + """Test close() method with already closed session""" + mock_session = Mock() + mock_session.closed = True + mock_session.close = AsyncMock() + + handler = BaseLLMAIOHTTPHandler() + handler.client_session = mock_session + handler._owns_session = True + + await handler.close() + + # Verify close was NOT called since session is already closed + mock_session.close.assert_not_called() + + @pytest.mark.asyncio + async def test_close_with_no_session(self): + """Test close() method with no session""" + handler = BaseLLMAIOHTTPHandler() + + # Should not raise any exceptions + await handler.close() + + def test_session_priority_dynamic_over_instance(self): + """Test that dynamic session takes priority over instance session""" + instance_session = Mock() + dynamic_session = Mock() + + handler = BaseLLMAIOHTTPHandler(client_session=instance_session) + + result = handler._get_async_client_session( + dynamic_client_session=dynamic_session + ) + + assert result is dynamic_session + assert result is not instance_session + + def test_session_ownership_tracking(self): + """Test proper session ownership tracking in various scenarios""" + # Scenario 1: Provided session - not owned + provided_session = Mock() + handler1 = BaseLLMAIOHTTPHandler(client_session=provided_session) + assert not handler1._owns_session + + # Scenario 2: No session initially - becomes owned when created + handler2 = BaseLLMAIOHTTPHandler() + assert handler2._owns_session + + with patch("aiohttp.ClientSession") as mock_client_session: + mock_session_instance = Mock() + mock_client_session.return_value = mock_session_instance + + handler2._get_async_client_session() + assert handler2._owns_session + + @pytest.mark.asyncio + async def test_context_manager_pattern_compatibility(self): + """Test that the handler works well with context manager pattern""" + mock_session = Mock() + mock_session.closed = False + mock_session.close = AsyncMock() + + # Test as context manager style usage + handler = BaseLLMAIOHTTPHandler() + handler.client_session = mock_session + handler._owns_session = True + + try: + # Simulate some work + session = handler._get_async_client_session() + assert session is mock_session + finally: + await handler.close() + + # Verify cleanup happened + mock_session.close.assert_called_once() + + @patch("litellm.llms.custom_httpx.aiohttp_handler.aiohttp.ClientSession") + def test_lazy_session_creation(self, mock_client_session): + """Test that session is created lazily only when needed""" + handler = BaseLLMAIOHTTPHandler() + + # Session should not be created on init + mock_client_session.assert_not_called() + assert handler.client_session is None + + # Session should be created when requested + mock_session_instance = Mock() + mock_client_session.return_value = mock_session_instance + + session = handler._get_async_client_session() + + mock_client_session.assert_called_once() + assert session is mock_session_instance + assert handler.client_session is mock_session_instance + + def test_session_reuse(self): + """Test that the same session is reused across multiple calls""" + instance_session = Mock() + handler = BaseLLMAIOHTTPHandler(client_session=instance_session) + + # Multiple calls should return the same session + session1 = handler._get_async_client_session() + session2 = handler._get_async_client_session() + session3 = handler._get_async_client_session() + + assert session1 is session2 is session3 is instance_session + + # =============================== + # TRANSPORT INJECTION TESTS + # =============================== + + def test_init_with_transport(self): + """Test handler initialization with provided transport""" + mock_transport = Mock(spec=LiteLLMAiohttpTransport) + + handler = BaseLLMAIOHTTPHandler(transport=mock_transport) + + assert handler.transport is mock_transport + assert handler._owns_transport is False + + def test_init_with_connector(self): + """Test handler initialization with provided connector""" + mock_connector = Mock(spec=aiohttp.BaseConnector) + + handler = BaseLLMAIOHTTPHandler(connector=mock_connector) + + assert handler.connector is mock_connector + assert handler._owns_connector is False + + def test_init_with_transport_and_session(self): + """Test handler initialization with both transport and session""" + mock_transport = Mock(spec=LiteLLMAiohttpTransport) + mock_session = Mock() + + handler = BaseLLMAIOHTTPHandler( + client_session=mock_session, transport=mock_transport + ) + + assert handler.transport is mock_transport + assert handler._owns_transport is False + assert handler.client_session is mock_session + assert handler._owns_session is False + + def test_get_connector_from_provided_connector(self): + """Test _get_connector returns provided connector""" + mock_connector = Mock(spec=aiohttp.BaseConnector) + handler = BaseLLMAIOHTTPHandler(connector=mock_connector) + + result = handler._get_connector() + + assert result is mock_connector + + def test_get_connector_from_transport(self): + """Test _get_connector extracts connector from transport""" + mock_connector = Mock(spec=aiohttp.BaseConnector) + + # Use a simple object instead of Mock to avoid callable issues + class MockSession: + def __init__(self): + self.connector = mock_connector + + mock_session = MockSession() + + mock_transport = Mock(spec=LiteLLMAiohttpTransport) + mock_transport.client = mock_session + + handler = BaseLLMAIOHTTPHandler(transport=mock_transport) + + result = handler._get_connector() + + assert result is mock_connector + + def test_get_connector_from_transport_with_callable_client(self): + """Test _get_connector with transport that has callable client""" + mock_transport = Mock(spec=LiteLLMAiohttpTransport) + mock_transport.client = lambda: Mock() # Callable client + + handler = BaseLLMAIOHTTPHandler(transport=mock_transport) + + result = handler._get_connector() + + assert result is None + + @patch("aiohttp.ClientSession") + def test_create_client_session_with_transport(self, mock_client_session): + """Test session creation using transport""" + mock_session_from_transport = Mock() + + mock_transport = Mock(spec=LiteLLMAiohttpTransport) + mock_transport._get_valid_client_session = Mock( + return_value=mock_session_from_transport + ) + + handler = BaseLLMAIOHTTPHandler(transport=mock_transport) + + result = handler._create_client_session_with_transport() + + # Should use transport's session creation method + mock_transport._get_valid_client_session.assert_called_once() + assert result is mock_session_from_transport + + # Should not call aiohttp.ClientSession directly + mock_client_session.assert_not_called() + + @patch("aiohttp.ClientSession") + def test_create_client_session_with_connector(self, mock_client_session): + """Test session creation using connector""" + mock_connector = Mock(spec=aiohttp.BaseConnector) + mock_session_instance = Mock() + mock_client_session.return_value = mock_session_instance + + handler = BaseLLMAIOHTTPHandler(connector=mock_connector) + + result = handler._create_client_session_with_transport() + + # Should create session with connector + mock_client_session.assert_called_once_with(connector=mock_connector) + assert result is mock_session_instance + + @patch("aiohttp.ClientSession") + def test_create_client_session_default(self, mock_client_session): + """Test default session creation when no transport/connector provided""" + mock_session_instance = Mock() + mock_client_session.return_value = mock_session_instance + + handler = BaseLLMAIOHTTPHandler() + + result = handler._create_client_session_with_transport() + + # Should create default session + mock_client_session.assert_called_once_with() + assert result is mock_session_instance + + @patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler._create_aiohttp_transport" + ) + def test_get_or_create_transport(self, mock_create_transport): + """Test transport creation when none provided""" + mock_transport_instance = Mock(spec=LiteLLMAiohttpTransport) + mock_create_transport.return_value = mock_transport_instance + + handler = BaseLLMAIOHTTPHandler() + + result = handler._get_or_create_transport() + + mock_create_transport.assert_called_once() + assert result is mock_transport_instance + assert handler.transport is mock_transport_instance + assert handler._owns_transport is True + + def test_get_or_create_transport_with_existing(self): + """Test _get_or_create_transport returns existing transport""" + mock_transport = Mock(spec=LiteLLMAiohttpTransport) + handler = BaseLLMAIOHTTPHandler(transport=mock_transport) + + result = handler._get_or_create_transport() + + assert result is mock_transport + + @pytest.mark.asyncio + async def test_close_with_owned_transport(self): + """Test close() method with owned transport""" + mock_transport = Mock(spec=LiteLLMAiohttpTransport) + mock_transport.aclose = AsyncMock() + + handler = BaseLLMAIOHTTPHandler() + handler.transport = mock_transport + handler._owns_transport = True + + await handler.close() + + # Verify transport close was called + mock_transport.aclose.assert_called_once() + + @pytest.mark.asyncio + async def test_close_with_non_owned_transport(self): + """Test close() method with non-owned transport (should not close)""" + mock_transport = Mock(spec=LiteLLMAiohttpTransport) + mock_transport.aclose = AsyncMock() + + handler = BaseLLMAIOHTTPHandler(transport=mock_transport) + + await handler.close() + + # Verify transport close was NOT called since we don't own this transport + mock_transport.aclose.assert_not_called() + + @pytest.mark.asyncio + async def test_close_transport_without_aclose_method(self): + """Test close() handles transport without aclose method gracefully""" + mock_transport = Mock() # No aclose method + + handler = BaseLLMAIOHTTPHandler() + handler.transport = mock_transport + handler._owns_transport = True + + # Should not raise any exceptions + await handler.close() + + def test_transport_priority_hierarchy(self): + """Test that session creation follows the right priority: transport > connector > default""" + # Test with transport having _get_valid_client_session + mock_transport = Mock(spec=LiteLLMAiohttpTransport) + mock_session_from_transport = Mock() + mock_transport._get_valid_client_session = Mock( + return_value=mock_session_from_transport + ) + + mock_connector = Mock(spec=aiohttp.BaseConnector) + + handler = BaseLLMAIOHTTPHandler( + transport=mock_transport, connector=mock_connector + ) + + result = handler._create_client_session_with_transport() + + # Should use transport, not connector + mock_transport._get_valid_client_session.assert_called_once() + assert result is mock_session_from_transport From 009106def0ea9abdd069a42eb1add816ad91c1c2 Mon Sep 17 00:00:00 2001 From: Dharamendra Kumar Date: Sun, 7 Sep 2025 17:42:26 -0700 Subject: [PATCH 06/73] docs for custom aiohttp session --- .../docs/completion/http_handler_config.md | 145 ++++++++++++++++++ docs/my-website/sidebars.js | 1 + 2 files changed, 146 insertions(+) create mode 100644 docs/my-website/docs/completion/http_handler_config.md diff --git a/docs/my-website/docs/completion/http_handler_config.md b/docs/my-website/docs/completion/http_handler_config.md new file mode 100644 index 00000000000..d4a25ce2043 --- /dev/null +++ b/docs/my-website/docs/completion/http_handler_config.md @@ -0,0 +1,145 @@ +# Custom HTTP Handler + +Configure custom aiohttp sessions for better performance and control in LiteLLM completions. + +## Overview + +You can now inject custom `aiohttp.ClientSession` instances into LiteLLM for: +- Custom connection pooling and timeouts +- Corporate proxy and SSL configurations +- Performance optimization +- Request monitoring + +## Basic Usage + +### Default (No Changes Required) +```python +import litellm + +# Works exactly as before +response = await litellm.acompletion( + model="gpt-3.5-turbo", + messages=[{"role": "user", "content": "Hello!"}] +) +``` + +### Custom Session +```python +import aiohttp +import litellm +from litellm.llms.custom_httpx.aiohttp_handler import BaseLLMAIOHTTPHandler + +# Create optimized session +session = aiohttp.ClientSession( + timeout=aiohttp.ClientTimeout(total=180), + connector=aiohttp.TCPConnector(limit=300, limit_per_host=75) +) + +# Replace global handler +litellm.base_llm_aiohttp_handler = BaseLLMAIOHTTPHandler(client_session=session) + +# All completions now use your session +response = await litellm.acompletion(model="gpt-3.5-turbo", messages=[...]) +``` + +## Common Patterns + +### FastAPI Integration +```python +from contextlib import asynccontextmanager +from fastapi import FastAPI +import aiohttp +import litellm + +@asynccontextmanager +async def lifespan(app: FastAPI): + # Startup + session = aiohttp.ClientSession( + timeout=aiohttp.ClientTimeout(total=180), + connector=aiohttp.TCPConnector(limit=300) + ) + litellm.base_llm_aiohttp_handler = BaseLLMAIOHTTPHandler( + client_session=session + ) + yield + # Shutdown + await session.close() + +app = FastAPI(lifespan=lifespan) + +@app.post("/chat") +async def chat(messages: list[dict]): + return await litellm.acompletion(model="gpt-3.5-turbo", messages=messages) +``` + +### Corporate Proxy +```python +import ssl + +# Custom SSL context +ssl_context = ssl.create_default_context() +ssl_context.load_cert_chain('cert.pem', 'key.pem') + +# Proxy session +session = aiohttp.ClientSession( + connector=aiohttp.TCPConnector(ssl=ssl_context), + trust_env=True # Use environment proxy settings +) + +litellm.base_llm_aiohttp_handler = BaseLLMAIOHTTPHandler(client_session=session) +``` + +### High Performance +```python +# Optimized for high throughput +session = aiohttp.ClientSession( + timeout=aiohttp.ClientTimeout(total=300), + connector=aiohttp.TCPConnector( + limit=1000, # High connection limit + limit_per_host=200, # Per host limit + ttl_dns_cache=600, # DNS cache + keepalive_timeout=60, # Keep connections alive + enable_cleanup_closed=True + ) +) + +litellm.base_llm_aiohttp_handler = BaseLLMAIOHTTPHandler(client_session=session) +``` + +## Constructor Options + +```python +BaseLLMAIOHTTPHandler( + client_session=None, # Custom aiohttp.ClientSession + transport=None, # Advanced transport control + connector=None, # Custom aiohttp.BaseConnector +) +``` + +## Resource Management + +- **User sessions**: You manage the lifecycle (call `await session.close()`) +- **Auto-created sessions**: Automatically cleaned up by the handler +- **100% backward compatible**: Existing code works unchanged + +## Configuration Tips + +### Development +```python +session = aiohttp.ClientSession( + timeout=aiohttp.ClientTimeout(total=60), + connector=aiohttp.TCPConnector(limit=50) +) +``` + +### Production +```python +session = aiohttp.ClientSession( + timeout=aiohttp.ClientTimeout(total=300), + connector=aiohttp.TCPConnector( + limit=1000, + limit_per_host=200, + keepalive_timeout=60 + ) +) +``` \ No newline at end of file diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js index f81ecda3916..0cf1663a342 100644 --- a/docs/my-website/sidebars.js +++ b/docs/my-website/sidebars.js @@ -259,6 +259,7 @@ const sidebars = { "completion/input", "completion/output", "completion/usage", + "completion/http_handler_config", ], }, "response_api", From fa2d3a89718c1e9b751f953344f45c50753d57b5 Mon Sep 17 00:00:00 2001 From: Dharamendra Kumar Date: Sun, 7 Sep 2025 17:51:46 -0700 Subject: [PATCH 07/73] Update poetry.lock --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index da9e29d8caa..2368e6f064a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -1914,7 +1914,7 @@ description = "WSGI HTTP Server for UNIX" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "(python_version >= \"3.10\" or extra == \"proxy\") and (platform_system != \"Windows\" or extra == \"proxy\") and (python_version < \"3.12\" or extra == \"proxy\" or extra == \"mlflow\") and (extra == \"mlflow\" or extra == \"proxy\")" +markers = "platform_system != \"Windows\" and (extra == \"mlflow\" or extra == \"proxy\") and python_version >= \"3.10\" or extra == \"proxy\"" files = [ {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, @@ -2850,8 +2850,8 @@ files = [ [package.dependencies] numpy = [ {version = ">=1.23.3", markers = "python_version >= \"3.11\""}, + {version = ">1.20"}, {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, - {version = ">1.20", markers = "python_version < \"3.10\""}, {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, ] From 5b26e78ad6c576427b1f5a418bcf3422d849ab62 Mon Sep 17 00:00:00 2001 From: Dharamendra Kumar Date: Sun, 7 Sep 2025 18:04:00 -0700 Subject: [PATCH 08/73] Fix linting --- tests/test_litellm/llms/custom_httpx/test_aiohttp_handler.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_litellm/llms/custom_httpx/test_aiohttp_handler.py b/tests/test_litellm/llms/custom_httpx/test_aiohttp_handler.py index 21df2aecad3..6e19b2341a7 100644 --- a/tests/test_litellm/llms/custom_httpx/test_aiohttp_handler.py +++ b/tests/test_litellm/llms/custom_httpx/test_aiohttp_handler.py @@ -1,7 +1,6 @@ -import asyncio import os import sys -from unittest.mock import AsyncMock, MagicMock, Mock, patch +from unittest.mock import AsyncMock, Mock, patch import aiohttp import pytest From 890ee1abfaf6a127b752deb7053fd22b34561d1e Mon Sep 17 00:00:00 2001 From: Toy-97 Date: Mon, 8 Sep 2025 19:17:58 +0800 Subject: [PATCH 09/73] update: DeepInfra model data refresh [2025-09-08] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed models: deepinfra/Qwen/Qwen2.5-Coder-32B-Instruct deepinfra/deepseek-ai/DeepSeek-V3-0324-Turbo deepinfra/meta-llama/Llama-3.2-90B-Vision-Instruct deepinfra/meta-llama/Llama-4-Maverick-17B-128E-Instruct-Turbo deepinfra/meta-llama/Meta-Llama-3-70B-Instruct deepinfra/mistralai/Devstral-Small-2507 deepinfra/mistralai/Mistral-7B-Instruct-v0.3 deepinfra/mistralai/Mistral-Small-3.1-24B-Instruct-2503 Modified models: deepinfra/deepseek-ai/DeepSeek-R1-0528: - cache_read_input_token_cost: None → 4e-07 deepinfra/google/gemma-3-4b-it: - input_cost_per_token: 2e-08 → 4e-08 - output_cost_per_token: 4e-08 → 8e-08 deepinfra/Qwen/QwQ-32B: - input_cost_per_token: 7.5e-08 → 1.5e-07 - output_cost_per_token: 1.5e-07 → 4e-07 deepinfra/deepseek-ai/DeepSeek-V3-0324: - cache_read_input_token_cost: None → 2.24e-07 deepinfra/deepseek-ai/DeepSeek-V3.1: - input_cost_per_token: 3e-07 → 2.7e-07 - cache_read_input_token_cost: None → 2.16e-07 deepinfra/Qwen/Qwen3-Coder-480B-A35B-Instruct-Turbo: - cache_read_input_token_cost: 1.5e-07 → 2.4e-07 deepinfra/NousResearch/Hermes-3-Llama-3.1-70B: - supports_tool_choice: True → False deepinfra/deepseek-ai/DeepSeek-R1-Turbo: - max_output_tokens: 163840 → 40960 - max_input_tokens: 163840 → 40960 - max_tokens: 163840 → 40960 --- model_prices_and_context_window.json | 542 ++------------------------- 1 file changed, 23 insertions(+), 519 deletions(-) diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index c8b4cc4b791..e9a2b492f93 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -15517,16 +15517,6 @@ "litellm_provider": "ollama", "mode": "completion" }, - "deepinfra/Austism/chronos-hermes-13b-v2": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 1.3e-07, - "output_cost_per_token": 1.3e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, "deepinfra/Gryphe/MythoMax-L2-13b": { "max_tokens": 4096, "max_input_tokens": 4096, @@ -15537,26 +15527,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/Gryphe/MythoMax-L2-13b-turbo": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 1.3e-07, - "output_cost_per_token": 1.3e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/KoboldAI/LLaMA2-13B-Tiefighter": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 1e-07, - "output_cost_per_token": 1e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, "deepinfra/NousResearch/Hermes-3-Llama-3.1-405B": { "max_tokens": 131072, "max_input_tokens": 131072, @@ -15575,78 +15545,18 @@ "output_cost_per_token": 2.8e-07, "litellm_provider": "deepinfra", "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/NovaSky-AI/Sky-T1-32B-Preview": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 1.2e-07, - "output_cost_per_token": 1.8e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/Phind/Phind-CodeLlama-34B-v2": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 6e-07, - "output_cost_per_token": 6e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/Qwen/QVQ-72B-Preview": { - "max_tokens": 32000, - "max_input_tokens": 32000, - "max_output_tokens": 32000, - "input_cost_per_token": 2.5e-07, - "output_cost_per_token": 5e-07, - "litellm_provider": "deepinfra", - "mode": "chat", "supports_tool_choice": false }, "deepinfra/Qwen/QwQ-32B": { "max_tokens": 131072, "max_input_tokens": 131072, "max_output_tokens": 131072, - "input_cost_per_token": 7.5e-08, - "output_cost_per_token": 1.5e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/Qwen/QwQ-32B-Preview": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 1.2e-07, - "output_cost_per_token": 1.8e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/Qwen/Qwen2-72B-Instruct": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 3.5e-07, + "input_cost_per_token": 1.5e-07, "output_cost_per_token": 4e-07, "litellm_provider": "deepinfra", "mode": "chat", "supports_tool_choice": true }, - "deepinfra/Qwen/Qwen2-7B-Instruct": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 5.5e-08, - "output_cost_per_token": 5.5e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, "deepinfra/Qwen/Qwen2.5-72B-Instruct": { "max_tokens": 32768, "max_input_tokens": 32768, @@ -15667,26 +15577,6 @@ "mode": "chat", "supports_tool_choice": false }, - "deepinfra/Qwen/Qwen2.5-Coder-32B-Instruct": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 6e-08, - "output_cost_per_token": 1.5e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/Qwen/Qwen2.5-Coder-7B": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 2.5e-08, - "output_cost_per_token": 5e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, "deepinfra/Qwen/Qwen2.5-VL-32B-Instruct": { "max_tokens": 128000, "max_input_tokens": 128000, @@ -15773,30 +15663,11 @@ "max_output_tokens": 262144, "input_cost_per_token": 3e-07, "output_cost_per_token": 1.2e-06, + "cache_read_input_token_cost": 2.4e-07, "litellm_provider": "deepinfra", "mode": "chat", "supports_tool_choice": true }, - "deepinfra/Sao10K/L3-70B-Euryale-v2.1": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 7e-07, - "output_cost_per_token": 8e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/Sao10K/L3-8B-Lunaris-v1": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 3e-08, - "output_cost_per_token": 6e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, "deepinfra/Sao10K/L3-8B-Lunaris-v1-Turbo": { "max_tokens": 8192, "max_input_tokens": 8192, @@ -15843,6 +15714,7 @@ "max_output_tokens": 200000, "input_cost_per_token": 3.3e-06, "output_cost_per_token": 1.65e-05, + "cache_read_input_token_cost": 3.3e-07, "litellm_provider": "deepinfra", "mode": "chat", "supports_tool_choice": true @@ -15867,67 +15739,15 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/bigcode/starcoder2-15b-instruct-v0.1": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 1.5e-07, - "output_cost_per_token": 1.5e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/cognitivecomputations/dolphin-2.6-mixtral-8x7b": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 2.4e-07, - "output_cost_per_token": 2.4e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/cognitivecomputations/dolphin-2.9.1-llama-3-70b": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 3.5e-07, - "output_cost_per_token": 4e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/deepinfra/airoboros-70b": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 7e-07, - "output_cost_per_token": 9e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/deepseek-ai/DeepSeek-Prover-V2-671B": { - "max_tokens": 163840, - "max_input_tokens": 163840, - "max_output_tokens": 163840, - "input_cost_per_token": 5e-07, - "output_cost_per_token": 2.18e-06, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false, - "supports_reasoning": true - }, "deepinfra/deepseek-ai/DeepSeek-R1": { "max_tokens": 163840, "max_input_tokens": 163840, "max_output_tokens": 163840, - "input_cost_per_token": 4.5e-07, - "output_cost_per_token": 2.15e-06, + "input_cost_per_token": 7e-07, + "output_cost_per_token": 2.4e-06, "litellm_provider": "deepinfra", "mode": "chat", - "supports_tool_choice": true, - "supports_reasoning": true + "supports_tool_choice": true }, "deepinfra/deepseek-ai/DeepSeek-R1-0528": { "max_tokens": 163840, @@ -15935,10 +15755,10 @@ "max_output_tokens": 163840, "input_cost_per_token": 5e-07, "output_cost_per_token": 2.15e-06, + "cache_read_input_token_cost": 4e-07, "litellm_provider": "deepinfra", "mode": "chat", - "supports_tool_choice": true, - "supports_reasoning": true + "supports_tool_choice": true }, "deepinfra/deepseek-ai/DeepSeek-R1-0528-Turbo": { "max_tokens": 32768, @@ -15948,8 +15768,7 @@ "output_cost_per_token": 3e-06, "litellm_provider": "deepinfra", "mode": "chat", - "supports_tool_choice": true, - "supports_reasoning": true + "supports_tool_choice": true }, "deepinfra/deepseek-ai/DeepSeek-R1-Distill-Llama-70B": { "max_tokens": 131072, @@ -15959,8 +15778,7 @@ "output_cost_per_token": 4e-07, "litellm_provider": "deepinfra", "mode": "chat", - "supports_tool_choice": false, - "supports_reasoning": true + "supports_tool_choice": false }, "deepinfra/deepseek-ai/DeepSeek-R1-Distill-Qwen-32B": { "max_tokens": 131072, @@ -15970,19 +15788,17 @@ "output_cost_per_token": 1.5e-07, "litellm_provider": "deepinfra", "mode": "chat", - "supports_tool_choice": true, - "supports_reasoning": true + "supports_tool_choice": true }, "deepinfra/deepseek-ai/DeepSeek-R1-Turbo": { - "max_tokens": 163840, - "max_input_tokens": 163840, - "max_output_tokens": 163840, + "max_tokens": 40960, + "max_input_tokens": 40960, + "max_output_tokens": 40960, "input_cost_per_token": 1e-06, "output_cost_per_token": 3e-06, "litellm_provider": "deepinfra", "mode": "chat", - "supports_tool_choice": true, - "supports_reasoning": true + "supports_tool_choice": true }, "deepinfra/deepseek-ai/DeepSeek-V3": { "max_tokens": 163840, @@ -15992,8 +15808,7 @@ "output_cost_per_token": 8.9e-07, "litellm_provider": "deepinfra", "mode": "chat", - "supports_tool_choice": true, - "supports_reasoning": true + "supports_tool_choice": true }, "deepinfra/deepseek-ai/DeepSeek-V3-0324": { "max_tokens": 163840, @@ -16001,59 +15816,18 @@ "max_output_tokens": 163840, "input_cost_per_token": 2.8e-07, "output_cost_per_token": 8.8e-07, + "cache_read_input_token_cost": 2.24e-07, "litellm_provider": "deepinfra", "mode": "chat", - "supports_tool_choice": true, - "supports_reasoning": true - }, - "deepinfra/deepseek-ai/DeepSeek-V3-0324-Turbo": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 1e-06, - "output_cost_per_token": 3e-06, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true, - "supports_reasoning": true + "supports_tool_choice": true }, "deepinfra/deepseek-ai/DeepSeek-V3.1": { "max_tokens": 163840, "max_input_tokens": 163840, "max_output_tokens": 163840, - "input_cost_per_token": 3e-07, + "input_cost_per_token": 2.7e-07, "output_cost_per_token": 1e-06, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false, - "supports_reasoning": true - }, - "deepinfra/google/codegemma-7b-it": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 7e-08, - "output_cost_per_token": 7e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/google/gemini-1.5-flash": { - "max_tokens": 1000000, - "max_input_tokens": 1000000, - "max_output_tokens": 1000000, - "input_cost_per_token": 7.5e-08, - "output_cost_per_token": 3e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/google/gemini-1.5-flash-8b": { - "max_tokens": 1000000, - "max_input_tokens": 1000000, - "max_output_tokens": 1000000, - "input_cost_per_token": 3.75e-08, - "output_cost_per_token": 1.5e-07, + "cache_read_input_token_cost": 2.16e-07, "litellm_provider": "deepinfra", "mode": "chat", "supports_tool_choice": true @@ -16088,36 +15862,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/google/gemma-1.1-7b-it": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 7e-08, - "output_cost_per_token": 7e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/google/gemma-2-27b-it": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 2.7e-07, - "output_cost_per_token": 2.7e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/google/gemma-2-9b-it": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 3e-08, - "output_cost_per_token": 6e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, "deepinfra/google/gemma-3-12b-it": { "max_tokens": 131072, "max_input_tokens": 131072, @@ -16142,48 +15886,8 @@ "max_tokens": 131072, "max_input_tokens": 131072, "max_output_tokens": 131072, - "input_cost_per_token": 2e-08, - "output_cost_per_token": 4e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/lizpreciatior/lzlv_70b_fp16_hf": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 3.5e-07, - "output_cost_per_token": 4e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/mattshumer/Reflection-Llama-3.1-70B": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 3.5e-07, - "output_cost_per_token": 4e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/meta-llama/Llama-2-13b-chat-hf": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 1.3e-07, - "output_cost_per_token": 1.3e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/meta-llama/Llama-2-70b-chat-hf": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 6.4e-07, - "output_cost_per_token": 8e-07, + "input_cost_per_token": 4e-08, + "output_cost_per_token": 8e-08, "litellm_provider": "deepinfra", "mode": "chat", "supports_tool_choice": true @@ -16198,16 +15902,6 @@ "mode": "chat", "supports_tool_choice": false }, - "deepinfra/meta-llama/Llama-3.2-1B-Instruct": { - "max_tokens": 131072, - "max_input_tokens": 131072, - "max_output_tokens": 131072, - "input_cost_per_token": 5e-09, - "output_cost_per_token": 1e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, "deepinfra/meta-llama/Llama-3.2-3B-Instruct": { "max_tokens": 131072, "max_input_tokens": 131072, @@ -16218,16 +15912,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/meta-llama/Llama-3.2-90B-Vision-Instruct": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 3.5e-07, - "output_cost_per_token": 4e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, "deepinfra/meta-llama/Llama-3.3-70B-Instruct": { "max_tokens": 131072, "max_input_tokens": 131072, @@ -16258,16 +15942,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/meta-llama/Llama-4-Maverick-17B-128E-Instruct-Turbo": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 5e-07, - "output_cost_per_token": 5e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, "deepinfra/meta-llama/Llama-4-Scout-17B-16E-Instruct": { "max_tokens": 327680, "max_input_tokens": 327680, @@ -16298,16 +15972,6 @@ "mode": "chat", "supports_tool_choice": false }, - "deepinfra/meta-llama/Meta-Llama-3-70B-Instruct": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 3e-07, - "output_cost_per_token": 4e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, "deepinfra/meta-llama/Meta-Llama-3-8B-Instruct": { "max_tokens": 8192, "max_input_tokens": 8192, @@ -16318,16 +15982,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/meta-llama/Meta-Llama-3.1-405B-Instruct": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 8e-07, - "output_cost_per_token": 8e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, "deepinfra/meta-llama/Meta-Llama-3.1-70B-Instruct": { "max_tokens": 131072, "max_input_tokens": 131072, @@ -16368,36 +16022,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/microsoft/Phi-3-medium-4k-instruct": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 1.4e-07, - "output_cost_per_token": 1.4e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/microsoft/Phi-4-multimodal-instruct": { - "max_tokens": 131072, - "max_input_tokens": 131072, - "max_output_tokens": 131072, - "input_cost_per_token": 5e-08, - "output_cost_per_token": 1e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/microsoft/WizardLM-2-7B": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 5.5e-08, - "output_cost_per_token": 5.5e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, "deepinfra/microsoft/WizardLM-2-8x22B": { "max_tokens": 65536, "max_input_tokens": 65536, @@ -16418,66 +16042,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/microsoft/phi-4-reasoning-plus": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 7e-08, - "output_cost_per_token": 3.5e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/mistralai/Devstral-Small-2505": { - "max_tokens": 128000, - "max_input_tokens": 128000, - "max_output_tokens": 128000, - "input_cost_per_token": 6e-08, - "output_cost_per_token": 1.2e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/mistralai/Devstral-Small-2507": { - "max_tokens": 128000, - "max_input_tokens": 128000, - "max_output_tokens": 128000, - "input_cost_per_token": 7e-08, - "output_cost_per_token": 2.8e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/mistralai/Mistral-7B-Instruct-v0.1": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 5.5e-08, - "output_cost_per_token": 5.5e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/mistralai/Mistral-7B-Instruct-v0.2": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 5.5e-08, - "output_cost_per_token": 5.5e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/mistralai/Mistral-7B-Instruct-v0.3": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 2.8e-08, - "output_cost_per_token": 5.4e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, "deepinfra/mistralai/Mistral-Nemo-Instruct-2407": { "max_tokens": 131072, "max_input_tokens": 131072, @@ -16498,16 +16062,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/mistralai/Mistral-Small-3.1-24B-Instruct-2503": { - "max_tokens": 128000, - "max_input_tokens": 128000, - "max_output_tokens": 128000, - "input_cost_per_token": 5e-08, - "output_cost_per_token": 1e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, "deepinfra/mistralai/Mistral-Small-3.2-24B-Instruct-2506": { "max_tokens": 128000, "max_input_tokens": 128000, @@ -16518,16 +16072,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/mistralai/Mixtral-8x22B-Instruct-v0.1": { - "max_tokens": 65536, - "max_input_tokens": 65536, - "max_output_tokens": 65536, - "input_cost_per_token": 6.5e-07, - "output_cost_per_token": 6.5e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, "deepinfra/mistralai/Mixtral-8x7B-Instruct-v0.1": { "max_tokens": 32768, "max_input_tokens": 32768, @@ -16558,16 +16102,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/nvidia/Nemotron-4-340B-Instruct": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 4.2e-06, - "output_cost_per_token": 4.2e-06, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, "deepinfra/openai/gpt-oss-120b": { "max_tokens": 131072, "max_input_tokens": 131072, @@ -16588,36 +16122,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/openbmb/MiniCPM-Llama3-V-2_5": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 3.4e-07, - "output_cost_per_token": 3.4e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/openchat/openchat-3.6-8b": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 5.5e-08, - "output_cost_per_token": 5.5e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/openchat/openchat_3.5": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 5.5e-08, - "output_cost_per_token": 5.5e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, "deepinfra/zai-org/GLM-4.5": { "max_tokens": 131072, "max_input_tokens": 131072, @@ -21126,4 +20630,4 @@ "notes": "Volcengine Doubao embedding model - text-240715 version with 2560 dimensions" } } -} \ No newline at end of file +} From d89af0abb92beb1fc0908cda1d2ccd732c143e40 Mon Sep 17 00:00:00 2001 From: Dharamendra Kumar Date: Mon, 8 Sep 2025 09:02:34 -0700 Subject: [PATCH 10/73] Revert "Update poetry.lock" This reverts commit fa2d3a89718c1e9b751f953344f45c50753d57b5. --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 29d1a877087..e03f800bc96 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -1949,7 +1949,7 @@ description = "WSGI HTTP Server for UNIX" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "platform_system != \"Windows\" and (extra == \"mlflow\" or extra == \"proxy\") and python_version >= \"3.10\" or extra == \"proxy\"" +markers = "(python_version >= \"3.10\" or extra == \"proxy\") and (platform_system != \"Windows\" or extra == \"proxy\") and (python_version < \"3.12\" or extra == \"proxy\" or extra == \"mlflow\") and (extra == \"mlflow\" or extra == \"proxy\")" files = [ {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, @@ -2885,8 +2885,8 @@ files = [ [package.dependencies] numpy = [ {version = ">=1.23.3", markers = "python_version >= \"3.11\""}, - {version = ">1.20"}, {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, + {version = ">1.20", markers = "python_version < \"3.10\""}, {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, ] From 6c1c647338d382325c077148c2519b0485d08600 Mon Sep 17 00:00:00 2001 From: Dharamendra Kumar Date: Mon, 8 Sep 2025 11:04:34 -0700 Subject: [PATCH 11/73] Fix conflicts --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index e03f800bc96..09fd508eb5d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -1949,7 +1949,7 @@ description = "WSGI HTTP Server for UNIX" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "(python_version >= \"3.10\" or extra == \"proxy\") and (platform_system != \"Windows\" or extra == \"proxy\") and (python_version < \"3.12\" or extra == \"proxy\" or extra == \"mlflow\") and (extra == \"mlflow\" or extra == \"proxy\")" +markers = "platform_system != \"Windows\" and (extra == \"mlflow\" or extra == \"proxy\") and python_version >= \"3.10\" or extra == \"proxy\"" files = [ {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, @@ -2885,8 +2885,8 @@ files = [ [package.dependencies] numpy = [ {version = ">=1.23.3", markers = "python_version >= \"3.11\""}, + {version = ">1.20"}, {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, - {version = ">1.20", markers = "python_version < \"3.10\""}, {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, ] From 7c5ec380c17558a121bb52cbbbc1bd6f5f4baa56 Mon Sep 17 00:00:00 2001 From: swarnabhasinha Date: Tue, 9 Sep 2025 15:57:53 +0530 Subject: [PATCH 12/73] fix: remove anthropic-beta header for Vertex AI requests with prompt caching --- litellm/llms/anthropic/common_utils.py | 11 +- .../test_vertex_ai_prompt_caching_fix.py | 134 ++++++++++++++++++ 2 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 tests/litellm/llms/anthropic/test_vertex_ai_prompt_caching_fix.py diff --git a/litellm/llms/anthropic/common_utils.py b/litellm/llms/anthropic/common_utils.py index 68b5341e954..06ebb5079d9 100644 --- a/litellm/llms/anthropic/common_utils.py +++ b/litellm/llms/anthropic/common_utils.py @@ -107,8 +107,10 @@ class AnthropicModelInfo(BaseLLMModelInfo): user_anthropic_beta_headers: Optional[List[str]] = None, ) -> dict: betas = set() - if prompt_caching_set: - betas.add("prompt-caching-2024-07-31") + # Note: prompt-caching-2024-07-31 header is no longer required for prompt caching + # as per current Anthropic documentation. It's now generally available. + # if prompt_caching_set: + # betas.add("prompt-caching-2024-07-31") if computer_tool_used: betas.add("computer-use-2024-10-22") # if pdf_used: @@ -176,6 +178,11 @@ class AnthropicModelInfo(BaseLLMModelInfo): mcp_server_used=mcp_server_used, ) + # For Vertex AI requests, remove any user-provided anthropic-beta headers + # since Vertex AI rejects them and they're no longer required for prompt caching + if optional_params.get("is_vertex_request", False): + headers = {k: v for k, v in headers.items() if k != "anthropic-beta"} + headers = {**headers, **anthropic_headers} return headers diff --git a/tests/litellm/llms/anthropic/test_vertex_ai_prompt_caching_fix.py b/tests/litellm/llms/anthropic/test_vertex_ai_prompt_caching_fix.py new file mode 100644 index 00000000000..67c37bc6f26 --- /dev/null +++ b/tests/litellm/llms/anthropic/test_vertex_ai_prompt_caching_fix.py @@ -0,0 +1,134 @@ +""" +Test file for Vertex AI prompt caching fix. + +This test verifies that: +1. The anthropic-beta header is removed for Vertex AI requests +2. Regular Anthropic requests still work correctly +3. Prompt caching detection logic is preserved +""" + +import unittest +from unittest.mock import patch, MagicMock +from litellm.llms.anthropic.common_utils import AnthropicModelInfo + + +class TestVertexAIPromptCachingFix(unittest.TestCase): + """Test cases for the Vertex AI prompt caching fix.""" + + def setUp(self): + """Set up test fixtures.""" + self.model_info = AnthropicModelInfo() + + def _is_cache_control_set(self, messages): + """Helper method to test cache control detection.""" + for message in messages: + if message.get("cache_control", None) is not None: + return True + _message_content = message.get("content") + if _message_content is not None and isinstance(_message_content, list): + for content in _message_content: + if "cache_control" in content: + return True + return False + + def test_vertex_ai_removes_anthropic_beta_header(self): + """Test that anthropic-beta header is removed for Vertex AI requests.""" + # Mock the get_anthropic_headers method + with patch.object(self.model_info, 'get_anthropic_headers') as mock_get_headers: + # Set up the mock to return headers with anthropic-beta + mock_get_headers.return_value = { + 'anthropic-beta': 'prompt-caching-2024-07-31', + 'content-type': 'application/json' + } + + # Test with Vertex AI request + optional_params = {'is_vertex_request': True} + headers = self.model_info.get_anthropic_headers( + model='vertex/claude-3-5-sonnet-20240620', + messages=[], + optional_params=optional_params + ) + + # Verify anthropic-beta header is removed + self.assertNotIn('anthropic-beta', headers) + + def test_regular_anthropic_preserves_anthropic_beta_header(self): + """Test that regular Anthropic requests preserve anthropic-beta header.""" + # Mock the get_anthropic_headers method + with patch.object(self.model_info, 'get_anthropic_headers') as mock_get_headers: + # Set up the mock to return headers with anthropic-beta + mock_get_headers.return_value = { + 'anthropic-beta': 'prompt-caching-2024-07-31', + 'content-type': 'application/json' + } + + # Test with regular Anthropic request (not Vertex AI) + optional_params = {'is_vertex_request': False} + headers = self.model_info.get_anthropic_headers( + model='claude-3-5-sonnet-20240620', + messages=[], + optional_params=optional_params + ) + + # Verify anthropic-beta header is preserved + self.assertIn('anthropic-beta', headers) + + def test_prompt_caching_detection_still_works(self): + """Test that prompt caching detection logic still works.""" + # Test messages with cache_control + messages_with_cache = [ + { + 'role': 'user', + 'content': [ + { + 'type': 'text', + 'text': 'Test prompt', + 'cache_control': {'type': 'ephemeral'} + } + ] + } + ] + + # Test messages without cache_control + messages_without_cache = [ + { + 'role': 'user', + 'content': [ + { + 'type': 'text', + 'text': 'Regular prompt' + } + ] + } + ] + + # Test cache detection + cache_detected_with = self._is_cache_control_set(messages_with_cache) + cache_detected_without = self._is_cache_control_set(messages_without_cache) + + self.assertTrue(cache_detected_with) + self.assertFalse(cache_detected_without) + + def test_vertex_ai_without_user_beta_header(self): + """Test Vertex AI request when no user-provided beta header exists.""" + # Mock the get_anthropic_headers method + with patch.object(self.model_info, 'get_anthropic_headers') as mock_get_headers: + # Set up the mock to return headers without anthropic-beta + mock_get_headers.return_value = { + 'content-type': 'application/json' + } + + # Test with Vertex AI request + optional_params = {'is_vertex_request': True} + headers = self.model_info.get_anthropic_headers( + model='vertex/claude-3-5-sonnet-20240620', + messages=[], + optional_params=optional_params + ) + + # Verify no anthropic-beta header is present + self.assertNotIn('anthropic-beta', headers) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From 4076e20b35652615a21bb457ce093e7b520332c3 Mon Sep 17 00:00:00 2001 From: swarnabhasinha Date: Tue, 9 Sep 2025 16:05:04 +0530 Subject: [PATCH 13/73] fix: lint --- litellm/llms/vertex_ai/gemini/transformation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/llms/vertex_ai/gemini/transformation.py b/litellm/llms/vertex_ai/gemini/transformation.py index 6de2931356d..91d04e84a73 100644 --- a/litellm/llms/vertex_ai/gemini/transformation.py +++ b/litellm/llms/vertex_ai/gemini/transformation.py @@ -470,7 +470,7 @@ def _transform_request_body( metadata = litellm_params["metadata"] if "requester_metadata" in metadata: rm = metadata["requester_metadata"] - labels = {k: v for k, v in rm.items() if type(v) is str} + labels = {k: v for k, v in rm.items() if isinstance(v, str)} filtered_params = { k: v for k, v in optional_params.items() if k in config_fields From f8ddd123d10ae308f4c70a599907c147279542f7 Mon Sep 17 00:00:00 2001 From: swarnabhasinha Date: Tue, 9 Sep 2025 16:16:59 +0530 Subject: [PATCH 14/73] fix: add missing patch import in vertex_ai test --- tests/test_litellm/llms/vertex_ai/test_vertex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_litellm/llms/vertex_ai/test_vertex.py b/tests/test_litellm/llms/vertex_ai/test_vertex.py index 7e683d1f54e..02bd622016b 100644 --- a/tests/test_litellm/llms/vertex_ai/test_vertex.py +++ b/tests/test_litellm/llms/vertex_ai/test_vertex.py @@ -10,7 +10,7 @@ import litellm.litellm_core_utils.prompt_templates import litellm.litellm_core_utils.prompt_templates.factory load_dotenv() -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch sys.path.insert( 0, os.path.abspath("../..") From 59360e64f9cca60312a399cbe1d5e22a31d62292 Mon Sep 17 00:00:00 2001 From: Derek Worthen Date: Tue, 9 Sep 2025 07:01:05 -0700 Subject: [PATCH 15/73] Fix embeddings using azure_ad_token_provider. --- litellm/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/main.py b/litellm/main.py index 1bb84cccdee..d7395eb1457 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -3854,7 +3854,7 @@ def embedding( # noqa: PLR0915 max_retries = kwargs.get("max_retries", None) litellm_logging_obj: LiteLLMLoggingObj = kwargs.get("litellm_logging_obj") # type: ignore mock_response: Optional[List[float]] = kwargs.get("mock_response", None) # type: ignore - azure_ad_token_provider = kwargs.pop("azure_ad_token_provider", None) + azure_ad_token_provider = kwargs.get("azure_ad_token_provider", None) aembedding = kwargs.get("aembedding", None) extra_headers = kwargs.get("extra_headers", None) headers = kwargs.get("headers", None) From 4d7e918b6367e49cdbc6f078d49740b742c7e800 Mon Sep 17 00:00:00 2001 From: Oleksandr Tereshchenko Date: Tue, 9 Sep 2025 17:55:03 +0300 Subject: [PATCH 16/73] restore forgotten `raise` keyword in front of an `exception_type()` call --- litellm/litellm_core_utils/streaming_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/litellm_core_utils/streaming_handler.py b/litellm/litellm_core_utils/streaming_handler.py index 2203cac11d0..83b4985b239 100644 --- a/litellm/litellm_core_utils/streaming_handler.py +++ b/litellm/litellm_core_utils/streaming_handler.py @@ -1937,7 +1937,7 @@ class CustomStreamWrapper: ) ## Map to OpenAI Exception try: - exception_type( + raise exception_type( model=self.model, custom_llm_provider=self.custom_llm_provider, original_exception=e, From f51036e092683a7d83a86d08b7d293203f7c7267 Mon Sep 17 00:00:00 2001 From: mubashir1osmani Date: Wed, 10 Sep 2025 00:32:26 -0400 Subject: [PATCH 17/73] added tags to langchain --- docs/my-website/docs/langchain/langchain.md | 318 ++++++++++++++++++++ docs/my-website/docs/proxy/user_keys.md | 100 ++++++ 2 files changed, 418 insertions(+) diff --git a/docs/my-website/docs/langchain/langchain.md b/docs/my-website/docs/langchain/langchain.md index 78425a73b99..c67375ce1be 100644 --- a/docs/my-website/docs/langchain/langchain.md +++ b/docs/my-website/docs/langchain/langchain.md @@ -162,3 +162,321 @@ Get more details [here](../observability/lunary_integration.md) ## Use LangChain ChatLiteLLM + Langfuse Checkout this section [here](../observability/langfuse_integration#use-langchain-chatlitellm--langfuse) for more details on how to integrate Langfuse with ChatLiteLLM. + +## Using Tags with LangChain and LiteLLM + +Tags are a powerful feature in LiteLLM that allow you to categorize, filter, and track your LLM requests. When using LangChain with LiteLLM, you can pass tags through the `extra_body` parameter in the metadata. + +### Basic Tag Usage + + + + +```python +import os +from langchain_openai import ChatOpenAI +from langchain_core.messages import HumanMessage, SystemMessage + +os.environ['OPENAI_API_KEY'] = "sk-your-key-here" + +chat = ChatOpenAI( + model="gpt-4o", + temperature=0.7, + extra_body={ + "metadata": { + "tags": ["production", "customer-support", "high-priority"] + } + } +) + +messages = [ + SystemMessage(content="You are a helpful customer support assistant."), + HumanMessage(content="How do I reset my password?") +] + +response = chat.invoke(messages) +print(response) +``` + + + + + +```python +import os +from langchain_openai import ChatOpenAI +from langchain_core.messages import HumanMessage, SystemMessage + +os.environ['ANTHROPIC_API_KEY'] = "sk-ant-your-key-here" + +chat = ChatOpenAI( + model="claude-3-sonnet-20240229", + temperature=0.7, + extra_body={ + "metadata": { + "tags": ["research", "analysis", "claude-model"] + } + } +) + +messages = [ + SystemMessage(content="You are a research analyst."), + HumanMessage(content="Analyze this market trend...") +] + +response = chat.invoke(messages) +print(response) +``` + + + + + +```python +import os +from langchain_openai import ChatOpenAI +from langchain_core.messages import HumanMessage, SystemMessage + +# No API key needed when using proxy +chat = ChatOpenAI( + openai_api_base="http://localhost:4000", # Your proxy URL + model="gpt-4o", + temperature=0.7, + extra_body={ + "metadata": { + "tags": ["proxy", "team-alpha", "feature-flagged"], + "generation_name": "customer-onboarding", + "trace_user_id": "user-12345" + } + } +) + +messages = [ + SystemMessage(content="You are an onboarding assistant."), + HumanMessage(content="Welcome our new customer!") +] + +response = chat.invoke(messages) +print(response) +``` + + + + +### Advanced Tag Patterns + +#### Dynamic Tags Based on Context + +```python +import os +from langchain_openai import ChatOpenAI +from langchain_core.messages import HumanMessage, SystemMessage + +def create_chat_with_tags(user_type: str, feature: str): + """Create a chat instance with dynamic tags based on context""" + + # Build tags dynamically + tags = ["langchain-integration"] + + if user_type == "premium": + tags.extend(["premium-user", "high-priority"]) + elif user_type == "enterprise": + tags.extend(["enterprise", "custom-sla"]) + else: + tags.append("standard-user") + + # Add feature-specific tags + if feature == "code-review": + tags.extend(["development", "code-analysis"]) + elif feature == "content-gen": + tags.extend(["marketing", "content-creation"]) + + return ChatOpenAI( + openai_api_base="http://localhost:4000", + model="gpt-4o", + temperature=0.7, + extra_body={ + "metadata": { + "tags": tags, + "user_type": user_type, + "feature": feature, + "trace_user_id": f"user-{user_type}-{feature}" + } + } + ) + +# Usage examples +premium_chat = create_chat_with_tags("premium", "code-review") +enterprise_chat = create_chat_with_tags("enterprise", "content-gen") + +messages = [HumanMessage(content="Help me with this task")] +response = premium_chat.invoke(messages) +``` + +#### Tags for Cost Tracking and Analytics + +```python +import os +from langchain_openai import ChatOpenAI +from langchain_core.messages import HumanMessage, SystemMessage + +# Tags for cost tracking +cost_tracking_chat = ChatOpenAI( + openai_api_base="http://localhost:4000", + model="gpt-4o", + temperature=0.7, + extra_body={ + "metadata": { + "tags": [ + "cost-center-marketing", + "budget-q4-2024", + "project-launch-campaign", + "high-cost-model" # Flag for expensive models + ], + "department": "marketing", + "project_id": "campaign-2024-q4", + "cost_threshold": "high" + } + } +) + +messages = [ + SystemMessage(content="You are a marketing copywriter."), + HumanMessage(content="Create compelling ad copy for our new product launch.") +] + +response = cost_tracking_chat.invoke(messages) +``` + +#### Tags for A/B Testing + +```python +import os +from langchain_openai import ChatOpenAI +from langchain_core.messages import HumanMessage, SystemMessage +import random + +def create_ab_test_chat(test_variant: str = None): + """Create chat instance for A/B testing with appropriate tags""" + + if test_variant is None: + test_variant = random.choice(["variant-a", "variant-b"]) + + return ChatOpenAI( + openai_api_base="http://localhost:4000", + model="gpt-4o", + temperature=0.7 if test_variant == "variant-a" else 0.9, # Different temp for variants + extra_body={ + "metadata": { + "tags": [ + "ab-test-experiment-1", + f"variant-{test_variant}", + "temperature-test", + "user-experience" + ], + "experiment_id": "ab-test-001", + "variant": test_variant, + "test_group": "temperature-optimization" + } + } + ) + +# Run A/B test +variant_a_chat = create_ab_test_chat("variant-a") +variant_b_chat = create_ab_test_chat("variant-b") + +test_message = [HumanMessage(content="Explain quantum computing in simple terms")] + +response_a = variant_a_chat.invoke(test_message) +response_b = variant_b_chat.invoke(test_message) +``` + +### Tag Best Practices + +#### 1. **Consistent Naming Convention** +```python +# ✅ Good: Consistent, descriptive tags +tags = ["production", "api-v2", "customer-support", "urgent"] + +# ❌ Avoid: Inconsistent or unclear tags +tags = ["prod", "v2", "support", "urgent123"] +``` + +#### 2. **Hierarchical Tags** +```python +# ✅ Good: Hierarchical structure +tags = ["env:production", "team:backend", "service:api", "priority:high"] + +# This allows for easy filtering and grouping +``` + +#### 3. **Include Context Information** +```python +extra_body={ + "metadata": { + "tags": ["production", "user-onboarding"], + "user_id": "user-12345", + "session_id": "session-abc123", + "feature_flag": "new-onboarding-flow", + "environment": "production" + } +} +``` + +#### 4. **Tag Categories** +Consider organizing tags into categories: +- **Environment**: `production`, `staging`, `development` +- **Team/Service**: `backend`, `frontend`, `api`, `worker` +- **Feature**: `authentication`, `payment`, `notification` +- **Priority**: `critical`, `high`, `medium`, `low` +- **User Type**: `premium`, `enterprise`, `free` + +### Using Tags with LiteLLM Proxy + +When using tags with LiteLLM Proxy, you can: + +1. **Filter requests** based on tags +2. **Track costs** by tags in spend reports +3. **Apply routing rules** based on tags +4. **Monitor usage** with tag-based analytics + +#### Example Proxy Configuration with Tags + +```yaml +# config.yaml +model_list: + - model_name: gpt-4o + litellm_params: + model: gpt-4o + api_key: your-key + +# Tag-based routing rules +tag_routing: + - tags: ["premium", "high-priority"] + models: ["gpt-4o", "claude-3-opus"] + - tags: ["standard"] + models: ["gpt-3.5-turbo", "claude-3-haiku"] +``` + +### Monitoring and Analytics + +Tags enable powerful analytics capabilities: + +```python +# Example: Get spend reports by tags +import requests + +response = requests.get( + "http://localhost:4000/global/spend/report", + headers={"Authorization": "Bearer sk-your-key"}, + params={ + "start_date": "2024-01-01", + "end_date": "2024-12-31", + "group_by": "tags" + } +) + +spend_by_tags = response.json() +``` + +This documentation covers the essential patterns for using tags effectively with LangChain and LiteLLM, enabling better organization, tracking, and analytics of your LLM requests. diff --git a/docs/my-website/docs/proxy/user_keys.md b/docs/my-website/docs/proxy/user_keys.md index ecf6f2d0532..21e1d3dbf40 100644 --- a/docs/my-website/docs/proxy/user_keys.md +++ b/docs/my-website/docs/proxy/user_keys.md @@ -357,6 +357,106 @@ assert user.age == 25 +## Using Tags for Categorization and Tracking + +Tags allow you to categorize, filter, and track your LLM requests. Add tags to your metadata for better organization and analytics. + + + + +```python +import openai +client = openai.OpenAI( + api_key="anything", + base_url="http://0.0.0.0:4000" +) + +response = client.chat.completions.create( + model="gpt-3.5-turbo", + messages=[{"role": "user", "content": "Hello!"}], + extra_body={ + "metadata": { + "tags": ["production", "customer-support", "urgent"], + "generation_name": "support-bot", + "trace_user_id": "user-123" + } + } +) +``` + + + + + +```python +from langchain_openai import ChatOpenAI +from langchain_core.messages import HumanMessage + +chat = ChatOpenAI( + openai_api_base="http://0.0.0.0:4000", + model="gpt-4o", + extra_body={ + "metadata": { + "tags": ["langchain-integration", "content-gen"], + "trace_user_id": "user-456" + } + } +) + +response = chat.invoke([HumanMessage(content="Generate a blog post")]) +``` + + + + + +```bash +curl --location 'http://0.0.0.0:4000/chat/completions' \ + --header 'Content-Type: application/json' \ + --data '{ + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "Hello!"}], + "metadata": { + "tags": ["api-test", "development"], + "trace_user_id": "test-user" + } +}' +``` + + + + + +```js +const { OpenAI } = require('openai'); + +const openai = new OpenAI({ + apiKey: "sk-1234", + baseURL: "http://0.0.0.0:4000" +}); + +async function main() { + const response = await openai.chat.completions.create({ + messages: [{ role: 'user', content: 'Hello!' }], + model: 'gpt-3.5-turbo', + metadata: { + tags: ["javascript-client", "api-test"], + trace_user_id: "js-user-789" + } + }); +} +``` + + + + +### Tag Benefits + +- **Cost Tracking**: Monitor spending by project/team/feature +- **Analytics**: Filter requests by tags in logs and dashboards +- **Routing**: Use tags for conditional model routing +- **Debugging**: Easier troubleshooting with categorized requests + ### Response Format ```json From c20a5b0bbd764d381ad3729a519f28b844878c49 Mon Sep 17 00:00:00 2001 From: mubashir1osmani Date: Wed, 10 Sep 2025 00:51:37 -0400 Subject: [PATCH 18/73] more info for turn_off_logging --- docs/my-website/docs/proxy/config_settings.md | 4 ++-- docs/my-website/docs/proxy/logging.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/my-website/docs/proxy/config_settings.md b/docs/my-website/docs/proxy/config_settings.md index 7eb355d39f2..e33301bcd2e 100644 --- a/docs/my-website/docs/proxy/config_settings.md +++ b/docs/my-website/docs/proxy/config_settings.md @@ -21,7 +21,7 @@ litellm_settings: failure_callback: ["sentry"] # list of failure callbacks callbacks: ["otel"] # list of callbacks - runs on success and failure service_callbacks: ["datadog", "prometheus"] # logs redis, postgres failures on datadog, prometheus - turn_off_message_logging: boolean # prevent the messages and responses from being logged to on your callbacks, but request metadata will still be logged. + turn_off_message_logging: boolean # prevent the messages and responses from being logged to on your callbacks, but request metadata will still be logged. Useful for privacy/compliance when handling sensitive data. redact_user_api_key_info: boolean # Redact information about the user api key (hashed token, user_id, team id, etc.), from logs. Currently supported for Langfuse, OpenTelemetry, Logfire, ArizeAI logging. langfuse_default_tags: ["cache_hit", "cache_key", "proxy_base_url", "user_api_key_alias", "user_api_key_user_id", "user_api_key_user_email", "user_api_key_team_alias", "semantic-similarity", "proxy_base_url"] # default tags for Langfuse Logging @@ -131,7 +131,7 @@ general_settings: | failure_callback | array of strings | List of failure callbacks [Doc Proxy logging callbacks](logging), [Doc Metrics](prometheus) | | callbacks | array of strings | List of callbacks - runs on success and failure [Doc Proxy logging callbacks](logging), [Doc Metrics](prometheus) | | service_callbacks | array of strings | System health monitoring - Logs redis, postgres failures on specified services (e.g. datadog, prometheus) [Doc Metrics](prometheus) | -| turn_off_message_logging | boolean | If true, prevents messages and responses from being logged to callbacks, but request metadata will still be logged [Proxy Logging](logging) | +| turn_off_message_logging | boolean | If true, prevents messages and responses from being logged to callbacks, but request metadata will still be logged. Useful for privacy/compliance when handling sensitive data [Proxy Logging](logging) | | modify_params | boolean | If true, allows modifying the parameters of the request before it is sent to the LLM provider | | enable_preview_features | boolean | If true, enables preview features - e.g. Azure O1 Models with streaming support.| | redact_user_api_key_info | boolean | If true, redacts information about the user api key from logs [Proxy Logging](logging#redacting-userapikeyinfo) | diff --git a/docs/my-website/docs/proxy/logging.md b/docs/my-website/docs/proxy/logging.md index 5d3f8417222..ff2591daad2 100644 --- a/docs/my-website/docs/proxy/logging.md +++ b/docs/my-website/docs/proxy/logging.md @@ -60,7 +60,7 @@ components in your system, including in logging tools. ### Redact Messages, Response Content -Set `litellm.turn_off_message_logging=True` This will prevent the messages and responses from being logged to your logging provider, but request metadata - e.g. spend, will still be tracked. +Set `litellm.turn_off_message_logging=True` This will prevent the messages and responses from being logged to your logging provider, but request metadata - e.g. spend, will still be tracked. Useful for privacy/compliance when handling sensitive data. From f6bc4d0bf949da3240382807e74f904ebc164e67 Mon Sep 17 00:00:00 2001 From: Tom Alon Date: Wed, 10 Sep 2025 11:58:15 +0300 Subject: [PATCH 19/73] Noma non blocking on monitor mode --- .../guardrails/guardrail_hooks/noma/noma.py | 228 +++++++++--- .../guardrails/guardrail_hooks/test_noma.py | 325 +++++++++++++++++- 2 files changed, 502 insertions(+), 51 deletions(-) diff --git a/litellm/proxy/guardrails/guardrail_hooks/noma/noma.py b/litellm/proxy/guardrails/guardrail_hooks/noma/noma.py index ed5929f0564..3bbd183e49b 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/noma/noma.py +++ b/litellm/proxy/guardrails/guardrail_hooks/noma/noma.py @@ -5,6 +5,7 @@ # # +-------------------------------------------------------------+ +import asyncio import copy import os from typing import Any, Dict, Literal, Optional, Union @@ -24,6 +25,10 @@ from litellm.proxy._types import UserAPIKeyAuth from litellm.types.guardrails import GuardrailEventHooks from litellm.types.utils import EmbeddingResponse, ImageResponse +# Type aliases +MessageRole = Literal["user", "assistant"] +LLMResponse = Union[Any, ModelResponse, EmbeddingResponse, ImageResponse] + class NomaBlockedMessage(HTTPException): """Exception raised when Noma guardrail blocks a message""" @@ -164,6 +169,138 @@ class NomaGuardrail(CustomGuardrail): super().__init__(**kwargs) + def _create_background_noma_check( + self, + coro, + ) -> None: + """Create a background task for Noma API calls without blocking the main flow""" + try: + asyncio.create_task(coro) + except Exception as e: + verbose_proxy_logger.error( + f"Failed to create background Noma task: {str(e)}" + ) + + async def _process_user_message_check( + self, + request_data: dict, + user_auth: UserAPIKeyAuth, + ) -> Optional[str]: + """Shared logic for processing user message checks""" + extra_data = self.get_guardrail_dynamic_request_body_params(request_data) + + user_message = await self._extract_user_message(request_data) + if not user_message: + return None + + payload = {"request": {"text": user_message}} + response_json = await self._call_noma_api( + payload=payload, + llm_request_id=None, + request_data=request_data, + user_auth=user_auth, + extra_data=extra_data, + ) + + if self.monitor_mode: + await self._handle_verdict_background("user", user_message, response_json) + else: + await self._check_verdict("user", user_message, response_json) + + return user_message + + async def _process_llm_response_check( + self, + request_data: dict, + response: LLMResponse, + user_auth: UserAPIKeyAuth, + ) -> Optional[str]: + """Shared logic for processing LLM response checks""" + extra_data = self.get_guardrail_dynamic_request_body_params(request_data) + + if not isinstance(response, litellm.ModelResponse): + return None + + content = None + for choice in response.choices: + if isinstance(choice, litellm.Choices) and choice.message.content: + content = choice.message.content + break + + if not content or not isinstance(content, str): + return None + + payload = {"response": {"text": content}} + + response_json = await self._call_noma_api( + payload=payload, + llm_request_id=response.id, + request_data=request_data, + user_auth=user_auth, + extra_data=extra_data, + ) + + if self.monitor_mode: + await self._handle_verdict_background("assistant", content, response_json) + else: + await self._check_verdict("assistant", content, response_json) + + return content + + async def _check_user_message_background( + self, + request_data: dict, + user_auth: UserAPIKeyAuth, + ) -> None: + """Check user message in background for monitor mode - non-blocking""" + try: + await self._process_user_message_check(request_data, user_auth) + except Exception as e: + verbose_proxy_logger.error( + f"Noma background user message check failed: {str(e)}" + ) + + async def _check_llm_response_background( + self, + request_data: dict, + response: LLMResponse, + user_auth: UserAPIKeyAuth, + ) -> None: + """Check LLM response in background for monitor mode - non-blocking""" + try: + await self._process_llm_response_check(request_data, response, user_auth) + except Exception as e: + verbose_proxy_logger.error( + f"Noma background response check failed: {str(e)}" + ) + + async def _handle_verdict_background( + self, + type: MessageRole, + message: str, + response_json: dict, + ) -> None: + """Handle verdict from Noma API in background - logging only, never blocks""" + try: + if not response_json.get("verdict", True): + msg = str.format( + "Noma guardrail blocked {type} message: {message}", + type=type, + message=message, + ) + verbose_proxy_logger.warning(msg) + else: + msg = str.format( + "Noma guardrail allowed {type} message: {message}", + type=type, + message=message, + ) + verbose_proxy_logger.info(msg) + except Exception as e: + verbose_proxy_logger.error( + f"Noma background verdict handling failed: {str(e)}" + ) + async def async_pre_call_hook( self, user_api_key_dict: UserAPIKeyAuth, @@ -191,6 +328,18 @@ class NomaGuardrail(CustomGuardrail): ): return data + # In monitor mode, run Noma check in background and return immediately + if self.monitor_mode: + try: + self._create_background_noma_check( + self._check_user_message_background(data, user_api_key_dict) + ) + except Exception as e: + verbose_proxy_logger.error( + f"Failed to start background Noma pre-call check: {str(e)}" + ) + return data + try: return await self._check_user_message(data, user_api_key_dict) except NomaBlockedMessage: @@ -198,7 +347,7 @@ class NomaGuardrail(CustomGuardrail): except Exception as e: verbose_proxy_logger.error(f"Noma pre-call hook failed: {str(e)}") - if self.block_failures and not self.monitor_mode: + if self.block_failures: raise return data @@ -220,6 +369,18 @@ class NomaGuardrail(CustomGuardrail): if self.should_run_guardrail(data=data, event_type=event_type) is not True: return data + # In monitor mode, run Noma check in background and return immediately + if self.monitor_mode: + try: + self._create_background_noma_check( + self._check_user_message_background(data, user_api_key_dict) + ) + except Exception as e: + verbose_proxy_logger.error( + f"Failed to start background Noma moderation check: {str(e)}" + ) + return data + try: return await self._check_user_message(data, user_api_key_dict) except NomaBlockedMessage: @@ -227,7 +388,7 @@ class NomaGuardrail(CustomGuardrail): except Exception as e: verbose_proxy_logger.error(f"Noma moderation hook failed: {str(e)}") - if self.block_failures and not self.monitor_mode: + if self.block_failures: raise return data @@ -235,19 +396,33 @@ class NomaGuardrail(CustomGuardrail): self, data: dict, user_api_key_dict: UserAPIKeyAuth, - response: Union[Any, ModelResponse, EmbeddingResponse, ImageResponse], + response: LLMResponse, ): event_type: GuardrailEventHooks = GuardrailEventHooks.post_call if self.should_run_guardrail(data=data, event_type=event_type) is not True: return response + # In monitor mode, run Noma check in background and return immediately + if self.monitor_mode: + try: + self._create_background_noma_check( + self._check_llm_response_background( + data, response, user_api_key_dict + ) + ) + except Exception as e: + verbose_proxy_logger.error( + f"Failed to start background Noma post-call check: {str(e)}" + ) + return response + try: return await self._check_llm_response(data, response, user_api_key_dict) except NomaBlockedMessage: raise except Exception as e: verbose_proxy_logger.error(f"Noma post-call hook failed: {str(e)}") - if self.block_failures and not self.monitor_mode: + if self.block_failures: raise return response @@ -257,55 +432,24 @@ class NomaGuardrail(CustomGuardrail): user_auth: UserAPIKeyAuth, ) -> Union[Exception, str, dict, None]: """Check user message for policy violations""" - extra_data = self.get_guardrail_dynamic_request_body_params(request_data) - - user_message = await self._extract_user_message(request_data) + user_message = await self._process_user_message_check(request_data, user_auth) if not user_message: return request_data - payload = {"request": {"text": user_message}} - response_json = await self._call_noma_api( - payload=payload, - llm_request_id=None, - request_data=request_data, - user_auth=user_auth, - extra_data=extra_data, - ) - await self._check_verdict("user", user_message, response_json) - return request_data async def _check_llm_response( self, request_data: dict, - response: Union[Any, ModelResponse, EmbeddingResponse, ImageResponse], + response: LLMResponse, user_auth: UserAPIKeyAuth, ) -> Union[Exception, ModelResponse, Any]: """Check LLM response for policy violations""" - extra_data = self.get_guardrail_dynamic_request_body_params(request_data) - - if not isinstance(response, litellm.ModelResponse): - return response - - content = None - for choice in response.choices: - if isinstance(choice, litellm.Choices) and choice.message.content: - content = choice.message.content - break - - if not content or not isinstance(content, str): - return response - - payload = {"response": {"text": content}} - - response_json = await self._call_noma_api( - payload=payload, - llm_request_id=response.id, - request_data=request_data, - user_auth=user_auth, - extra_data=extra_data, + content = await self._process_llm_response_check( + request_data, response, user_auth ) - await self._check_verdict("assistant", content, response_json) + if not content: + return response return response @@ -371,7 +515,7 @@ class NomaGuardrail(CustomGuardrail): async def _check_verdict( self, - type: Literal["user", "assistant"], + type: MessageRole, message: str, response_json: dict, ) -> None: diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_noma.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_noma.py index aeea5f81b10..f1e91db7d59 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_noma.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_noma.py @@ -50,7 +50,6 @@ def mock_user_api_key_dict(): soft_budget=None, tpm_limit=None, rpm_limit=None, - parallel_request_limit=None, metadata={}, max_parallel_requests=None, allowed_cache_controls=[], @@ -291,15 +290,38 @@ class TestNomaGuardrailHooks: default_on=True, ) - mock_response = MagicMock() - mock_response.json.return_value = { - "verdict": False, - "originalResponse": {"prompt": {"harmfulContent": {"result": True}}}, - } - mock_response.raise_for_status = MagicMock() + with patch.object( + guardrail, "_create_background_noma_check" + ) as mock_create_background: + # Should return immediately without waiting for API call + result = await guardrail.async_pre_call_hook( + user_api_key_dict=mock_user_api_key_dict, + cache=MagicMock(), + data=mock_request_data, + call_type="completion", + ) - with patch.object(guardrail.async_handler, "post", return_value=mock_response): - # Should not raise exception in monitor mode + assert result == mock_request_data + # Verify background task was created + mock_create_background.assert_called_once() + + @pytest.mark.asyncio + async def test_pre_call_hook_monitor_mode_background_task_failure( + self, mock_user_api_key_dict, mock_request_data + ): + """Test pre-call hook in monitor mode when background task creation fails""" + guardrail = NomaGuardrail( + api_key="test-key", + monitor_mode=True, + guardrail_name="test-guardrail", + event_hook="pre_call", + default_on=True, + ) + + with patch.object( + guardrail, "_create_background_noma_check", side_effect=Exception("Task creation failed") + ): + # Should still return successfully even if background task creation fails result = await guardrail.async_pre_call_hook( user_api_key_dict=mock_user_api_key_dict, cache=MagicMock(), @@ -457,6 +479,291 @@ class TestNomaGuardrailHooks: assert message is None +class TestBackgroundProcessing: + """Test the new background processing functionality""" + + @pytest.fixture + def monitor_mode_guardrail(self): + """Create a guardrail with monitor mode enabled""" + return NomaGuardrail( + api_key="test-api-key", + api_base="https://api.test.noma.security/", + application_id="test-app", + monitor_mode=True, # Enable monitor mode + block_failures=True, + guardrail_name="test-noma-guardrail", + event_hook="pre_call", + default_on=True, + ) + + @pytest.mark.asyncio + async def test_process_user_message_check_monitor_mode( + self, monitor_mode_guardrail, mock_user_api_key_dict, mock_request_data + ): + """Test shared helper method in monitor mode""" + mock_response = MagicMock() + mock_response.json.return_value = { + "verdict": False, + "originalResponse": {"prompt": {"harmfulContent": {"result": True}}}, + } + mock_response.raise_for_status = MagicMock() + + with patch.object( + monitor_mode_guardrail.async_handler, "post", return_value=mock_response + ) as mock_post: + with patch.object( + monitor_mode_guardrail, "_handle_verdict_background" + ) as mock_handle_verdict: + result = await monitor_mode_guardrail._process_user_message_check( + mock_request_data, mock_user_api_key_dict + ) + + assert result == "Hello, how are you?" + mock_post.assert_called_once() + mock_handle_verdict.assert_called_once_with( + "user", "Hello, how are you?", mock_response.json.return_value + ) + + @pytest.mark.asyncio + async def test_process_user_message_check_non_monitor_mode( + self, noma_guardrail, mock_user_api_key_dict, mock_request_data + ): + """Test shared helper method in non-monitor mode""" + mock_response = MagicMock() + mock_response.json.return_value = {"verdict": True} + mock_response.raise_for_status = MagicMock() + + with patch.object( + noma_guardrail.async_handler, "post", return_value=mock_response + ) as mock_post: + with patch.object( + noma_guardrail, "_check_verdict" + ) as mock_check_verdict: + result = await noma_guardrail._process_user_message_check( + mock_request_data, mock_user_api_key_dict + ) + + assert result == "Hello, how are you?" + mock_post.assert_called_once() + mock_check_verdict.assert_called_once_with( + "user", "Hello, how are you?", mock_response.json.return_value + ) + + @pytest.mark.asyncio + async def test_process_llm_response_check_monitor_mode( + self, monitor_mode_guardrail, mock_user_api_key_dict, mock_request_data + ): + """Test LLM response processing in monitor mode""" + from litellm.types.utils import Choices, Message + + response = ModelResponse( + id="test-response-id", + choices=[ + Choices( + finish_reason="stop", + index=0, + message=Message( + content="I'm doing well, thank you!", role="assistant" + ), + ) + ], + created=1234567890, + model="gpt-3.5-turbo", + object="chat.completion", + system_fingerprint=None, + usage={"prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30}, + ) + + mock_api_response = MagicMock() + mock_api_response.json.return_value = {"verdict": True} + mock_api_response.raise_for_status = MagicMock() + + with patch.object( + monitor_mode_guardrail.async_handler, "post", return_value=mock_api_response + ) as mock_post: + with patch.object( + monitor_mode_guardrail, "_handle_verdict_background" + ) as mock_handle_verdict: + result = await monitor_mode_guardrail._process_llm_response_check( + mock_request_data, response, mock_user_api_key_dict + ) + + assert result == "I'm doing well, thank you!" + mock_post.assert_called_once() + mock_handle_verdict.assert_called_once_with( + "assistant", "I'm doing well, thank you!", mock_api_response.json.return_value + ) + + @pytest.mark.asyncio + async def test_check_user_message_background( + self, monitor_mode_guardrail, mock_user_api_key_dict, mock_request_data + ): + """Test background user message check method""" + with patch.object( + monitor_mode_guardrail, "_process_user_message_check" + ) as mock_process: + await monitor_mode_guardrail._check_user_message_background( + mock_request_data, mock_user_api_key_dict + ) + + mock_process.assert_called_once_with(mock_request_data, mock_user_api_key_dict) + + @pytest.mark.asyncio + async def test_check_user_message_background_exception_handling( + self, monitor_mode_guardrail, mock_user_api_key_dict, mock_request_data + ): + """Test background user message check handles exceptions gracefully""" + with patch.object( + monitor_mode_guardrail, "_process_user_message_check", + side_effect=Exception("API failed") + ): + # Should not raise exception, just log error + await monitor_mode_guardrail._check_user_message_background( + mock_request_data, mock_user_api_key_dict + ) + + @pytest.mark.asyncio + async def test_check_llm_response_background( + self, monitor_mode_guardrail, mock_user_api_key_dict, mock_request_data + ): + """Test background LLM response check method""" + from litellm.types.utils import Choices, Message + + response = ModelResponse( + id="test-response-id", + choices=[ + Choices( + finish_reason="stop", + index=0, + message=Message(content="Test response", role="assistant"), + ) + ], + created=1234567890, + model="gpt-3.5-turbo", + object="chat.completion", + system_fingerprint=None, + usage={"prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30}, + ) + + with patch.object( + monitor_mode_guardrail, "_process_llm_response_check" + ) as mock_process: + await monitor_mode_guardrail._check_llm_response_background( + mock_request_data, response, mock_user_api_key_dict + ) + + mock_process.assert_called_once_with( + mock_request_data, response, mock_user_api_key_dict + ) + + @pytest.mark.asyncio + async def test_handle_verdict_background_blocked(self, monitor_mode_guardrail): + """Test background verdict handling for blocked content""" + response_json = { + "verdict": False, + "originalResponse": {"prompt": {"harmfulContent": {"result": True}}}, + } + + with patch("litellm._logging.verbose_proxy_logger.warning") as mock_warning: + await monitor_mode_guardrail._handle_verdict_background( + "user", "test message", response_json + ) + + mock_warning.assert_called_once() + assert "blocked user message" in mock_warning.call_args[0][0] + + @pytest.mark.asyncio + async def test_handle_verdict_background_allowed(self, monitor_mode_guardrail): + """Test background verdict handling for allowed content""" + response_json = {"verdict": True} + + with patch("litellm._logging.verbose_proxy_logger.info") as mock_info: + await monitor_mode_guardrail._handle_verdict_background( + "assistant", "test response", response_json + ) + + mock_info.assert_called_once() + assert "allowed assistant message" in mock_info.call_args[0][0] + + @pytest.mark.asyncio + async def test_create_background_noma_check(self, monitor_mode_guardrail): + """Test background task creation""" + async def dummy_coroutine(): + return "completed" + + with patch("asyncio.create_task") as mock_create_task: + monitor_mode_guardrail._create_background_noma_check(dummy_coroutine()) + mock_create_task.assert_called_once() + + @pytest.mark.asyncio + async def test_create_background_noma_check_exception(self, monitor_mode_guardrail): + """Test background task creation with exception handling""" + async def dummy_coroutine(): + return "completed" + + with patch("asyncio.create_task", side_effect=Exception("Task creation failed")): + # Should not raise exception, just log error + monitor_mode_guardrail._create_background_noma_check(dummy_coroutine()) + + @pytest.mark.asyncio + async def test_moderation_hook_monitor_mode( + self, monitor_mode_guardrail, mock_user_api_key_dict, mock_request_data + ): + """Test moderation hook in monitor mode""" + # Update event hook to during_call + monitor_mode_guardrail.event_hook = "during_call" + + with patch.object( + monitor_mode_guardrail, "_create_background_noma_check" + ) as mock_create_background: + result = await monitor_mode_guardrail.async_moderation_hook( + data=mock_request_data, + user_api_key_dict=mock_user_api_key_dict, + call_type="completion", + ) + + assert result == mock_request_data + mock_create_background.assert_called_once() + + @pytest.mark.asyncio + async def test_post_call_success_hook_monitor_mode( + self, monitor_mode_guardrail, mock_user_api_key_dict, mock_request_data + ): + """Test post-call success hook in monitor mode""" + from litellm.types.utils import Choices, Message + + # Update event hook to post_call + monitor_mode_guardrail.event_hook = "post_call" + + response = ModelResponse( + id="test-response-id", + choices=[ + Choices( + finish_reason="stop", + index=0, + message=Message(content="Test response", role="assistant"), + ) + ], + created=1234567890, + model="gpt-3.5-turbo", + object="chat.completion", + system_fingerprint=None, + usage={"prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30}, + ) + + with patch.object( + monitor_mode_guardrail, "_create_background_noma_check" + ) as mock_create_background: + result = await monitor_mode_guardrail.async_post_call_success_hook( + data=mock_request_data, + user_api_key_dict=mock_user_api_key_dict, + response=response, + ) + + assert result == response + mock_create_background.assert_called_once() + + class TestIntegration: @pytest.mark.asyncio async def test_full_guardrail_flow(self): From fc2e83587a37673c048a77bc3a1f301504fdc036 Mon Sep 17 00:00:00 2001 From: Tom Alon Date: Wed, 10 Sep 2025 14:11:38 +0300 Subject: [PATCH 20/73] PR fixes --- .../guardrails/guardrail_hooks/noma/noma.py | 40 +++++++------------ 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/litellm/proxy/guardrails/guardrail_hooks/noma/noma.py b/litellm/proxy/guardrails/guardrail_hooks/noma/noma.py index 3bbd183e49b..1a1ed2acb1f 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/noma/noma.py +++ b/litellm/proxy/guardrails/guardrail_hooks/noma/noma.py @@ -8,7 +8,7 @@ import asyncio import copy import os -from typing import Any, Dict, Literal, Optional, Union +from typing import Any, Dict, Final, Literal, Optional, Union from urllib.parse import urljoin from fastapi import HTTPException @@ -25,6 +25,10 @@ from litellm.proxy._types import UserAPIKeyAuth from litellm.types.guardrails import GuardrailEventHooks from litellm.types.utils import EmbeddingResponse, ImageResponse +# Constants +USER_ROLE: Final[Literal["user"]] = "user" +ASSISTANT_ROLE: Final[Literal["assistant"]] = "assistant" + # Type aliases MessageRole = Literal["user", "assistant"] LLMResponse = Union[Any, ModelResponse, EmbeddingResponse, ImageResponse] @@ -203,9 +207,9 @@ class NomaGuardrail(CustomGuardrail): ) if self.monitor_mode: - await self._handle_verdict_background("user", user_message, response_json) + await self._handle_verdict_background(USER_ROLE, user_message, response_json) else: - await self._check_verdict("user", user_message, response_json) + await self._check_verdict(USER_ROLE, user_message, response_json) return user_message @@ -241,9 +245,9 @@ class NomaGuardrail(CustomGuardrail): ) if self.monitor_mode: - await self._handle_verdict_background("assistant", content, response_json) + await self._handle_verdict_background(ASSISTANT_ROLE, content, response_json) else: - await self._check_verdict("assistant", content, response_json) + await self._check_verdict(ASSISTANT_ROLE, content, response_json) return content @@ -283,18 +287,10 @@ class NomaGuardrail(CustomGuardrail): """Handle verdict from Noma API in background - logging only, never blocks""" try: if not response_json.get("verdict", True): - msg = str.format( - "Noma guardrail blocked {type} message: {message}", - type=type, - message=message, - ) + msg = f"Noma guardrail blocked {type} message: {message}" verbose_proxy_logger.warning(msg) else: - msg = str.format( - "Noma guardrail allowed {type} message: {message}", - type=type, - message=message, - ) + msg = f"Noma guardrail allowed {type} message: {message}" verbose_proxy_logger.info(msg) except Exception as e: verbose_proxy_logger.error( @@ -460,7 +456,7 @@ class NomaGuardrail(CustomGuardrail): return None # Get the last user message - user_messages = [msg for msg in messages if msg.get("role") == "user"] + user_messages = [msg for msg in messages if msg.get("role") == USER_ROLE] if not user_messages: return None @@ -523,11 +519,7 @@ class NomaGuardrail(CustomGuardrail): Check the verdict from the Noma API and raise an exception if needed """ if not response_json.get("verdict", True): - msg = str.format( - "Noma guardrail blocked {type} message: {message}", - type=type, - message=message, - ) + msg = f"Noma guardrail blocked {type} message: {message}" if self.monitor_mode: verbose_proxy_logger.warning(msg) @@ -536,11 +528,7 @@ class NomaGuardrail(CustomGuardrail): original_response = response_json.get("originalResponse", {}) raise NomaBlockedMessage(original_response) else: - msg = str.format( - "Noma guardrail allowed {type} message: {message}", - type=type, - message=message, - ) + msg = f"Noma guardrail allowed {type} message: {message}" if self.monitor_mode: verbose_proxy_logger.info(msg) else: From 2668112a18071b8813d2575e27539e0775de58bd Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Wed, 10 Sep 2025 08:48:37 -0700 Subject: [PATCH 21/73] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c8a073432c9..df2350b6c9e 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Discord - + Slack @@ -408,7 +408,7 @@ All these checks must pass before your PR can be merged. - [Schedule Demo 👋](https://calendly.com/d/4mp-gd3-k5k/berriai-1-1-onboarding-litellm-hosted-version) - [Community Discord 💭](https://discord.gg/wuPM9dRgDw) -- [Community Slack 💭](https://join.slack.com/share/enQtOTE0ODczMzk2Nzk4NC01YjUxNjY2YjBlYTFmNDRiZTM3NDFiYTM3MzVkODFiMDVjOGRjMmNmZTZkZTMzOWQzZGQyZWIwYjQ0MWExYmE3) +- [Community Slack 💭](https://www.litellm.ai/support) - Our numbers 📞 +1 (770) 8783-106 / ‭+1 (412) 618-6238‬ - Our emails ✉️ ishaan@berri.ai / krrish@berri.ai From f3c5a59e5700e6db01b6aaf4642aa7ca6594ff97 Mon Sep 17 00:00:00 2001 From: Arseny Boykov <36469655+Bobronium@users.noreply.github.com> Date: Thu, 11 Sep 2025 00:46:27 +0200 Subject: [PATCH 22/73] Revert "Use _PROXY_MaxParallelRequestsHandler_v3 by default (#14352)" (#14420) This reverts commit 5b680bb4a350b8261b24df7ce806fdcda9c88807. --- litellm/proxy/hooks/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/hooks/__init__.py b/litellm/proxy/hooks/__init__.py index 467565d748a..83d7e173431 100644 --- a/litellm/proxy/hooks/__init__.py +++ b/litellm/proxy/hooks/__init__.py @@ -17,13 +17,13 @@ except ImportError: # List of all available hooks that can be enabled PROXY_HOOKS = { "max_budget_limiter": _PROXY_MaxBudgetLimiter, - "parallel_request_limiter": _PROXY_MaxParallelRequestsHandler_v3, + "parallel_request_limiter": _PROXY_MaxParallelRequestsHandler, "cache_control_check": _PROXY_CacheControlCheck, } ## FEATURE FLAG HOOKS ## -if os.getenv("LEGACY_MULTI_INSTANCE_RATE_LIMITING", "false").lower() == "true": - PROXY_HOOKS["parallel_request_limiter"] = _PROXY_MaxParallelRequestsHandler +if os.getenv("EXPERIMENTAL_MULTI_INSTANCE_RATE_LIMITING", "false").lower() == "true": + PROXY_HOOKS["parallel_request_limiter"] = _PROXY_MaxParallelRequestsHandler_v3 ### update PROXY_HOOKS with ENTERPRISE_PROXY_HOOKS ### From dc5650eedaafe2e215cb9b9ddf67fc937399cb57 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 10 Sep 2025 15:47:18 -0700 Subject: [PATCH 23/73] Revert "fix: remove anthropic-beta header for Vertex AI requests with prompt caching" (#14421) --- litellm/llms/anthropic/common_utils.py | 11 +- .../test_vertex_ai_prompt_caching_fix.py | 134 ------------------ .../llms/vertex_ai/test_vertex.py | 2 +- 3 files changed, 3 insertions(+), 144 deletions(-) delete mode 100644 tests/litellm/llms/anthropic/test_vertex_ai_prompt_caching_fix.py diff --git a/litellm/llms/anthropic/common_utils.py b/litellm/llms/anthropic/common_utils.py index 06ebb5079d9..68b5341e954 100644 --- a/litellm/llms/anthropic/common_utils.py +++ b/litellm/llms/anthropic/common_utils.py @@ -107,10 +107,8 @@ class AnthropicModelInfo(BaseLLMModelInfo): user_anthropic_beta_headers: Optional[List[str]] = None, ) -> dict: betas = set() - # Note: prompt-caching-2024-07-31 header is no longer required for prompt caching - # as per current Anthropic documentation. It's now generally available. - # if prompt_caching_set: - # betas.add("prompt-caching-2024-07-31") + if prompt_caching_set: + betas.add("prompt-caching-2024-07-31") if computer_tool_used: betas.add("computer-use-2024-10-22") # if pdf_used: @@ -178,11 +176,6 @@ class AnthropicModelInfo(BaseLLMModelInfo): mcp_server_used=mcp_server_used, ) - # For Vertex AI requests, remove any user-provided anthropic-beta headers - # since Vertex AI rejects them and they're no longer required for prompt caching - if optional_params.get("is_vertex_request", False): - headers = {k: v for k, v in headers.items() if k != "anthropic-beta"} - headers = {**headers, **anthropic_headers} return headers diff --git a/tests/litellm/llms/anthropic/test_vertex_ai_prompt_caching_fix.py b/tests/litellm/llms/anthropic/test_vertex_ai_prompt_caching_fix.py deleted file mode 100644 index 67c37bc6f26..00000000000 --- a/tests/litellm/llms/anthropic/test_vertex_ai_prompt_caching_fix.py +++ /dev/null @@ -1,134 +0,0 @@ -""" -Test file for Vertex AI prompt caching fix. - -This test verifies that: -1. The anthropic-beta header is removed for Vertex AI requests -2. Regular Anthropic requests still work correctly -3. Prompt caching detection logic is preserved -""" - -import unittest -from unittest.mock import patch, MagicMock -from litellm.llms.anthropic.common_utils import AnthropicModelInfo - - -class TestVertexAIPromptCachingFix(unittest.TestCase): - """Test cases for the Vertex AI prompt caching fix.""" - - def setUp(self): - """Set up test fixtures.""" - self.model_info = AnthropicModelInfo() - - def _is_cache_control_set(self, messages): - """Helper method to test cache control detection.""" - for message in messages: - if message.get("cache_control", None) is not None: - return True - _message_content = message.get("content") - if _message_content is not None and isinstance(_message_content, list): - for content in _message_content: - if "cache_control" in content: - return True - return False - - def test_vertex_ai_removes_anthropic_beta_header(self): - """Test that anthropic-beta header is removed for Vertex AI requests.""" - # Mock the get_anthropic_headers method - with patch.object(self.model_info, 'get_anthropic_headers') as mock_get_headers: - # Set up the mock to return headers with anthropic-beta - mock_get_headers.return_value = { - 'anthropic-beta': 'prompt-caching-2024-07-31', - 'content-type': 'application/json' - } - - # Test with Vertex AI request - optional_params = {'is_vertex_request': True} - headers = self.model_info.get_anthropic_headers( - model='vertex/claude-3-5-sonnet-20240620', - messages=[], - optional_params=optional_params - ) - - # Verify anthropic-beta header is removed - self.assertNotIn('anthropic-beta', headers) - - def test_regular_anthropic_preserves_anthropic_beta_header(self): - """Test that regular Anthropic requests preserve anthropic-beta header.""" - # Mock the get_anthropic_headers method - with patch.object(self.model_info, 'get_anthropic_headers') as mock_get_headers: - # Set up the mock to return headers with anthropic-beta - mock_get_headers.return_value = { - 'anthropic-beta': 'prompt-caching-2024-07-31', - 'content-type': 'application/json' - } - - # Test with regular Anthropic request (not Vertex AI) - optional_params = {'is_vertex_request': False} - headers = self.model_info.get_anthropic_headers( - model='claude-3-5-sonnet-20240620', - messages=[], - optional_params=optional_params - ) - - # Verify anthropic-beta header is preserved - self.assertIn('anthropic-beta', headers) - - def test_prompt_caching_detection_still_works(self): - """Test that prompt caching detection logic still works.""" - # Test messages with cache_control - messages_with_cache = [ - { - 'role': 'user', - 'content': [ - { - 'type': 'text', - 'text': 'Test prompt', - 'cache_control': {'type': 'ephemeral'} - } - ] - } - ] - - # Test messages without cache_control - messages_without_cache = [ - { - 'role': 'user', - 'content': [ - { - 'type': 'text', - 'text': 'Regular prompt' - } - ] - } - ] - - # Test cache detection - cache_detected_with = self._is_cache_control_set(messages_with_cache) - cache_detected_without = self._is_cache_control_set(messages_without_cache) - - self.assertTrue(cache_detected_with) - self.assertFalse(cache_detected_without) - - def test_vertex_ai_without_user_beta_header(self): - """Test Vertex AI request when no user-provided beta header exists.""" - # Mock the get_anthropic_headers method - with patch.object(self.model_info, 'get_anthropic_headers') as mock_get_headers: - # Set up the mock to return headers without anthropic-beta - mock_get_headers.return_value = { - 'content-type': 'application/json' - } - - # Test with Vertex AI request - optional_params = {'is_vertex_request': True} - headers = self.model_info.get_anthropic_headers( - model='vertex/claude-3-5-sonnet-20240620', - messages=[], - optional_params=optional_params - ) - - # Verify no anthropic-beta header is present - self.assertNotIn('anthropic-beta', headers) - - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/tests/test_litellm/llms/vertex_ai/test_vertex.py b/tests/test_litellm/llms/vertex_ai/test_vertex.py index 02bd622016b..7e683d1f54e 100644 --- a/tests/test_litellm/llms/vertex_ai/test_vertex.py +++ b/tests/test_litellm/llms/vertex_ai/test_vertex.py @@ -10,7 +10,7 @@ import litellm.litellm_core_utils.prompt_templates import litellm.litellm_core_utils.prompt_templates.factory load_dotenv() -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock sys.path.insert( 0, os.path.abspath("../..") From d544c6e595ec0254b0d35e4d99ce23cae1524138 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 10 Sep 2025 15:52:29 -0700 Subject: [PATCH 24/73] fix DatabricksFunction --- ...odel_prices_and_context_window_backup.json | 542 +----------------- litellm/types/llms/databricks.py | 2 +- 2 files changed, 24 insertions(+), 520 deletions(-) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 168cbeeade0..ff7b6b36dc8 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -15389,16 +15389,6 @@ "litellm_provider": "ollama", "mode": "completion" }, - "deepinfra/Austism/chronos-hermes-13b-v2": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 1.3e-07, - "output_cost_per_token": 1.3e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, "deepinfra/Gryphe/MythoMax-L2-13b": { "max_tokens": 4096, "max_input_tokens": 4096, @@ -15409,26 +15399,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/Gryphe/MythoMax-L2-13b-turbo": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 1.3e-07, - "output_cost_per_token": 1.3e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/KoboldAI/LLaMA2-13B-Tiefighter": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 1e-07, - "output_cost_per_token": 1e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, "deepinfra/NousResearch/Hermes-3-Llama-3.1-405B": { "max_tokens": 131072, "max_input_tokens": 131072, @@ -15447,78 +15417,18 @@ "output_cost_per_token": 2.8e-07, "litellm_provider": "deepinfra", "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/NovaSky-AI/Sky-T1-32B-Preview": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 1.2e-07, - "output_cost_per_token": 1.8e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/Phind/Phind-CodeLlama-34B-v2": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 6e-07, - "output_cost_per_token": 6e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/Qwen/QVQ-72B-Preview": { - "max_tokens": 32000, - "max_input_tokens": 32000, - "max_output_tokens": 32000, - "input_cost_per_token": 2.5e-07, - "output_cost_per_token": 5e-07, - "litellm_provider": "deepinfra", - "mode": "chat", "supports_tool_choice": false }, "deepinfra/Qwen/QwQ-32B": { "max_tokens": 131072, "max_input_tokens": 131072, "max_output_tokens": 131072, - "input_cost_per_token": 7.5e-08, - "output_cost_per_token": 1.5e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/Qwen/QwQ-32B-Preview": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 1.2e-07, - "output_cost_per_token": 1.8e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/Qwen/Qwen2-72B-Instruct": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 3.5e-07, + "input_cost_per_token": 1.5e-07, "output_cost_per_token": 4e-07, "litellm_provider": "deepinfra", "mode": "chat", "supports_tool_choice": true }, - "deepinfra/Qwen/Qwen2-7B-Instruct": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 5.5e-08, - "output_cost_per_token": 5.5e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, "deepinfra/Qwen/Qwen2.5-72B-Instruct": { "max_tokens": 32768, "max_input_tokens": 32768, @@ -15539,26 +15449,6 @@ "mode": "chat", "supports_tool_choice": false }, - "deepinfra/Qwen/Qwen2.5-Coder-32B-Instruct": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 6e-08, - "output_cost_per_token": 1.5e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/Qwen/Qwen2.5-Coder-7B": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 2.5e-08, - "output_cost_per_token": 5e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, "deepinfra/Qwen/Qwen2.5-VL-32B-Instruct": { "max_tokens": 128000, "max_input_tokens": 128000, @@ -15645,30 +15535,11 @@ "max_output_tokens": 262144, "input_cost_per_token": 3e-07, "output_cost_per_token": 1.2e-06, + "cache_read_input_token_cost": 2.4e-07, "litellm_provider": "deepinfra", "mode": "chat", "supports_tool_choice": true }, - "deepinfra/Sao10K/L3-70B-Euryale-v2.1": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 7e-07, - "output_cost_per_token": 8e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/Sao10K/L3-8B-Lunaris-v1": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 3e-08, - "output_cost_per_token": 6e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, "deepinfra/Sao10K/L3-8B-Lunaris-v1-Turbo": { "max_tokens": 8192, "max_input_tokens": 8192, @@ -15715,6 +15586,7 @@ "max_output_tokens": 200000, "input_cost_per_token": 3.3e-06, "output_cost_per_token": 1.65e-05, + "cache_read_input_token_cost": 3.3e-07, "litellm_provider": "deepinfra", "mode": "chat", "supports_tool_choice": true @@ -15739,67 +15611,15 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/bigcode/starcoder2-15b-instruct-v0.1": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 1.5e-07, - "output_cost_per_token": 1.5e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/cognitivecomputations/dolphin-2.6-mixtral-8x7b": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 2.4e-07, - "output_cost_per_token": 2.4e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/cognitivecomputations/dolphin-2.9.1-llama-3-70b": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 3.5e-07, - "output_cost_per_token": 4e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/deepinfra/airoboros-70b": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 7e-07, - "output_cost_per_token": 9e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/deepseek-ai/DeepSeek-Prover-V2-671B": { - "max_tokens": 163840, - "max_input_tokens": 163840, - "max_output_tokens": 163840, - "input_cost_per_token": 5e-07, - "output_cost_per_token": 2.18e-06, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false, - "supports_reasoning": true - }, "deepinfra/deepseek-ai/DeepSeek-R1": { "max_tokens": 163840, "max_input_tokens": 163840, "max_output_tokens": 163840, - "input_cost_per_token": 4.5e-07, - "output_cost_per_token": 2.15e-06, + "input_cost_per_token": 7e-07, + "output_cost_per_token": 2.4e-06, "litellm_provider": "deepinfra", "mode": "chat", - "supports_tool_choice": true, - "supports_reasoning": true + "supports_tool_choice": true }, "deepinfra/deepseek-ai/DeepSeek-R1-0528": { "max_tokens": 163840, @@ -15807,10 +15627,10 @@ "max_output_tokens": 163840, "input_cost_per_token": 5e-07, "output_cost_per_token": 2.15e-06, + "cache_read_input_token_cost": 4e-07, "litellm_provider": "deepinfra", "mode": "chat", - "supports_tool_choice": true, - "supports_reasoning": true + "supports_tool_choice": true }, "deepinfra/deepseek-ai/DeepSeek-R1-0528-Turbo": { "max_tokens": 32768, @@ -15820,8 +15640,7 @@ "output_cost_per_token": 3e-06, "litellm_provider": "deepinfra", "mode": "chat", - "supports_tool_choice": true, - "supports_reasoning": true + "supports_tool_choice": true }, "deepinfra/deepseek-ai/DeepSeek-R1-Distill-Llama-70B": { "max_tokens": 131072, @@ -15831,8 +15650,7 @@ "output_cost_per_token": 4e-07, "litellm_provider": "deepinfra", "mode": "chat", - "supports_tool_choice": false, - "supports_reasoning": true + "supports_tool_choice": false }, "deepinfra/deepseek-ai/DeepSeek-R1-Distill-Qwen-32B": { "max_tokens": 131072, @@ -15842,19 +15660,17 @@ "output_cost_per_token": 1.5e-07, "litellm_provider": "deepinfra", "mode": "chat", - "supports_tool_choice": true, - "supports_reasoning": true + "supports_tool_choice": true }, "deepinfra/deepseek-ai/DeepSeek-R1-Turbo": { - "max_tokens": 163840, - "max_input_tokens": 163840, - "max_output_tokens": 163840, + "max_tokens": 40960, + "max_input_tokens": 40960, + "max_output_tokens": 40960, "input_cost_per_token": 1e-06, "output_cost_per_token": 3e-06, "litellm_provider": "deepinfra", "mode": "chat", - "supports_tool_choice": true, - "supports_reasoning": true + "supports_tool_choice": true }, "deepinfra/deepseek-ai/DeepSeek-V3": { "max_tokens": 163840, @@ -15864,8 +15680,7 @@ "output_cost_per_token": 8.9e-07, "litellm_provider": "deepinfra", "mode": "chat", - "supports_tool_choice": true, - "supports_reasoning": true + "supports_tool_choice": true }, "deepinfra/deepseek-ai/DeepSeek-V3-0324": { "max_tokens": 163840, @@ -15873,59 +15688,18 @@ "max_output_tokens": 163840, "input_cost_per_token": 2.8e-07, "output_cost_per_token": 8.8e-07, + "cache_read_input_token_cost": 2.24e-07, "litellm_provider": "deepinfra", "mode": "chat", - "supports_tool_choice": true, - "supports_reasoning": true - }, - "deepinfra/deepseek-ai/DeepSeek-V3-0324-Turbo": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 1e-06, - "output_cost_per_token": 3e-06, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true, - "supports_reasoning": true + "supports_tool_choice": true }, "deepinfra/deepseek-ai/DeepSeek-V3.1": { "max_tokens": 163840, "max_input_tokens": 163840, "max_output_tokens": 163840, - "input_cost_per_token": 3e-07, + "input_cost_per_token": 2.7e-07, "output_cost_per_token": 1e-06, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false, - "supports_reasoning": true - }, - "deepinfra/google/codegemma-7b-it": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 7e-08, - "output_cost_per_token": 7e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/google/gemini-1.5-flash": { - "max_tokens": 1000000, - "max_input_tokens": 1000000, - "max_output_tokens": 1000000, - "input_cost_per_token": 7.5e-08, - "output_cost_per_token": 3e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/google/gemini-1.5-flash-8b": { - "max_tokens": 1000000, - "max_input_tokens": 1000000, - "max_output_tokens": 1000000, - "input_cost_per_token": 3.75e-08, - "output_cost_per_token": 1.5e-07, + "cache_read_input_token_cost": 2.16e-07, "litellm_provider": "deepinfra", "mode": "chat", "supports_tool_choice": true @@ -15960,36 +15734,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/google/gemma-1.1-7b-it": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 7e-08, - "output_cost_per_token": 7e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/google/gemma-2-27b-it": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 2.7e-07, - "output_cost_per_token": 2.7e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/google/gemma-2-9b-it": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 3e-08, - "output_cost_per_token": 6e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, "deepinfra/google/gemma-3-12b-it": { "max_tokens": 131072, "max_input_tokens": 131072, @@ -16014,48 +15758,8 @@ "max_tokens": 131072, "max_input_tokens": 131072, "max_output_tokens": 131072, - "input_cost_per_token": 2e-08, - "output_cost_per_token": 4e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/lizpreciatior/lzlv_70b_fp16_hf": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 3.5e-07, - "output_cost_per_token": 4e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/mattshumer/Reflection-Llama-3.1-70B": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 3.5e-07, - "output_cost_per_token": 4e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/meta-llama/Llama-2-13b-chat-hf": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 1.3e-07, - "output_cost_per_token": 1.3e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/meta-llama/Llama-2-70b-chat-hf": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 6.4e-07, - "output_cost_per_token": 8e-07, + "input_cost_per_token": 4e-08, + "output_cost_per_token": 8e-08, "litellm_provider": "deepinfra", "mode": "chat", "supports_tool_choice": true @@ -16070,16 +15774,6 @@ "mode": "chat", "supports_tool_choice": false }, - "deepinfra/meta-llama/Llama-3.2-1B-Instruct": { - "max_tokens": 131072, - "max_input_tokens": 131072, - "max_output_tokens": 131072, - "input_cost_per_token": 5e-09, - "output_cost_per_token": 1e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, "deepinfra/meta-llama/Llama-3.2-3B-Instruct": { "max_tokens": 131072, "max_input_tokens": 131072, @@ -16090,16 +15784,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/meta-llama/Llama-3.2-90B-Vision-Instruct": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 3.5e-07, - "output_cost_per_token": 4e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, "deepinfra/meta-llama/Llama-3.3-70B-Instruct": { "max_tokens": 131072, "max_input_tokens": 131072, @@ -16130,16 +15814,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/meta-llama/Llama-4-Maverick-17B-128E-Instruct-Turbo": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 5e-07, - "output_cost_per_token": 5e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, "deepinfra/meta-llama/Llama-4-Scout-17B-16E-Instruct": { "max_tokens": 327680, "max_input_tokens": 327680, @@ -16170,16 +15844,6 @@ "mode": "chat", "supports_tool_choice": false }, - "deepinfra/meta-llama/Meta-Llama-3-70B-Instruct": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 3e-07, - "output_cost_per_token": 4e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, "deepinfra/meta-llama/Meta-Llama-3-8B-Instruct": { "max_tokens": 8192, "max_input_tokens": 8192, @@ -16190,16 +15854,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/meta-llama/Meta-Llama-3.1-405B-Instruct": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 8e-07, - "output_cost_per_token": 8e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, "deepinfra/meta-llama/Meta-Llama-3.1-70B-Instruct": { "max_tokens": 131072, "max_input_tokens": 131072, @@ -16240,36 +15894,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/microsoft/Phi-3-medium-4k-instruct": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 1.4e-07, - "output_cost_per_token": 1.4e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/microsoft/Phi-4-multimodal-instruct": { - "max_tokens": 131072, - "max_input_tokens": 131072, - "max_output_tokens": 131072, - "input_cost_per_token": 5e-08, - "output_cost_per_token": 1e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/microsoft/WizardLM-2-7B": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 5.5e-08, - "output_cost_per_token": 5.5e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, "deepinfra/microsoft/WizardLM-2-8x22B": { "max_tokens": 65536, "max_input_tokens": 65536, @@ -16290,66 +15914,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/microsoft/phi-4-reasoning-plus": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 7e-08, - "output_cost_per_token": 3.5e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/mistralai/Devstral-Small-2505": { - "max_tokens": 128000, - "max_input_tokens": 128000, - "max_output_tokens": 128000, - "input_cost_per_token": 6e-08, - "output_cost_per_token": 1.2e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/mistralai/Devstral-Small-2507": { - "max_tokens": 128000, - "max_input_tokens": 128000, - "max_output_tokens": 128000, - "input_cost_per_token": 7e-08, - "output_cost_per_token": 2.8e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/mistralai/Mistral-7B-Instruct-v0.1": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 5.5e-08, - "output_cost_per_token": 5.5e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, - "deepinfra/mistralai/Mistral-7B-Instruct-v0.2": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 5.5e-08, - "output_cost_per_token": 5.5e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/mistralai/Mistral-7B-Instruct-v0.3": { - "max_tokens": 32768, - "max_input_tokens": 32768, - "max_output_tokens": 32768, - "input_cost_per_token": 2.8e-08, - "output_cost_per_token": 5.4e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, "deepinfra/mistralai/Mistral-Nemo-Instruct-2407": { "max_tokens": 131072, "max_input_tokens": 131072, @@ -16370,16 +15934,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/mistralai/Mistral-Small-3.1-24B-Instruct-2503": { - "max_tokens": 128000, - "max_input_tokens": 128000, - "max_output_tokens": 128000, - "input_cost_per_token": 5e-08, - "output_cost_per_token": 1e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, "deepinfra/mistralai/Mistral-Small-3.2-24B-Instruct-2506": { "max_tokens": 128000, "max_input_tokens": 128000, @@ -16390,16 +15944,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/mistralai/Mixtral-8x22B-Instruct-v0.1": { - "max_tokens": 65536, - "max_input_tokens": 65536, - "max_output_tokens": 65536, - "input_cost_per_token": 6.5e-07, - "output_cost_per_token": 6.5e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, "deepinfra/mistralai/Mixtral-8x7B-Instruct-v0.1": { "max_tokens": 32768, "max_input_tokens": 32768, @@ -16430,16 +15974,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/nvidia/Nemotron-4-340B-Instruct": { - "max_tokens": 4096, - "max_input_tokens": 4096, - "max_output_tokens": 4096, - "input_cost_per_token": 4.2e-06, - "output_cost_per_token": 4.2e-06, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, "deepinfra/openai/gpt-oss-120b": { "max_tokens": 131072, "max_input_tokens": 131072, @@ -16460,36 +15994,6 @@ "mode": "chat", "supports_tool_choice": true }, - "deepinfra/openbmb/MiniCPM-Llama3-V-2_5": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 3.4e-07, - "output_cost_per_token": 3.4e-07, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/openchat/openchat-3.6-8b": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 5.5e-08, - "output_cost_per_token": 5.5e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": false - }, - "deepinfra/openchat/openchat_3.5": { - "max_tokens": 8192, - "max_input_tokens": 8192, - "max_output_tokens": 8192, - "input_cost_per_token": 5.5e-08, - "output_cost_per_token": 5.5e-08, - "litellm_provider": "deepinfra", - "mode": "chat", - "supports_tool_choice": true - }, "deepinfra/zai-org/GLM-4.5": { "max_tokens": 131072, "max_input_tokens": 131072, @@ -20998,4 +20502,4 @@ "notes": "Volcengine Doubao embedding model - text-240715 version with 2560 dimensions" } } -} \ No newline at end of file +} diff --git a/litellm/types/llms/databricks.py b/litellm/types/llms/databricks.py index c362d065694..c484e80ada3 100644 --- a/litellm/types/llms/databricks.py +++ b/litellm/types/llms/databricks.py @@ -51,7 +51,7 @@ AllDatabricksContentValues = Union[str, List[AllDatabricksContentListValues]] class DatabricksFunction(TypedDict, total=False): name: Required[str] - description: dict | str + description: Union[dict, str] parameters: dict strict: bool From 199c262e33d0ff335cb1b113cc891d46cd5e3293 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 10 Sep 2025 15:55:27 -0700 Subject: [PATCH 25/73] EXPERIMENTAL_MULTI_INSTANCE_RATE_LIMITING --- docs/my-website/docs/proxy/config_settings.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/my-website/docs/proxy/config_settings.md b/docs/my-website/docs/proxy/config_settings.md index 82669b10cd5..ee857f700a3 100644 --- a/docs/my-website/docs/proxy/config_settings.md +++ b/docs/my-website/docs/proxy/config_settings.md @@ -473,6 +473,7 @@ router_settings: | EMAIL_SIGNATURE | Custom HTML footer/signature for all emails. Can include HTML tags for formatting and links. | EMAIL_SUBJECT_INVITATION | Custom subject template for invitation emails. | EMAIL_SUBJECT_KEY_CREATED | Custom subject template for key creation emails. +| EXPERIMENTAL_MULTI_INSTANCE_RATE_LIMITING | Flag to enable new multi-instance rate limiting. **Default is False** | FIREWORKS_AI_4_B | Size parameter for Fireworks AI 4B model. Default is 4 | FIREWORKS_AI_16_B | Size parameter for Fireworks AI 16B model. Default is 16 | FIREWORKS_AI_56_B_MOE | Size parameter for Fireworks AI 56B MOE model. Default is 56 From d78ed53cbbeabdfa945abcfd9f4cb78105a177f9 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 10 Sep 2025 16:17:22 -0700 Subject: [PATCH 26/73] fix mypy linting --- .../llms/databricks/chat/transformation.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/litellm/llms/databricks/chat/transformation.py b/litellm/llms/databricks/chat/transformation.py index 0f3530f85da..cda372470a4 100644 --- a/litellm/llms/databricks/chat/transformation.py +++ b/litellm/llms/databricks/chat/transformation.py @@ -169,13 +169,14 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): if tool is None: return None - kwags = { - "name":tool["name"], - "parameters":cast(dict, tool.get("input_schema") or {}) + kwags: dict = { + "name": tool["name"], + "parameters": cast(dict, tool.get("input_schema") or {}) } - if tool.get("description"): - kwags["description"] = tool.get("description") + description = tool.get("description") + if description is not None: + kwags["description"] = cast(Union[dict, str], description) return DatabricksTool( type="function", @@ -336,8 +337,8 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): elif isinstance(content, list): content_str = "" for item in content: - if item["type"] == "text": - content_str += item["text"] + if item.get("type") == "text": + content_str += item.get("text", "") return content_str else: raise Exception(f"Unsupported content type: {type(content)}") @@ -366,8 +367,9 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): reasoning_content: Optional[str] = None if isinstance(content, list): for item in content: - if item["type"] == "reasoning": - for sum in item["summary"]: + if item.get("type") == "reasoning": + summary_list = item.get("summary", []) + for sum in summary_list: if reasoning_content is None: reasoning_content = "" reasoning_content += sum["text"] From 1f42e41c8d46bc2d60caf0e3627b034509c6e391 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 10 Sep 2025 16:41:08 -0700 Subject: [PATCH 27/73] [Bug]: Fix Authorization header not being sent to configured MCP servers (#14422) * test: test_mcp_server_config_auth_value_header_used * fix: authentication_token * docs: fix instructions on using responses api with MCPs * mcp fixes --- docs/my-website/docs/mcp.md | 150 ++++++++++++++---- docs/my-website/img/mcp_tools.png | Bin 0 -> 221493 bytes .../mcp_server/mcp_server_manager.py | 5 +- tests/mcp_tests/test_mcp_auth_priority.py | 23 +++ 4 files changed, 143 insertions(+), 35 deletions(-) create mode 100644 docs/my-website/img/mcp_tools.png diff --git a/docs/my-website/docs/mcp.md b/docs/my-website/docs/mcp.md index 45cec48cd7e..1e523600a86 100644 --- a/docs/my-website/docs/mcp.md +++ b/docs/my-website/docs/mcp.md @@ -195,70 +195,155 @@ litellm_settings: ## Using your MCP +### Use on LiteLLM UI + +### Use with Responses API + +Replace `http://localhost:4000` with your LiteLLM Proxy base URL. + - - -#### Connect via OpenAI Responses API - -Use the OpenAI Responses API to connect to your LiteLLM MCP server: + ```bash title="cURL Example" showLineNumbers -curl --location 'https://api.openai.com/v1/responses' \ +curl --location 'http://localhost:4000/v1/responses' \ --header 'Content-Type: application/json' \ ---header "Authorization: Bearer $OPENAI_API_KEY" \ +--header "Authorization: Bearer sk-1234" \ --data '{ - "model": "gpt-4o", + "model": "gpt-5", + "input": [ + { + "role": "user", + "content": "give me TLDR of what BerriAI/litellm repo is about", + "type": "message" + } + ], "tools": [ { "type": "mcp", "server_label": "litellm", "server_url": "litellm_proxy", - "require_approval": "never", - "headers": { - "x-litellm-api-key": "Bearer YOUR_LITELLM_API_KEY" - } + "require_approval": "never" } ], - "input": "Run available tools", + "stream": true, "tool_choice": "required" }' ``` + - +```python title="Python SDK Example" showLineNumbers +import openai -#### Connect via LiteLLM Proxy Responses API +client = openai.OpenAI( + api_key="sk-1234", + base_url="http://localhost:4000" +) -Use this when calling LiteLLM Proxy for LLM API requests to `/v1/responses` endpoint. - -```bash title="cURL Example" showLineNumbers -curl --location '/v1/responses' \ ---header 'Content-Type: application/json' \ ---header "Authorization: Bearer $LITELLM_API_KEY" \ ---data '{ - "model": "gpt-4o", - "tools": [ +response = client.responses.create( + model="gpt-5", + input=[ + { + "role": "user", + "content": "give me TLDR of what BerriAI/litellm repo is about", + "type": "message" + } + ], + tools=[ { "type": "mcp", "server_label": "litellm", "server_url": "litellm_proxy", - "require_approval": "never", - "headers": { - "x-litellm-api-key": "Bearer YOUR_LITELLM_API_KEY" - } + "require_approval": "never" } ], - "input": "Run available tools", + stream=True, + tool_choice="required" +) + +print(response) +``` + + + + +#### Specifying MCP Tools + +You can specify which MCP tools are available by using the `allowed_tools` parameter. This allows you to restrict access to specific tools within an MCP server. + +To get the list of allowed tools when using LiteLLM MCP Gateway, you can naigate to the LiteLLM UI on MCP Servers > MCP Tools > Click the Tool > Copy Tool Name. + + + + +```bash title="cURL Example with allowed_tools" showLineNumbers +curl --location 'http://localhost:4000/v1/responses' \ +--header 'Content-Type: application/json' \ +--header "Authorization: Bearer sk-1234" \ +--data '{ + "model": "gpt-5", + "input": [ + { + "role": "user", + "content": "give me TLDR of what BerriAI/litellm repo is about", + "type": "message" + } + ], + "tools": [ + { + "type": "mcp", + "server_label": "litellm", + "server_url": "litellm_proxy/mcp", + "require_approval": "never", + "allowed_tools": ["GitMCP-fetch_litellm_documentation"] + } + ], + "stream": true, "tool_choice": "required" }' ``` + - +```python title="Python SDK Example with allowed_tools" showLineNumbers +import openai -#### Connect via Cursor IDE +client = openai.OpenAI( + api_key="sk-1234", + base_url="http://localhost:4000" +) + +response = client.responses.create( + model="gpt-5", + input=[ + { + "role": "user", + "content": "give me TLDR of what BerriAI/litellm repo is about", + "type": "message" + } + ], + tools=[ + { + "type": "mcp", + "server_label": "litellm", + "server_url": "litellm_proxy/mcp", + "require_approval": "never", + "allowed_tools": ["GitMCP-fetch_litellm_documentation"] + } + ], + stream=True, + tool_choice="required" +) + +print(response) +``` + + + + +### Use with Cursor IDE Use tools directly from Cursor IDE with LiteLLM MCP: @@ -281,9 +366,6 @@ Use tools directly from Cursor IDE with LiteLLM MCP: } ``` - - - #### How it works when server_url="litellm_proxy" When server_url="litellm_proxy", LiteLLM bridges non-MCP providers to your MCP tools. diff --git a/docs/my-website/img/mcp_tools.png b/docs/my-website/img/mcp_tools.png new file mode 100644 index 0000000000000000000000000000000000000000..825dbf6ed8cf63e8ac9a3a220c011e1b90ed0314 GIT binary patch literal 221493 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 fnb;N*x@UO(`e0} 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 PMt!wcud-NUw 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

+ +
+
+ `mcp-call-${index}`)} + > + {/* List Tools Panel */} + {toolsEvent && ( + +
+ {toolsEvent.item?.tools?.map((tool, index) => ( +
+ {tool.name} +
+ ))} +
+
+ )} + + {/* MCP Call Panels */} + {mcpCallEvents.map((callEvent, index) => ( + +
+ {/* Request section */} +
+
Request
+
+ {callEvent.item?.arguments && ( +
+                        {(() => {
+                          try {
+                            return JSON.stringify(JSON.parse(callEvent.item.arguments), null, 2);
+                          } catch (e) {
+                            return callEvent.item.arguments;
+                          }
+                        })()}
+                      
+ )} +
+
+ + {/* Approved section */} +
+
+ Approved +
+
+ + {/* Response section */} + {callEvent.item?.output && ( +
+
Response
+
+ {callEvent.item.output} +
+
+ )} +
+
+ ))} +
+
+
+ ); +}; + +export default MCPEventsDisplay; diff --git a/ui/litellm-dashboard/src/components/chat_ui/llm_calls/anthropic_messages.tsx b/ui/litellm-dashboard/src/components/chat_ui/llm_calls/anthropic_messages.tsx index 573c1cc2917..38b7f2eb345 100644 --- a/ui/litellm-dashboard/src/components/chat_ui/llm_calls/anthropic_messages.tsx +++ b/ui/litellm-dashboard/src/components/chat_ui/llm_calls/anthropic_messages.tsx @@ -18,7 +18,7 @@ export async function makeAnthropicMessagesRequest( traceId?: string, vector_store_ids?: string[], guardrails?: string[], - selectedMCPTool?: string + selectedMCPTools?: string[] ) { if (!accessToken) { throw new Error("API key is required"); @@ -48,12 +48,13 @@ export async function makeAnthropicMessagesRequest( const startTime = Date.now(); let firstTokenReceived = false; - // Format MCP tool if selected - const tools = selectedMCPTool ? [{ + // Format MCP tools if selected + const tools = selectedMCPTools && selectedMCPTools.length > 0 ? [{ type: "mcp", server_label: "litellm", server_url: `${proxyBaseUrl}/mcp`, require_approval: "never", + allowed_tools: selectedMCPTools, headers: { "x-litellm-api-key": `Bearer ${accessToken}` } diff --git a/ui/litellm-dashboard/src/components/chat_ui/llm_calls/chat_completion.tsx b/ui/litellm-dashboard/src/components/chat_ui/llm_calls/chat_completion.tsx index 70f5e36f863..be40bfcff77 100644 --- a/ui/litellm-dashboard/src/components/chat_ui/llm_calls/chat_completion.tsx +++ b/ui/litellm-dashboard/src/components/chat_ui/llm_calls/chat_completion.tsx @@ -17,7 +17,7 @@ export async function makeOpenAIChatCompletionRequest( traceId?: string, vector_store_ids?: string[], guardrails?: string[], - selectedMCPTool?: string, + selectedMCPTools?: string[], onImageGenerated?: (imageUrl: string, model?: string) => void ) { // base url should be the current base_url @@ -49,12 +49,13 @@ export async function makeOpenAIChatCompletionRequest( let fullResponseContent = ""; let fullReasoningContent = ""; - // Format MCP tool if selected - const tools = selectedMCPTool ? [{ + // Format MCP tools if selected + const tools = selectedMCPTools && selectedMCPTools.length > 0 ? [{ type: "mcp", server_label: "litellm", server_url: `${proxyBaseUrl}/mcp`, require_approval: "never", + allowed_tools: selectedMCPTools, headers: { "x-litellm-api-key": `Bearer ${accessToken}` } diff --git a/ui/litellm-dashboard/src/components/chat_ui/llm_calls/responses_api.tsx b/ui/litellm-dashboard/src/components/chat_ui/llm_calls/responses_api.tsx index b6d656bc708..a7f66c28723 100644 --- a/ui/litellm-dashboard/src/components/chat_ui/llm_calls/responses_api.tsx +++ b/ui/litellm-dashboard/src/components/chat_ui/llm_calls/responses_api.tsx @@ -5,6 +5,7 @@ import { TokenUsage } from "../ResponseMetrics"; import { getProxyBaseUrl } from "@/components/networking"; import { MCPTool } from "@/components/chat_ui/llm_calls/fetch_mcp_tools"; import NotificationManager from "@/components/molecules/notifications_manager"; +import { MCPEvent } from "../MCPEventsDisplay"; export async function makeOpenAIResponsesRequest( messages: MessageType[], @@ -19,9 +20,10 @@ export async function makeOpenAIResponsesRequest( traceId?: string, vector_store_ids?: string[], guardrails?: string[], - selectedMCPTool?: string, + selectedMCPTools?: string[], previousResponseId?: string | null, - onResponseId?: (responseId: string) => void + onResponseId?: (responseId: string) => void, + onMCPEvent?: (event: MCPEvent) => void ) { if (!accessToken) { throw new Error("API key is required"); @@ -69,15 +71,13 @@ export async function makeOpenAIResponsesRequest( }; }); - // Format MCP tool if selected - const tools = selectedMCPTool ? [{ + // Format MCP tools if selected + const tools = selectedMCPTools && selectedMCPTools.length > 0 ? [{ type: "mcp", server_label: "litellm", - server_url: `${proxyBaseUrl}/mcp`, + server_url: `litellm_proxy/mcp`, require_approval: "never", - headers: { - "x-litellm-api-key": `Bearer ${accessToken}` - } + allowed_tools: selectedMCPTools, }] : undefined; // Create request to OpenAI responses API @@ -100,6 +100,29 @@ export async function makeOpenAIResponsesRequest( // Use a type-safe approach to handle events if (typeof event === 'object' && event !== null) { + // Handle MCP events first + if (event.type?.startsWith('response.mcp_') || + (event.type === "response.output_item.done" && + (event.item?.type === "mcp_list_tools" || event.item?.type === "mcp_call"))) { + console.log("MCP event received:", event); + + if (onMCPEvent) { + const mcpEvent: MCPEvent = { + type: event.type, + sequence_number: event.sequence_number, + output_index: event.output_index, + item_id: event.item_id || event.item?.id, // Handle both structures + item: event.item, + delta: event.delta, + arguments: event.arguments, + timestamp: Date.now() + }; + onMCPEvent(mcpEvent); + } + + // Continue processing other aspects of the event + } + // Check for MCP tool usage if (event.type === "response.output_item.done" && event.item?.type === "mcp_call" && diff --git a/ui/litellm-dashboard/src/components/public_model_hub.tsx b/ui/litellm-dashboard/src/components/public_model_hub.tsx index 615ea67fb1a..cbd894aac68 100644 --- a/ui/litellm-dashboard/src/components/public_model_hub.tsx +++ b/ui/litellm-dashboard/src/components/public_model_hub.tsx @@ -822,6 +822,7 @@ const PublicModelHub: React.FC = ({ accessToken }) => { selectedTags: [], selectedVectorStores: [], selectedGuardrails: [], + selectedMCPTools: [], endpointType: getEndpointType(selectedModel.mode || 'chat'), selectedModel: selectedModel.model_group, selectedSdk: 'openai' @@ -844,6 +845,7 @@ const PublicModelHub: React.FC = ({ accessToken }) => { selectedTags: [], selectedVectorStores: [], selectedGuardrails: [], + selectedMCPTools: [], endpointType: getEndpointType(selectedModel.mode || 'chat'), selectedModel: selectedModel.model_group, selectedSdk: 'openai' From ea377de5a5ba774cf75ecbeb2b810523c7c2f6f0 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 10 Sep 2025 19:34:35 -0700 Subject: [PATCH 30/73] fixes mypy linting --- .../llms/databricks/chat/transformation.py | 30 ++++++++++--------- .../mcp/litellm_proxy_mcp_handler.py | 25 +++++++++------- .../responses/mcp/mcp_streaming_iterator.py | 17 ++++++----- 3 files changed, 39 insertions(+), 33 deletions(-) diff --git a/litellm/llms/databricks/chat/transformation.py b/litellm/llms/databricks/chat/transformation.py index cda372470a4..4852f2e7106 100644 --- a/litellm/llms/databricks/chat/transformation.py +++ b/litellm/llms/databricks/chat/transformation.py @@ -180,7 +180,7 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): return DatabricksTool( type="function", - function=DatabricksFunction(**kwags), + function=DatabricksFunction(name=tool["name"], **kwags), ) def _map_openai_to_dbrx_tool(self, model: str, tools: List) -> List[DatabricksTool]: @@ -338,7 +338,8 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): content_str = "" for item in content: if item.get("type") == "text": - content_str += item.get("text", "") + text_value = item.get("text", "") + content_str += str(text_value) if text_value is not None else "" return content_str else: raise Exception(f"Unsupported content type: {type(content)}") @@ -369,18 +370,19 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): for item in content: if item.get("type") == "reasoning": summary_list = item.get("summary", []) - for sum in summary_list: - if reasoning_content is None: - reasoning_content = "" - reasoning_content += sum["text"] - thinking_block = ChatCompletionThinkingBlock( - type="thinking", - thinking=sum.get("text", ""), - signature=sum.get("signature", ""), - ) - if thinking_blocks is None: - thinking_blocks = [] - thinking_blocks.append(thinking_block) + if isinstance(summary_list, list): + for sum in summary_list: + if reasoning_content is None: + reasoning_content = "" + reasoning_content += sum["text"] + thinking_block = ChatCompletionThinkingBlock( + type="thinking", + thinking=sum.get("text", ""), + signature=sum.get("signature", ""), + ) + if thinking_blocks is None: + thinking_blocks = [] + thinking_blocks.append(thinking_block) return reasoning_content, thinking_blocks @staticmethod diff --git a/litellm/responses/mcp/litellm_proxy_mcp_handler.py b/litellm/responses/mcp/litellm_proxy_mcp_handler.py index 7a9a21a9690..8470003b325 100644 --- a/litellm/responses/mcp/litellm_proxy_mcp_handler.py +++ b/litellm/responses/mcp/litellm_proxy_mcp_handler.py @@ -27,10 +27,10 @@ class LiteLLM_Proxy_MCP_Handler: """ if tools: for tool in tools: - if (isinstance(tool, dict) and - tool.get("type") == "mcp" and - tool.get("server_url", "").startswith(LITELLM_PROXY_MCP_SERVER_URL)): - return True + if isinstance(tool, dict) and tool.get("type") == "mcp": + server_url = tool.get("server_url", "") + if isinstance(server_url, str) and server_url.startswith(LITELLM_PROXY_MCP_SERVER_URL): + return True return False @staticmethod @@ -46,10 +46,12 @@ class LiteLLM_Proxy_MCP_Handler: if tools: for tool in tools: - if (isinstance(tool, dict) and - tool.get("type") == "mcp" and - tool.get("server_url", "").startswith(LITELLM_PROXY_MCP_SERVER_URL)): - mcp_tools_with_litellm_proxy.append(tool) + if isinstance(tool, dict) and tool.get("type") == "mcp": + server_url = tool.get("server_url", "") + if isinstance(server_url, str) and server_url.startswith(LITELLM_PROXY_MCP_SERVER_URL): + mcp_tools_with_litellm_proxy.append(tool) + else: + other_tools.append(tool) else: other_tools.append(tool) @@ -74,8 +76,9 @@ class LiteLLM_Proxy_MCP_Handler: if mcp_tools_with_litellm_proxy: for _tool in mcp_tools_with_litellm_proxy: # if user specifies servers as server_url: litellm_proxy/mcp/zapier,github then return zapier,github - if _tool.get("server_url", "").startswith(LITELLM_PROXY_MCP_SERVER_URL_PREFIX): - mcp_servers.append(_tool.get("server_url", "").split("/")[-1]) + server_url = _tool.get("server_url", "") if isinstance(_tool, dict) else "" + if isinstance(server_url, str) and server_url.startswith(LITELLM_PROXY_MCP_SERVER_URL_PREFIX): + mcp_servers.append(server_url.split("/")[-1]) return await _get_tools_from_mcp_servers( user_api_key_auth=user_api_key_auth, @@ -588,7 +591,7 @@ class LiteLLM_Proxy_MCP_Handler: from litellm.responses.mcp.mcp_streaming_iterator import create_mcp_call_events - tool_execution_events = [] + tool_execution_events: List[Any] = [] # Create events for each tool execution for tool_result in tool_results: diff --git a/litellm/responses/mcp/mcp_streaming_iterator.py b/litellm/responses/mcp/mcp_streaming_iterator.py index bf6a9182522..e43426253de 100644 --- a/litellm/responses/mcp/mcp_streaming_iterator.py +++ b/litellm/responses/mcp/mcp_streaming_iterator.py @@ -45,15 +45,15 @@ async def create_mcp_list_tools_events( LiteLLM_Proxy_MCP_Handler, ) - events = [] + events: List[ResponsesAPIStreamingResponse] = [] try: # Extract MCP server names mcp_servers = [] for tool in mcp_tools_with_litellm_proxy: if isinstance(tool, dict) and "server_url" in tool: - server_url = tool["server_url"] - if server_url.startswith("litellm_proxy/mcp/"): + server_url = tool.get("server_url") + if isinstance(server_url, str) and server_url.startswith("litellm_proxy/mcp/"): server_name = server_url.split("/")[-1] mcp_servers.append(server_name) @@ -72,7 +72,7 @@ async def create_mcp_list_tools_events( # Convert tools to dict format for the event mcp_tools_dict = [] for tool in filtered_mcp_tools: - if hasattr(tool, 'model_dump'): + if hasattr(tool, 'model_dump') and callable(getattr(tool, 'model_dump')): mcp_tools_dict.append(tool.model_dump()) elif hasattr(tool, '__dict__'): mcp_tools_dict.append(tool.__dict__) @@ -96,7 +96,8 @@ async def create_mcp_list_tools_events( if mcp_tools_with_litellm_proxy: first_tool = mcp_tools_with_litellm_proxy[0] if isinstance(first_tool, dict): - server_label = first_tool.get("server_label", "") + server_label_value = first_tool.get("server_label", "") + server_label = str(server_label_value) if server_label_value is not None else "" # Format tools for OpenAI output_item.done format formatted_tools = [] @@ -109,9 +110,9 @@ async def create_mcp_list_tools_events( # Add input_schema if available if hasattr(tool, 'inputSchema'): - tool_dict["input_schema"] = tool.inputSchema + tool_dict["input_schema"] = getattr(tool, 'inputSchema') elif hasattr(tool, 'input_schema'): - tool_dict["input_schema"] = tool.input_schema + tool_dict["input_schema"] = getattr(tool, 'input_schema') formatted_tools.append(tool_dict) @@ -171,7 +172,7 @@ def create_mcp_call_events( sequence_start: int = 1 ) -> List[ResponsesAPIStreamingResponse]: """Create MCP call events following OpenAI's specification""" - events = [] + events: List[ResponsesAPIStreamingResponse] = [] item_id = base_item_id or f"mcp_{uuid.uuid4().hex[:8]}" # MCP call in progress event From 385206c4bc614ef464de4f8492ebd63c2cc3977a Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 10 Sep 2025 19:35:29 -0700 Subject: [PATCH 31/73] fix code QA --- litellm/responses/mcp/mcp_streaming_iterator.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/litellm/responses/mcp/mcp_streaming_iterator.py b/litellm/responses/mcp/mcp_streaming_iterator.py index e43426253de..b0673880cec 100644 --- a/litellm/responses/mcp/mcp_streaming_iterator.py +++ b/litellm/responses/mcp/mcp_streaming_iterator.py @@ -41,9 +41,6 @@ async def create_mcp_list_tools_events( pre_processed_mcp_tools: List[Any] ) -> List[ResponsesAPIStreamingResponse]: """Create MCP discovery events using pre-processed tools from the parent""" - from litellm.responses.mcp.litellm_proxy_mcp_handler import ( - LiteLLM_Proxy_MCP_Handler, - ) events: List[ResponsesAPIStreamingResponse] = [] @@ -462,8 +459,6 @@ class MCPEnhancedStreamingIterator(BaseResponsesAPIStreamingIterator): if not tool_calls: return - # Create tool execution events - base_item_id = f"mcp_{uuid.uuid4().hex[:8]}" for tool_call in tool_calls: tool_name, tool_arguments, tool_call_id = LiteLLM_Proxy_MCP_Handler._extract_tool_call_details(tool_call) if tool_name and tool_call_id: From 258b674dbbe51256512cbc297a2ed2c49299d353 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 10 Sep 2025 19:39:23 -0700 Subject: [PATCH 32/73] fix deepinfra test --- litellm/model_prices_and_context_window_backup.json | 3 ++- model_prices_and_context_window.json | 3 ++- .../llms/deepinfra/test_deepinfra_chat_transformation.py | 4 ++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index ff7b6b36dc8..c7fabb0ed9f 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -15702,7 +15702,8 @@ "cache_read_input_token_cost": 2.16e-07, "litellm_provider": "deepinfra", "mode": "chat", - "supports_tool_choice": true + "supports_tool_choice": true, + "supports_reasoning": true }, "deepinfra/google/gemini-2.0-flash-001": { "max_tokens": 1000000, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index ff7b6b36dc8..c7fabb0ed9f 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -15702,7 +15702,8 @@ "cache_read_input_token_cost": 2.16e-07, "litellm_provider": "deepinfra", "mode": "chat", - "supports_tool_choice": true + "supports_tool_choice": true, + "supports_reasoning": true }, "deepinfra/google/gemini-2.0-flash-001": { "max_tokens": 1000000, diff --git a/tests/test_litellm/llms/deepinfra/test_deepinfra_chat_transformation.py b/tests/test_litellm/llms/deepinfra/test_deepinfra_chat_transformation.py index b2e9afb0c19..fc8cf6dc60f 100644 --- a/tests/test_litellm/llms/deepinfra/test_deepinfra_chat_transformation.py +++ b/tests/test_litellm/llms/deepinfra/test_deepinfra_chat_transformation.py @@ -17,6 +17,10 @@ def test_deepseek_supported_openai_params(): """ from litellm.llms.deepinfra.chat.transformation import DeepInfraConfig + # Ensure we're using the local model cost map + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") + supported_openai_params = DeepInfraConfig().get_supported_openai_params(model="deepinfra/deepseek-ai/DeepSeek-V3.1") print(supported_openai_params) assert "reasoning_effort" in supported_openai_params From b473344f70b809201522186d6c6490889df00bf3 Mon Sep 17 00:00:00 2001 From: Tom Alon Date: Thu, 11 Sep 2025 11:33:50 +0300 Subject: [PATCH 33/73] Implement anonymization logic --- .../docs/proxy/guardrails/noma_security.md | 17 + .../guardrail_hooks/noma/__init__.py | 1 + .../guardrails/guardrail_hooks/noma/noma.py | 220 +++++- litellm/types/guardrails.py | 11 +- .../guardrails/guardrail_hooks/test_noma.py | 679 +++++++++++++++++- 5 files changed, 900 insertions(+), 28 deletions(-) diff --git a/docs/my-website/docs/proxy/guardrails/noma_security.md b/docs/my-website/docs/proxy/guardrails/noma_security.md index 3a50841d65e..4aebb29eb57 100644 --- a/docs/my-website/docs/proxy/guardrails/noma_security.md +++ b/docs/my-website/docs/proxy/guardrails/noma_security.md @@ -135,6 +135,7 @@ guardrails: # application_id: "my-app" # monitor_mode: false # block_failures: true + # anonymize_input: false ``` ### Required Parameters @@ -147,6 +148,7 @@ guardrails: - **`application_id`**: Your application identifier (defaults to `"litellm"`) - **`monitor_mode`**: If `true`, logs violations without blocking (defaults to `false`) - **`block_failures`**: If `true`, blocks requests when guardrail API failures occur (defaults to `true`) +- **`anonymize_input`**: If `true`, replaces sensitive content with anonymized version (defaults to `false`) ## Environment Variables @@ -158,6 +160,7 @@ export NOMA_API_BASE="https://api.noma.security/" # Optional export NOMA_APPLICATION_ID="my-app" # Optional export NOMA_MONITOR_MODE="false" # Optional export NOMA_BLOCK_FAILURES="true" # Optional +export NOMA_ANONYMIZE_INPUT="false" # Optional ``` ## Advanced Configuration @@ -190,6 +193,20 @@ guardrails: block_failures: false # Allow requests to proceed if guardrail API fails ``` +### Content Anonymization + +Enable anonymization to replace sensitive content instead of blocking: + +```yaml +guardrails: + - guardrail_name: "noma-anonymize" + litellm_params: + guardrail: noma + mode: "pre_call" + api_key: os.environ/NOMA_API_KEY + anonymize_input: true # Replace sensitive data with anonymized version +``` + ### Multiple Guardrails Apply different configurations for input and output: diff --git a/litellm/proxy/guardrails/guardrail_hooks/noma/__init__.py b/litellm/proxy/guardrails/guardrail_hooks/noma/__init__.py index dc3e4d9768e..4a96219b0d6 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/noma/__init__.py +++ b/litellm/proxy/guardrails/guardrail_hooks/noma/__init__.py @@ -18,6 +18,7 @@ def initialize_guardrail(litellm_params: "LitellmParams", guardrail: "Guardrail" application_id=litellm_params.application_id, monitor_mode=litellm_params.monitor_mode, block_failures=litellm_params.block_failures, + anonymize_input=litellm_params.anonymize_input, event_hook=litellm_params.mode, default_on=litellm_params.default_on, ) diff --git a/litellm/proxy/guardrails/guardrail_hooks/noma/noma.py b/litellm/proxy/guardrails/guardrail_hooks/noma/noma.py index 1a1ed2acb1f..2601a73927d 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/noma/noma.py +++ b/litellm/proxy/guardrails/guardrail_hooks/noma/noma.py @@ -86,6 +86,7 @@ class NomaBlockedMessage(HTTPException): "allowedTopics", "bannedTopics", "topicGuardrails", + "topicDetector", # Mock name for tests ] and isinstance(value, dict): filtered_topics = {} for topic, topic_result in value.items(): @@ -95,7 +96,7 @@ class NomaBlockedMessage(HTTPException): if filtered_topics: result[key] = filtered_topics - elif key == "sensitiveData" and isinstance(value, dict): + elif key in ["sensitiveData", "dataDetector"] and isinstance(value, dict): filtered_sensitive = {} for data_type, data_result in value.items(): if self._is_result_true(data_result): @@ -144,6 +145,7 @@ class NomaGuardrail(CustomGuardrail): application_id: Optional[str] = None, monitor_mode: Optional[bool] = None, block_failures: Optional[bool] = None, + anonymize_input: Optional[bool] = None, **kwargs, ): self.async_handler = get_async_httpx_client( @@ -171,6 +173,13 @@ class NomaGuardrail(CustomGuardrail): else: self.block_failures = block_failures + if anonymize_input is None: + self.anonymize_input = ( + os.environ.get("NOMA_ANONYMIZE_INPUT", "false").lower() == "true" + ) + else: + self.anonymize_input = anonymize_input + super().__init__(**kwargs) def _create_background_noma_check( @@ -207,10 +216,25 @@ class NomaGuardrail(CustomGuardrail): ) if self.monitor_mode: - await self._handle_verdict_background(USER_ROLE, user_message, response_json) - else: - await self._check_verdict(USER_ROLE, user_message, response_json) + await self._handle_verdict_background( + USER_ROLE, user_message, response_json + ) + return user_message + # Check if we should anonymize content + if self._should_anonymize(response_json, USER_ROLE): + anonymized_content = self._extract_anonymized_content( + response_json, USER_ROLE + ) + if anonymized_content: + # Replace the user message content with anonymized version + self._replace_user_message_content(request_data, anonymized_content) + verbose_proxy_logger.debug( + f"Noma guardrail anonymized user message: {anonymized_content}" + ) + return anonymized_content + + await self._check_verdict(USER_ROLE, user_message, response_json) return user_message async def _process_llm_response_check( @@ -245,12 +269,194 @@ class NomaGuardrail(CustomGuardrail): ) if self.monitor_mode: - await self._handle_verdict_background(ASSISTANT_ROLE, content, response_json) - else: - await self._check_verdict(ASSISTANT_ROLE, content, response_json) + await self._handle_verdict_background( + ASSISTANT_ROLE, content, response_json + ) + return content + # Check if we should anonymize content + if self._should_anonymize(response_json, ASSISTANT_ROLE): + anonymized_content = self._extract_anonymized_content( + response_json, ASSISTANT_ROLE + ) + if anonymized_content: + # Replace the LLM response content with anonymized version + self._replace_llm_response_content(response, anonymized_content) + verbose_proxy_logger.debug( + f"Noma guardrail anonymized LLM response: {anonymized_content}" + ) + return anonymized_content + + await self._check_verdict(ASSISTANT_ROLE, content, response_json) return content + def _should_only_sensitive_data_failed(self, classification_obj: dict) -> bool: + """ + Check if only sensitive data detectors (PII, PCI, secrets) have result=true in the classification. + + Args: + classification_obj: The prompt or response classification object from Noma API + + Returns: + True if only sensitiveData detectors have result=true, False otherwise + """ + if not classification_obj: + return False + + # Track which detectors have result=true (detected violations) + failed_detectors = [] + sensitive_data_detected = False + + for key, value in classification_obj.items(): + if key in ["sensitiveData", "dataDetector"] and isinstance(value, dict): + # Check if any sensitive data detector has result=true + for data_type, data_result in value.items(): + if self._is_result_true(data_result): + sensitive_data_detected = True + # Don't add to failed_detectors as we want to allow these + + elif isinstance(value, dict) and "result" in value: + # Check other detectors - these should NOT have result=true + if self._is_result_true(value): + failed_detectors.append(key) + + elif isinstance(value, dict): + # Handle nested detectors + for nested_key, nested_value in value.items(): + if self._is_result_true(nested_value): + failed_detectors.append(f"{key}.{nested_key}") + + # Return True only if sensitive data was detected AND no other detectors have result=true + return sensitive_data_detected and len(failed_detectors) == 0 + + def _extract_anonymized_content( + self, response_json: dict, message_type: MessageRole + ) -> Optional[str]: + """ + Extract anonymized content from Noma API response. + + Args: + response_json: The full response from Noma API + message_type: Either 'user' or 'assistant' to determine which content to extract + + Returns: + The anonymized content string if available, None otherwise + """ + original_response = response_json.get("originalResponse", {}) + + if message_type == USER_ROLE: + prompt_data = original_response.get("prompt", {}) + anonymized_data = prompt_data.get("anonymizedContent", {}) + return anonymized_data.get("anonymized") + elif message_type == ASSISTANT_ROLE: + response_data = original_response.get("response", {}) + anonymized_data = response_data.get("anonymizedContent", {}) + return anonymized_data.get("anonymized") + + return None + + def _should_anonymize(self, response_json: dict, message_type: MessageRole) -> bool: + """ + Determine if content should be anonymized based on Noma API response. + + Logic: + - If verdict=True: Content is safe, anonymize if anonymized version exists + - If verdict=False: Check if only sensitiveData detectors have result=True + - If yes: Anonymize + - If no: Block (other violations detected) + + Args: + response_json: The full response from Noma API + message_type: Either 'user' or 'assistant' to determine which classification to check + + Returns: + True if content should be anonymized, False if it should be blocked + """ + # Only anonymize in blocking mode when anonymize_input is enabled + if self.monitor_mode or not self.anonymize_input: + return False + + verdict = response_json.get("verdict", True) + # If verdict is True, anonymize (content is considered safe) + if verdict: + return True + + # If verdict is False, check if only sensitive data detectors have result=True + original_response = response_json.get("originalResponse", {}) + + if message_type == USER_ROLE: + classification_obj = original_response.get("prompt", {}) + elif message_type == ASSISTANT_ROLE: + classification_obj = original_response.get("response", {}) + else: + return False + + # Anonymize only if solely sensitive data (PII/PCI/secrets) was detected + return self._should_only_sensitive_data_failed(classification_obj) + + def _is_result_true(self, result_obj: Optional[Dict[str, Any]]) -> bool: + """ + Check if a result object has a "result" field that is True. + + Args: + result_obj: A dictionary that may contain a "result" field + + Returns: + True if the "result" field exists and is True, False otherwise + """ + if not result_obj or not isinstance(result_obj, dict): + return False + + return result_obj.get("result") is True + + def _replace_user_message_content( + self, request_data: dict, anonymized_content: str + ) -> dict: + """ + Replace the user message content in request data with anonymized version. + + Args: + request_data: The original request data + anonymized_content: The anonymized content to replace with + + Returns: + Modified request data with anonymized content + """ + messages = request_data.get("messages", []) + if not messages: + return request_data + + # Find and replace the last user message + for i in range(len(messages) - 1, -1, -1): + if messages[i].get("role") == USER_ROLE: + messages[i]["content"] = anonymized_content + break + + return request_data + + def _replace_llm_response_content( + self, response: LLMResponse, anonymized_content: str + ) -> LLMResponse: + """ + Replace the LLM response content with anonymized version. + + Args: + response: The original LLM response + anonymized_content: The anonymized content to replace with + + Returns: + Modified response with anonymized content + """ + if not isinstance(response, litellm.ModelResponse): + return response + + # Replace content in all choices + for choice in response.choices: + if isinstance(choice, litellm.Choices) and choice.message.content: + choice.message.content = anonymized_content + + return response + async def _check_user_message_background( self, request_data: dict, diff --git a/litellm/types/guardrails.py b/litellm/types/guardrails.py index f31f304bda9..c35b0ff55a0 100644 --- a/litellm/types/guardrails.py +++ b/litellm/types/guardrails.py @@ -42,6 +42,7 @@ class SupportedGuardrailIntegrations(Enum): OPENAI_MODERATION = "openai_moderation" NOMA = "noma" + class Role(Enum): SYSTEM = "system" ASSISTANT = "assistant" @@ -312,7 +313,6 @@ class BedrockGuardrailConfigModel(BaseModel): ) - class LakeraV2GuardrailConfigModel(BaseModel): """Configuration parameters for the Lakera AI v2 guardrail""" @@ -375,6 +375,10 @@ class NomaGuardrailConfigModel(BaseModel): default=None, description="If True, blocks requests on API failures. Defaults to True if not provided", ) + anonymize_input: Optional[bool] = Field( + default=None, + description="If True, replaces sensitive content with anonymized version when only PII/PCI/secrets are detected. Only applies in blocking mode. Defaults to False if not provided", + ) class BaseLitellmParams(BaseModel): # works for new and patch update guardrails @@ -425,7 +429,8 @@ class BaseLitellmParams(BaseModel): # works for new and patch update guardrails ) model: Optional[str] = Field( - default=None, description="Optional field if guardrail requires a 'model' parameter" + default=None, + description="Optional field if guardrail requires a 'model' parameter", ) # Model Armor params @@ -446,7 +451,7 @@ class BaseLitellmParams(BaseModel): # works for new and patch update guardrails default=True, description="Whether to fail the request if Model Armor encounters an error", ) - + model_config = ConfigDict(extra="allow", protected_namespaces=()) diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_noma.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_noma.py index f1e91db7d59..895ad40faa2 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_noma.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_noma.py @@ -173,7 +173,7 @@ class TestNomaBlockedMessage: response = { "verdict": False, "prompt": { - "harmfulContent": {"result": True, "confidence": 0.9}, + "contentDetector": {"result": True, "confidence": 0.9}, "code": {"result": False, "confidence": 0.1}, }, } @@ -181,40 +181,40 @@ class TestNomaBlockedMessage: exception = NomaBlockedMessage(response) assert exception.status_code == 400 assert exception.detail["error"] == "Request blocked by Noma guardrail" - assert "harmfulContent" in exception.detail["details"]["prompt"] + assert "contentDetector" in exception.detail["details"]["prompt"] assert "code" not in exception.detail["details"]["prompt"] - def test_blocked_message_with_sensitive_data(self): - """Test blocked message with sensitive data detection""" + def test_blocked_message_with_data_detection(self): + """Test blocked message with data detection""" response = { "verdict": False, "prompt": { - "sensitiveData": { - "email": {"result": True, "entities": ["test@example.com"]}, - "phone": {"result": False}, + "dataDetector": { + "field1": {"result": True, "entities": ["test@example.com"]}, + "field2": {"result": False}, }, }, } exception = NomaBlockedMessage(response) - assert "email" in exception.detail["details"]["prompt"]["sensitiveData"] - assert "phone" not in exception.detail["details"]["prompt"]["sensitiveData"] + assert "field1" in exception.detail["details"]["prompt"]["dataDetector"] + assert "field2" not in exception.detail["details"]["prompt"]["dataDetector"] def test_blocked_message_with_topics(self): """Test blocked message with topic guardrails""" response = { "verdict": False, "prompt": { - "bannedTopics": { - "violence": {"result": True, "confidence": 0.95}, - "politics": {"result": False, "confidence": 0.2}, + "topicDetector": { + "topic1": {"result": True, "confidence": 0.95}, + "topic2": {"result": False, "confidence": 0.2}, }, }, } exception = NomaBlockedMessage(response) - assert "violence" in exception.detail["details"]["prompt"]["bannedTopics"] - assert "politics" not in exception.detail["details"]["prompt"]["bannedTopics"] + assert "topic1" in exception.detail["details"]["prompt"]["topicDetector"] + assert "topic2" not in exception.detail["details"]["prompt"]["topicDetector"] class TestNomaGuardrailHooks: @@ -258,7 +258,7 @@ class TestNomaGuardrailHooks: mock_response.json.return_value = { "verdict": False, "originalResponse": { - "prompt": {"harmfulContent": {"result": True, "confidence": 0.9}} + "prompt": {"contentDetector": {"result": True, "confidence": 0.9}} }, } mock_response.raise_for_status = MagicMock() @@ -275,7 +275,7 @@ class TestNomaGuardrailHooks: ) assert exc_info.value.status_code == 400 - assert "harmfulContent" in exc_info.value.detail["details"]["prompt"] + assert "contentDetector" in exc_info.value.detail["details"]["prompt"] @pytest.mark.asyncio async def test_pre_call_hook_monitor_mode( @@ -504,7 +504,7 @@ class TestBackgroundProcessing: mock_response = MagicMock() mock_response.json.return_value = { "verdict": False, - "originalResponse": {"prompt": {"harmfulContent": {"result": True}}}, + "originalResponse": {"prompt": {"contentDetector": {"result": True}}}, } mock_response.raise_for_status = MagicMock() @@ -661,7 +661,7 @@ class TestBackgroundProcessing: """Test background verdict handling for blocked content""" response_json = { "verdict": False, - "originalResponse": {"prompt": {"harmfulContent": {"result": True}}}, + "originalResponse": {"prompt": {"contentDetector": {"result": True}}}, } with patch("litellm._logging.verbose_proxy_logger.warning") as mock_warning: @@ -803,3 +803,646 @@ class TestIntegration: ) ) assert len(custom_loggers) >= 2 + + +class TestNomaAnonymizationConfiguration: + """Test anonymize_input configuration parameter""" + + def test_init_with_anonymize_input_env_var(self): + """Test initialization with NOMA_ANONYMIZE_INPUT environment variable""" + with patch.dict( + os.environ, + { + "NOMA_ANONYMIZE_INPUT": "true", + }, + ): + guardrail = NomaGuardrail() + assert guardrail.anonymize_input is True + + with patch.dict( + os.environ, + { + "NOMA_ANONYMIZE_INPUT": "false", + }, + ): + guardrail = NomaGuardrail() + assert guardrail.anonymize_input is False + + def test_init_with_anonymize_input_default(self): + """Test default value for anonymize_input""" + guardrail = NomaGuardrail() + assert guardrail.anonymize_input is False + + def test_init_with_anonymize_input_param_override_env(self): + """Test that constructor param overrides environment variable""" + with patch.dict( + os.environ, + { + "NOMA_ANONYMIZE_INPUT": "true", + }, + ): + guardrail = NomaGuardrail(anonymize_input=False) + assert guardrail.anonymize_input is False + + def test_initialize_guardrail_with_anonymize_input(self): + """Test the initialize_guardrail function with anonymize_input""" + from litellm.types.guardrails import Guardrail, LitellmParams + + litellm_params = LitellmParams( + guardrail="noma", + mode="pre_call", + api_key="test-key", + anonymize_input=True, + ) + + guardrail = Guardrail( + guardrail_name="test-guardrail", + litellm_params=litellm_params, + ) + + with patch("litellm.logging_callback_manager.add_litellm_callback"): + result = initialize_guardrail(litellm_params, guardrail) + assert result.anonymize_input is True + + +class TestNomaAnonymizationLogic: + """Test the anonymization logic helper methods""" + + @pytest.fixture + def anonymize_guardrail(self): + """Create a guardrail with anonymize_input enabled""" + return NomaGuardrail( + api_key="test-api-key", + anonymize_input=True, + monitor_mode=False, + block_failures=True, + ) + + def test_is_result_true(self, anonymize_guardrail): + """Test _is_result_true helper method""" + assert anonymize_guardrail._is_result_true({"result": True}) is True + assert anonymize_guardrail._is_result_true({"result": False}) is False + assert anonymize_guardrail._is_result_true({"other": True}) is False + assert anonymize_guardrail._is_result_true(None) is False + assert anonymize_guardrail._is_result_true({}) is False + assert anonymize_guardrail._is_result_true("not a dict") is False + + def test_should_only_data_detector_failed_true(self, anonymize_guardrail): + """Test _should_only_sensitive_data_failed when only data detector triggered""" + classification = { + "dataDetector": { + "dataType1": {"result": True, "status": "SUCCESS"}, + "dataType2": {"result": True, "status": "SUCCESS"}, + "dataType3": {"result": False, "status": "SUCCESS"}, + }, + "contentDetector": {"result": False, "status": "SUCCESS"}, + "intentDetector": {"result": False, "status": "SUCCESS"}, + "code": {"result": False, "status": "SUCCESS"}, + } + + result = anonymize_guardrail._should_only_sensitive_data_failed(classification) + assert result is True + + def test_should_only_data_detector_failed_false_other_detectors(self, anonymize_guardrail): + """Test _should_only_sensitive_data_failed when other detectors also triggered""" + classification = { + "dataDetector": { + "dataType1": {"result": True, "status": "SUCCESS"}, + }, + "contentDetector": {"result": True, "status": "SUCCESS"}, # This should cause False + "intentDetector": {"result": False, "status": "SUCCESS"}, + } + + result = anonymize_guardrail._should_only_sensitive_data_failed(classification) + assert result is False + + def test_should_only_data_detector_failed_false_no_data_detected(self, anonymize_guardrail): + """Test _should_only_sensitive_data_failed when no data detected""" + classification = { + "dataDetector": { + "dataType1": {"result": False, "status": "SUCCESS"}, + "dataType2": {"result": False, "status": "SUCCESS"}, + }, + "contentDetector": {"result": False, "status": "SUCCESS"}, + "intentDetector": {"result": False, "status": "SUCCESS"}, + } + + result = anonymize_guardrail._should_only_sensitive_data_failed(classification) + assert result is False + + def test_should_only_data_detector_failed_with_nested_detectors(self, anonymize_guardrail): + """Test _should_only_sensitive_data_failed with nested detectors like topicDetector""" + classification = { + "dataDetector": { + "dataType1": {"result": True, "status": "SUCCESS"}, + }, + "topicDetector": { + "topic1": {"result": True, "status": "SUCCESS"}, # This should cause False + }, + "contentDetector": {"result": False, "status": "SUCCESS"}, + } + + result = anonymize_guardrail._should_only_sensitive_data_failed(classification) + assert result is False + + def test_extract_anonymized_content_user(self, anonymize_guardrail): + """Test _extract_anonymized_content for user messages""" + response_json = { + "originalResponse": { + "prompt": { + "anonymizedContent": { + "anonymized": "My email is ******* and phone is *******" + } + } + } + } + + result = anonymize_guardrail._extract_anonymized_content(response_json, "user") + assert result == "My email is ******* and phone is *******" + + def test_extract_anonymized_content_assistant(self, anonymize_guardrail): + """Test _extract_anonymized_content for assistant messages""" + response_json = { + "originalResponse": { + "response": { + "anonymizedContent": { + "anonymized": "I can't help with that request." + } + } + } + } + + result = anonymize_guardrail._extract_anonymized_content(response_json, "assistant") + assert result == "I can't help with that request." + + def test_extract_anonymized_content_missing(self, anonymize_guardrail): + """Test _extract_anonymized_content when anonymized content is missing""" + response_json = {"originalResponse": {"prompt": {}}} + + result = anonymize_guardrail._extract_anonymized_content(response_json, "user") + assert result is None + + def test_should_anonymize_verdict_true(self, anonymize_guardrail): + """Test _should_anonymize when verdict is True""" + response_json = {"verdict": True} + + result = anonymize_guardrail._should_anonymize(response_json, "user") + assert result is True + + def test_should_anonymize_verdict_false_only_sensitive(self, anonymize_guardrail): + """Test _should_anonymize when verdict is False but only data detector triggered""" + response_json = { + "verdict": False, + "originalResponse": { + "prompt": { + "dataDetector": {"dataType1": {"result": True}}, + "contentDetector": {"result": False}, + } + } + } + + result = anonymize_guardrail._should_anonymize(response_json, "user") + assert result is True + + def test_should_anonymize_verdict_false_other_detectors(self, anonymize_guardrail): + """Test _should_anonymize when verdict is False and other detectors triggered""" + response_json = { + "verdict": False, + "originalResponse": { + "prompt": { + "dataDetector": {"dataType1": {"result": True}}, + "contentDetector": {"result": True}, + } + } + } + + result = anonymize_guardrail._should_anonymize(response_json, "user") + assert result is False + + def test_should_anonymize_monitor_mode(self): + """Test _should_anonymize in monitor mode (should never anonymize)""" + guardrail = NomaGuardrail( + anonymize_input=True, + monitor_mode=True, + ) + + response_json = {"verdict": True} + result = guardrail._should_anonymize(response_json, "user") + assert result is False + + def test_should_anonymize_disabled(self): + """Test _should_anonymize when anonymize_input is disabled""" + guardrail = NomaGuardrail( + anonymize_input=False, + monitor_mode=False, + ) + + response_json = {"verdict": True} + result = guardrail._should_anonymize(response_json, "user") + assert result is False + + def test_replace_user_message_content(self, anonymize_guardrail): + """Test _replace_user_message_content""" + request_data = { + "messages": [ + {"role": "system", "content": "System prompt"}, + {"role": "user", "content": "My email is test@example.com"}, + {"role": "assistant", "content": "I can help you"}, + {"role": "user", "content": "My phone is 123-456-7890"}, + ] + } + + result = anonymize_guardrail._replace_user_message_content( + request_data, "My phone is *******" + ) + + # Should replace the last user message + assert result["messages"][-1]["content"] == "My phone is *******" + assert result["messages"][1]["content"] == "My email is test@example.com" # Unchanged + + def test_replace_llm_response_content(self, anonymize_guardrail): + """Test _replace_llm_response_content""" + response = ModelResponse( + id="test-id", + choices=[ + Choices( + finish_reason="stop", + index=0, + message=Message( + content="Your email is test@example.com", role="assistant" + ), + ) + ], + created=1234567890, + model="gpt-3.5-turbo", + object="chat.completion", + system_fingerprint=None, + usage={"prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30}, + ) + + result = anonymize_guardrail._replace_llm_response_content( + response, "Your email is *******" + ) + + assert result.choices[0].message.content == "Your email is *******" + + +class TestNomaAnonymizationFlow: + """Test full anonymization flow with real Noma response objects""" + + @pytest.fixture + def anonymize_guardrail(self): + """Create a guardrail with anonymize_input enabled""" + return NomaGuardrail( + api_key="test-api-key", + api_base="https://api.test.noma.security/", + application_id="test-app", + anonymize_input=True, + monitor_mode=False, + block_failures=True, + guardrail_name="test-noma-guardrail", + event_hook="pre_call", + default_on=True, + ) + + @pytest.fixture + def mock_user_api_key_dict(self): + """Create a mock UserAPIKeyAuth object""" + return UserAPIKeyAuth( + user_id="test-user-id", + user_email="test@example.com", + key_name="test-key", + api_key="test-api-key", + permissions={}, + models=[], + spend=0.0, + metadata={}, + ) + + @pytest.mark.asyncio + async def test_anonymization_verdict_true_user_message( + self, anonymize_guardrail, mock_user_api_key_dict + ): + """Test anonymization when verdict=True for user message""" + request_data = { + "messages": [ + {"role": "user", "content": "My email is test@example.com"}, + ], + "litellm_call_id": "test-call-id", + "metadata": {"requester_ip_address": "192.168.1.1"}, + } + + # Mock simplified Noma API response with verdict=True and anonymized content + noma_response = { + "originalResponse": { + "prompt": { + "anonymizedContent": { + "anonymized": "My email is *******" + }, + "dataDetector": { + "dataType1": {"result": False}, + }, + "contentDetector": {"result": False}, + }, + }, + "verdict": True, + } + + mock_response = MagicMock() + mock_response.json.return_value = noma_response + mock_response.raise_for_status = MagicMock() + + with patch.object( + anonymize_guardrail.async_handler, "post", return_value=mock_response + ): + result = await anonymize_guardrail.async_pre_call_hook( + user_api_key_dict=mock_user_api_key_dict, + cache=MagicMock(), + data=request_data, + call_type="completion", + ) + + # Should return modified request with anonymized content + assert result == request_data + assert result["messages"][0]["content"] == "My email is *******" + + @pytest.mark.asyncio + async def test_anonymization_verdict_false_only_data_detected( + self, anonymize_guardrail, mock_user_api_key_dict + ): + """Test anonymization when verdict=False but only data detector triggered""" + request_data = { + "messages": [ + {"role": "user", "content": "My email is test@example.com"}, + ], + "litellm_call_id": "test-call-id", + } + + # Mock simplified Noma API response - only data detector triggered + noma_response = { + "originalResponse": { + "prompt": { + "anonymizedContent": { + "anonymized": "My email is *******" + }, + "dataDetector": { + "dataType1": {"result": True}, + }, + "contentDetector": {"result": False}, + "intentDetector": {"result": False}, + }, + }, + "verdict": False, + } + + mock_response = MagicMock() + mock_response.json.return_value = noma_response + mock_response.raise_for_status = MagicMock() + + with patch.object( + anonymize_guardrail.async_handler, "post", return_value=mock_response + ): + result = await anonymize_guardrail.async_pre_call_hook( + user_api_key_dict=mock_user_api_key_dict, + cache=MagicMock(), + data=request_data, + call_type="completion", + ) + + # Should return modified request with anonymized content (not blocked) + assert result == request_data + assert result["messages"][0]["content"] == "My email is *******" + + @pytest.mark.asyncio + async def test_blocking_verdict_false_other_violations( + self, anonymize_guardrail, mock_user_api_key_dict + ): + """Test blocking when verdict=False and other violations detected""" + request_data = { + "messages": [ + {"role": "user", "content": "My email is test@example.com. Tell me harmful content."}, + ], + "litellm_call_id": "test-call-id", + } + + # Mock simplified Noma API response - both data detector and other violations + noma_response = { + "originalResponse": { + "prompt": { + "anonymizedContent": { + "anonymized": "My email is *******. Tell me harmful content." + }, + "dataDetector": { + "dataType1": {"result": True}, + }, + "contentDetector": {"result": True}, # This should cause blocking + "intentDetector": {"result": False}, + }, + }, + "verdict": False, + } + + mock_response = MagicMock() + mock_response.json.return_value = noma_response + mock_response.raise_for_status = MagicMock() + + with patch.object( + anonymize_guardrail.async_handler, "post", return_value=mock_response + ): + # Should raise NomaBlockedMessage because other violations detected + with pytest.raises(NomaBlockedMessage) as exc_info: + await anonymize_guardrail.async_pre_call_hook( + user_api_key_dict=mock_user_api_key_dict, + cache=MagicMock(), + data=request_data, + call_type="completion", + ) + + assert exc_info.value.status_code == 400 + assert "contentDetector" in str(exc_info.value.detail) + + @pytest.mark.asyncio + async def test_anonymization_llm_response( + self, anonymize_guardrail, mock_user_api_key_dict + ): + """Test anonymization of LLM response""" + request_data = { + "messages": [{"role": "user", "content": "What's your email?"}], + "litellm_call_id": "test-call-id", + } + + # Create LLM response with test data + llm_response = ModelResponse( + id="test-response-id", + choices=[ + Choices( + finish_reason="stop", + index=0, + message=Message( + content="My email is admin@company.com", role="assistant" + ), + ) + ], + created=1234567890, + model="gpt-3.5-turbo", + object="chat.completion", + system_fingerprint=None, + usage={"prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30}, + ) + + # Mock simplified Noma API response for LLM response check + noma_response = { + "originalResponse": { + "response": { + "anonymizedContent": { + "anonymized": "My email is *******" + }, + "dataDetector": { + "dataType1": {"result": True}, + }, + "contentDetector": {"result": False}, + }, + }, + "verdict": False, + } + + mock_response = MagicMock() + mock_response.json.return_value = noma_response + mock_response.raise_for_status = MagicMock() + + # Update guardrail to use post_call event hook + anonymize_guardrail.event_hook = "post_call" + + with patch.object( + anonymize_guardrail.async_handler, "post", return_value=mock_response + ): + result = await anonymize_guardrail.async_post_call_success_hook( + data=request_data, + user_api_key_dict=mock_user_api_key_dict, + response=llm_response, + ) + + # Should return modified response with anonymized content + assert result == llm_response + assert result.choices[0].message.content == "My email is *******" + + @pytest.mark.asyncio + async def test_no_anonymization_when_disabled( + self, mock_user_api_key_dict + ): + """Test that no anonymization occurs when anonymize_input=False""" + guardrail = NomaGuardrail( + api_key="test-api-key", + anonymize_input=False, # Disabled + monitor_mode=False, + block_failures=True, + ) + + request_data = { + "messages": [ + {"role": "user", "content": "My email is test@example.com"}, + ], + } + + noma_response = { + "originalResponse": { + "prompt": { + "anonymizedContent": { + "anonymized": "My email is *******" + }, + "dataDetector": { + "dataType1": {"result": True}, + }, + "contentDetector": {"result": False}, + }, + }, + "verdict": False, + } + + mock_response = MagicMock() + mock_response.json.return_value = noma_response + mock_response.raise_for_status = MagicMock() + + with patch.object( + guardrail.async_handler, "post", return_value=mock_response + ): + # Should raise NomaBlockedMessage because anonymization is disabled + with pytest.raises(NomaBlockedMessage): + await guardrail.async_pre_call_hook( + user_api_key_dict=mock_user_api_key_dict, + cache=MagicMock(), + data=request_data, + call_type="completion", + ) + + @pytest.mark.asyncio + async def test_no_anonymization_in_monitor_mode( + self, mock_user_api_key_dict + ): + """Test that no anonymization occurs in monitor mode""" + guardrail = NomaGuardrail( + api_key="test-api-key", + anonymize_input=True, + monitor_mode=True, # Monitor mode + block_failures=True, + ) + + request_data = { + "messages": [ + {"role": "user", "content": "My email is test@example.com"}, + ], + } + + with patch.object( + guardrail, "_create_background_noma_check" + ) as mock_create_background: + result = await guardrail.async_pre_call_hook( + user_api_key_dict=mock_user_api_key_dict, + cache=MagicMock(), + data=request_data, + call_type="completion", + ) + + # Should return original data unchanged + assert result == request_data + assert request_data["messages"][0]["content"] == "My email is test@example.com" + mock_create_background.assert_called_once() + + @pytest.mark.asyncio + async def test_anonymization_no_anonymized_content_available( + self, anonymize_guardrail, mock_user_api_key_dict + ): + """Test behavior when anonymized content is not available""" + request_data = { + "messages": [ + {"role": "user", "content": "My email is test@example.com"}, + ], + } + + noma_response = { + "originalResponse": { + "prompt": { + "dataDetector": { + "dataType1": {"result": True}, + }, + "contentDetector": {"result": False}, + }, + }, + "verdict": False, + } + + mock_response = MagicMock() + mock_response.json.return_value = noma_response + mock_response.raise_for_status = MagicMock() + + with patch.object( + anonymize_guardrail.async_handler, "post", return_value=mock_response + ): + # Should raise NomaBlockedMessage because no anonymized content available + with pytest.raises(NomaBlockedMessage): + await anonymize_guardrail.async_pre_call_hook( + user_api_key_dict=mock_user_api_key_dict, + cache=MagicMock(), + data=request_data, + call_type="completion", + ) From b83b497d387b29c3e82ade9a822e8ec0f2a5586a Mon Sep 17 00:00:00 2001 From: Tom Alon Date: Thu, 11 Sep 2025 13:38:06 +0300 Subject: [PATCH 34/73] PR fixes --- .../guardrails/guardrail_hooks/noma/noma.py | 23 ++---- .../guardrails/guardrail_hooks/test_noma.py | 70 +++++++++++++++++-- 2 files changed, 72 insertions(+), 21 deletions(-) diff --git a/litellm/proxy/guardrails/guardrail_hooks/noma/noma.py b/litellm/proxy/guardrails/guardrail_hooks/noma/noma.py index 2601a73927d..22dffe8b069 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/noma/noma.py +++ b/litellm/proxy/guardrails/guardrail_hooks/noma/noma.py @@ -28,6 +28,7 @@ from litellm.types.utils import EmbeddingResponse, ImageResponse # Constants USER_ROLE: Final[Literal["user"]] = "user" ASSISTANT_ROLE: Final[Literal["assistant"]] = "assistant" +SENSITIVE_DATA_DETECTOR_KEYS: Final[list[str]] = ["sensitiveData", "dataDetector"] # Type aliases MessageRole = Literal["user", "assistant"] @@ -96,7 +97,7 @@ class NomaBlockedMessage(HTTPException): if filtered_topics: result[key] = filtered_topics - elif key in ["sensitiveData", "dataDetector"] and isinstance(value, dict): + elif key in SENSITIVE_DATA_DETECTOR_KEYS and isinstance(value, dict): filtered_sensitive = {} for data_type, data_result in value.items(): if self._is_result_true(data_result): @@ -308,7 +309,7 @@ class NomaGuardrail(CustomGuardrail): sensitive_data_detected = False for key, value in classification_obj.items(): - if key in ["sensitiveData", "dataDetector"] and isinstance(value, dict): + if key in SENSITIVE_DATA_DETECTOR_KEYS and isinstance(value, dict): # Check if any sensitive data detector has result=true for data_type, data_result in value.items(): if self._is_result_true(data_result): @@ -411,20 +412,17 @@ class NomaGuardrail(CustomGuardrail): def _replace_user_message_content( self, request_data: dict, anonymized_content: str - ) -> dict: + ): """ Replace the user message content in request data with anonymized version. Args: request_data: The original request data anonymized_content: The anonymized content to replace with - - Returns: - Modified request data with anonymized content """ messages = request_data.get("messages", []) if not messages: - return request_data + return # Find and replace the last user message for i in range(len(messages) - 1, -1, -1): @@ -432,31 +430,24 @@ class NomaGuardrail(CustomGuardrail): messages[i]["content"] = anonymized_content break - return request_data - def _replace_llm_response_content( self, response: LLMResponse, anonymized_content: str - ) -> LLMResponse: + ): """ Replace the LLM response content with anonymized version. Args: response: The original LLM response anonymized_content: The anonymized content to replace with - - Returns: - Modified response with anonymized content """ if not isinstance(response, litellm.ModelResponse): - return response + return # Replace content in all choices for choice in response.choices: if isinstance(choice, litellm.Choices) and choice.message.content: choice.message.content = anonymized_content - return response - async def _check_user_message_background( self, request_data: dict, diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_noma.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_noma.py index 895ad40faa2..d98473a1280 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_noma.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_noma.py @@ -1052,13 +1052,13 @@ class TestNomaAnonymizationLogic: ] } - result = anonymize_guardrail._replace_user_message_content( + anonymize_guardrail._replace_user_message_content( request_data, "My phone is *******" ) # Should replace the last user message - assert result["messages"][-1]["content"] == "My phone is *******" - assert result["messages"][1]["content"] == "My email is test@example.com" # Unchanged + assert request_data["messages"][-1]["content"] == "My phone is *******" + assert request_data["messages"][1]["content"] == "My email is test@example.com" # Unchanged def test_replace_llm_response_content(self, anonymize_guardrail): """Test _replace_llm_response_content""" @@ -1080,11 +1080,11 @@ class TestNomaAnonymizationLogic: usage={"prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30}, ) - result = anonymize_guardrail._replace_llm_response_content( + anonymize_guardrail._replace_llm_response_content( response, "Your email is *******" ) - assert result.choices[0].message.content == "Your email is *******" + assert response.choices[0].message.content == "Your email is *******" class TestNomaAnonymizationFlow: @@ -1446,3 +1446,63 @@ class TestNomaAnonymizationFlow: data=request_data, call_type="completion", ) + + @pytest.mark.asyncio + async def test_anonymization_llm_response_no_anonymized_content_available( + self, anonymize_guardrail, mock_user_api_key_dict + ): + """Test behavior when LLM response has no anonymized content available""" + request_data = { + "messages": [{"role": "user", "content": "What's your email?"}], + "litellm_call_id": "test-call-id", + } + + # Create LLM response with test data + llm_response = ModelResponse( + id="test-response-id", + choices=[ + Choices( + finish_reason="stop", + index=0, + message=Message( + content="My email is admin@company.com", role="assistant" + ), + ) + ], + created=1234567890, + model="gpt-3.5-turbo", + object="chat.completion", + system_fingerprint=None, + usage={"prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30}, + ) + + # Mock Noma API response with no anonymized content available + noma_response = { + "originalResponse": { + "response": { + "dataDetector": { + "dataType1": {"result": True}, + }, + "contentDetector": {"result": False}, + }, + }, + "verdict": False, + } + + mock_response = MagicMock() + mock_response.json.return_value = noma_response + mock_response.raise_for_status = MagicMock() + + # Update guardrail to use post_call event hook + anonymize_guardrail.event_hook = "post_call" + + with patch.object( + anonymize_guardrail.async_handler, "post", return_value=mock_response + ): + # Should raise NomaBlockedMessage because no anonymized content available for LLM response + with pytest.raises(NomaBlockedMessage): + await anonymize_guardrail.async_post_call_success_hook( + data=request_data, + user_api_key_dict=mock_user_api_key_dict, + response=llm_response, + ) From ee5a9d0aa0588821bce3bb8575ab1989bec24238 Mon Sep 17 00:00:00 2001 From: Din Date: Thu, 11 Sep 2025 14:53:37 +0100 Subject: [PATCH 35/73] propagate execution context into logging tasks --- litellm/litellm_core_utils/logging_worker.py | 81 ++++++++++++------- .../test_litellm_logging.py | 70 ++++++++++++++++ .../litellm_core_utils/test_logging_worker.py | 79 ++++++++++++++++++ 3 files changed, 202 insertions(+), 28 deletions(-) diff --git a/litellm/litellm_core_utils/logging_worker.py b/litellm/litellm_core_utils/logging_worker.py index 3f83719dd32..16860ad6852 100644 --- a/litellm/litellm_core_utils/logging_worker.py +++ b/litellm/litellm_core_utils/logging_worker.py @@ -1,10 +1,21 @@ import asyncio import contextlib -from typing import Coroutine, Optional +import contextvars +from typing import Coroutine, Optional, TypedDict from litellm._logging import verbose_logger +class LoggingTask(TypedDict): + """ + A logging task with its associated context to ensure logging is executed in + the original task's context. + """ + + coroutine: Coroutine + context: contextvars.Context + + class LoggingWorker: """ A simple, async logging worker that processes log coroutines in the background. @@ -13,77 +24,84 @@ class LoggingWorker: This leads to a +200 RPS performance improvement when using LiteLLM Python SDK or Proxy Server. - Use this to queue coroutine tasks that are not critical to the main flow of the application. e.g Success/Error callbacks, logging, etc. """ + LOGGING_WORKER_MAX_QUEUE_SIZE = 50_000 LOGGING_WORKER_MAX_TIME_PER_COROUTINE = 20.0 MAX_ITERATIONS_TO_CLEAR_QUEUE = 200 MAX_TIME_TO_CLEAR_QUEUE = 5.0 - + def __init__( - self, - timeout: float = LOGGING_WORKER_MAX_TIME_PER_COROUTINE, + self, + timeout: float = LOGGING_WORKER_MAX_TIME_PER_COROUTINE, max_queue_size: int = LOGGING_WORKER_MAX_QUEUE_SIZE, ): self.timeout = timeout self.max_queue_size = max_queue_size - self._queue: Optional[asyncio.Queue] = None + self._queue: Optional[asyncio.Queue[LoggingTask]] = None self._worker_task: Optional[asyncio.Task] = None - + def _ensure_queue(self) -> None: """Initialize the queue if it doesn't exist.""" if self._queue is None: self._queue = asyncio.Queue(maxsize=self.max_queue_size) - + def start(self) -> None: """Start the logging worker. Idempotent - safe to call multiple times.""" self._ensure_queue() if self._worker_task is None or self._worker_task.done(): self._worker_task = asyncio.create_task(self._worker_loop()) - + async def _worker_loop(self) -> None: """Main worker loop that processes log coroutines sequentially.""" try: if self._queue is None: return - + while True: # Process one coroutine at a time to keep event loop load predictable - coroutine = await self._queue.get() + task = await self._queue.get() try: - await asyncio.wait_for(coroutine, timeout=self.timeout) + # Run the coroutine in its original context + await asyncio.wait_for( + task["context"].run(asyncio.create_task, task["coroutine"]), + timeout=self.timeout, + ) except Exception as e: verbose_logger.exception(f"LoggingWorker error: {e}") pass finally: self._queue.task_done() - + except asyncio.CancelledError: verbose_logger.debug("LoggingWorker cancelled during shutdown") # Attempt to clear remaining items to prevent "never awaited" warnings await self.clear_queue() - + def enqueue(self, coroutine: Coroutine) -> None: """ - Add a coroutine to the logging queue. + Add a coroutine to the logging queue. Hot path: never blocks, drops logs if queue is full. """ if self._queue is None: return - + try: - self._queue.put_nowait(coroutine) + # Capture the current context when enqueueing + task = LoggingTask(coroutine=coroutine, context=contextvars.copy_context()) + self._queue.put_nowait(task) except asyncio.QueueFull as e: verbose_logger.exception(f"LoggingWorker queue is full: {e}") # Drop logs on overload to protect request throughput pass - + def ensure_initialized_and_enqueue(self, async_coroutine: Coroutine): """ Ensure the logging worker is initialized and enqueue the coroutine. """ self.start() self.enqueue(async_coroutine) - + async def stop(self) -> None: """Stop the logging worker and clean up resources.""" if self._worker_task: @@ -91,34 +109,42 @@ class LoggingWorker: with contextlib.suppress(Exception): await self._worker_task self._worker_task = None - + async def flush(self) -> None: """Flush the logging queue.""" if self._queue is None: return while not self._queue.empty(): await self._queue.join() - + async def clear_queue(self): """ Clear the queue with a maximum time limit. """ if self._queue is None: return - + start_time = asyncio.get_event_loop().time() - + for _ in range(self.MAX_ITERATIONS_TO_CLEAR_QUEUE): # Check if we've exceeded the maximum time - if asyncio.get_event_loop().time() - start_time >= self.MAX_TIME_TO_CLEAR_QUEUE: - verbose_logger.warning(f"clear_queue exceeded max_time of {self.MAX_TIME_TO_CLEAR_QUEUE}s, stopping early") + if ( + asyncio.get_event_loop().time() - start_time + >= self.MAX_TIME_TO_CLEAR_QUEUE + ): + verbose_logger.warning( + f"clear_queue exceeded max_time of {self.MAX_TIME_TO_CLEAR_QUEUE}s, stopping early" + ) break - + try: - coroutine = self._queue.get_nowait() + task = self._queue.get_nowait() # Await the coroutine to properly execute and avoid "never awaited" warnings try: - await asyncio.wait_for(coroutine, timeout=self.timeout) + await asyncio.wait_for( + task["context"].run(asyncio.create_task, task["coroutine"]), + timeout=self.timeout, + ) except Exception: # Suppress errors during cleanup pass @@ -129,4 +155,3 @@ class LoggingWorker: # Global instance for backward compatibility GLOBAL_LOGGING_WORKER = LoggingWorker() - diff --git a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py index 48a22dcc8af..fa5164b9c16 100644 --- a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py +++ b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py @@ -467,3 +467,73 @@ async def test_e2e_generate_cold_storage_object_key_not_configured(): assert result is None +@pytest.mark.asyncio +async def test_logging_opentelemetry_context_propagation(): + """ + Test that OpenTelemtry context propagation works with async completion. + """ + import asyncio + import litellm + + from litellm.integrations.custom_logger import CustomLogger + from opentelemetry import trace + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.sdk.trace.export import SimpleSpanProcessor + from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter + + provider = TracerProvider() + exporter = InMemorySpanExporter() + provider.add_span_processor(SimpleSpanProcessor(exporter)) + trace.set_tracer_provider(provider) + tracer = trace.get_tracer(__name__) + + class MockOpenTelemetryLogger(CustomLogger): + async def async_log_success_event(self, kwargs, response_obj, start_time, end_time): + span = tracer.start_span(start_time=start_time.timestamp() * 1e9, name="async_log_success_event") + span.end(end_time=end_time) + + + mock_logging_obj = MockOpenTelemetryLogger() + + litellm.callbacks = [mock_logging_obj] + + with tracer.start_as_current_span("span_1") as span: + span_1_id = span.get_span_context().span_id + await litellm.acompletion( + max_tokens=100, + messages=[{"role": "user", "content": "Hey"}], + model="openai/codex-mini-latest", + mock_response="Hello, world!", + ) + + + with tracer.start_as_current_span("span_2") as span: + span_2_id = span.get_span_context().span_id + await litellm.acompletion( + max_tokens=100, + messages=[{"role": "user", "content": "Hey"}], + model="openai/codex-mini-latest", + mock_response="Hello, world!", + ) + + await asyncio.sleep(1) + spans = exporter.get_finished_spans() + assert len(spans) == 4 + assert span_1_id != span_2_id + sorted_spans = sorted(list(spans), key=lambda x: x.start_time or 0) + + assert sorted_spans[0].name == "span_1" + assert sorted_spans[1].name == "async_log_success_event" + assert sorted_spans[2].name == "span_2" + assert sorted_spans[3].name == "async_log_success_event" + + first_span_context = sorted_spans[0].get_span_context() + assert first_span_context is not None and first_span_context.span_id == span_1_id + second_span_context = sorted_spans[2].get_span_context() + assert second_span_context is not None and second_span_context.span_id == span_2_id + first_completion_span_parent = sorted_spans[1].parent + assert first_completion_span_parent is not None and first_completion_span_parent.span_id == span_1_id + + # This check would fail without the proper context propagation, and span[3] would end up with span_1_id as the parent + second_completion_span_parent = sorted_spans[3].parent + assert second_completion_span_parent is not None and second_completion_span_parent.span_id == span_2_id diff --git a/tests/test_litellm/litellm_core_utils/test_logging_worker.py b/tests/test_litellm/litellm_core_utils/test_logging_worker.py index 24c77339025..47e626e04af 100644 --- a/tests/test_litellm/litellm_core_utils/test_logging_worker.py +++ b/tests/test_litellm/litellm_core_utils/test_logging_worker.py @@ -2,6 +2,7 @@ Tests for the LoggingWorker class to ensure graceful shutdown handling. """ import asyncio +import contextvars import pytest from unittest.mock import AsyncMock, patch @@ -139,3 +140,81 @@ class TestLoggingWorker: # Should have logged queue full exceptions exception_calls = [call for call in mock_logger.exception.call_args_list if "queue is full" in str(call)] assert len(exception_calls) > 0 + + @pytest.mark.asyncio + async def test_context_propagation(self, logging_worker): + """Test that enqueued tasks execute in their original context.""" + # Create a context variable for testing + test_context_var: contextvars.ContextVar[str] = contextvars.ContextVar('test_context_var') + + # Track results from multiple tasks + task_results = [] + + async def test_task(task_id: str): + """A test coroutine that checks if it can access the context variable.""" + # Sleep a bit to simulate real work and ensure context persists + await asyncio.sleep(0.1) + + try: + # Try to get the context variable value + value = test_context_var.get() + task_results.append({ + 'task_id': task_id, + 'context_value': value, + 'context_accessible': True + }) + except LookupError: + # Context variable not found + task_results.append({ + 'task_id': task_id, + 'context_accessible': False, + 'context_value': None + }) + + # Start the logging worker + logging_worker.start() + + # Create two separate contexts and enqueue tasks from each + + # Context 1: Set context var to "context_1" + ctx1 = contextvars.copy_context() + ctx1.run(test_context_var.set, "context_1") + ctx1.run(logging_worker.enqueue, test_task("task_1")) + + # Context 2: Set context var to "context_2" + ctx2 = contextvars.copy_context() + ctx2.run(test_context_var.set, "context_2") + ctx2.run(logging_worker.enqueue, test_task("task_2")) + + # Context 3: No context variable set (should get LookupError) + ctx3 = contextvars.copy_context() + ctx3.run(logging_worker.enqueue, test_task("task_3")) + + # Wait for all tasks to be processed + await asyncio.sleep(0.5) + + # Stop the worker + await logging_worker.stop() + + # Sort results by task_id for consistent testing + task_results.sort(key=lambda x: x['task_id']) + + # Verify that each task saw its own context + assert len(task_results) == 3, f"Expected 3 results, got {len(task_results)}" + + # Task 1 should see "context_1" + task1_result = next((r for r in task_results if r['task_id'] == 'task_1'), None) + assert task1_result is not None, "Task 1 result not found" + assert task1_result['context_accessible'] is True, "Task 1 should have access to context variable" + assert task1_result['context_value'] == "context_1", f"Task 1 should see 'context_1', got: {task1_result['context_value']}" + + # Task 2 should see "context_2" + task2_result = next((r for r in task_results if r['task_id'] == 'task_2'), None) + assert task2_result is not None, "Task 2 result not found" + assert task2_result['context_accessible'] is True, "Task 2 should have access to context variable" + assert task2_result['context_value'] == "context_2", f"Task 2 should see 'context_2', got: {task2_result['context_value']}" + + # Task 3 should not have access to the context variable + task3_result = next((r for r in task_results if r['task_id'] == 'task_3'), None) + assert task3_result is not None, "Task 3 result not found" + assert task3_result['context_accessible'] is False, "Task 3 should not have access to context variable" From 090e0fddf4d1b51cdd19afb839393eb41ed25105 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Thu, 11 Sep 2025 22:20:54 +0530 Subject: [PATCH 36/73] fix vertex ai file upload --- .../openai_files_endpoints/files_endpoints.py | 23 ++ .../test_files_endpoint.py | 173 +++++++++++ .../test_vertex_ai_files_regression.py | 271 ++++++++++++++++++ 3 files changed, 467 insertions(+) create mode 100644 tests/test_litellm/proxy/openai_files_endpoint/test_vertex_ai_files_regression.py diff --git a/litellm/proxy/openai_files_endpoints/files_endpoints.py b/litellm/proxy/openai_files_endpoints/files_endpoints.py index f2a7ccccdf9..143e208a94b 100644 --- a/litellm/proxy/openai_files_endpoints/files_endpoints.py +++ b/litellm/proxy/openai_files_endpoints/files_endpoints.py @@ -69,6 +69,29 @@ def get_files_provider_config( ): global files_config if custom_llm_provider == "vertex_ai": + # For Vertex AI, extract config from model_list instead of files_config + from litellm.proxy.proxy_server import proxy_config + + if hasattr(proxy_config, "config") and "model_list" in proxy_config.config: + for model in proxy_config.config["model_list"]: + if isinstance(model, dict) and "litellm_params" in model: + litellm_params = model["litellm_params"] + if litellm_params.get("model", "").startswith("vertex_ai/"): + # Extract vertex_ai specific parameters + vertex_config = {} + if "vertex_project" in litellm_params: + vertex_config["vertex_project"] = litellm_params[ + "vertex_project" + ] + if "vertex_location" in litellm_params: + vertex_config["vertex_location"] = litellm_params[ + "vertex_location" + ] + if "vertex_credentials" in litellm_params: + vertex_config["vertex_credentials"] = litellm_params[ + "vertex_credentials" + ] + return vertex_config return None if files_config is None: raise ValueError("files_settings is not set, set it on your config.yaml file.") diff --git a/tests/test_litellm/proxy/openai_files_endpoint/test_files_endpoint.py b/tests/test_litellm/proxy/openai_files_endpoint/test_files_endpoint.py index 710e4265013..73c5c51ab91 100644 --- a/tests/test_litellm/proxy/openai_files_endpoint/test_files_endpoint.py +++ b/tests/test_litellm/proxy/openai_files_endpoint/test_files_endpoint.py @@ -476,3 +476,176 @@ def test_create_file_for_each_model( openai_call_found = True break assert openai_call_found, "OpenAI call not found with expected parameters" + + +def test_get_files_provider_config_vertex_ai_with_model_list(): + """ + Test that get_files_provider_config correctly extracts Vertex AI config from model_list + This test verifies the fix for the proxy file upload issue + """ + from litellm.proxy.openai_files_endpoints.files_endpoints import get_files_provider_config, files_config + from litellm.proxy.proxy_server import proxy_config + + # Mock the proxy_config with a model_list containing Vertex AI configuration + mock_config = { + 'model_list': [ + { + 'model_name': 'gemini-2.5-flash', + 'litellm_params': { + 'model': 'vertex_ai/gemini-2.5-flash', + 'vertex_project': 'test-project-123', + 'vertex_location': 'us-central1', + 'vertex_credentials': '/path/to/service_account.json' + } + }, + { + 'model_name': 'gpt-3.5-turbo', + 'litellm_params': { + 'model': 'openai/gpt-3.5-turbo', + 'api_key': 'test-key' + } + } + ] + } + + # Mock proxy_config.config + original_config = getattr(proxy_config, 'config', None) + proxy_config.config = mock_config + + # Mock files_config to avoid ValueError for non-vertex_ai providers + original_files_config = files_config + import litellm.proxy.openai_files_endpoints.files_endpoints + litellm.proxy.openai_files_endpoints.files_endpoints.files_config = [] + + try: + # Test that vertex_ai provider returns the correct config + result = get_files_provider_config('vertex_ai') + + assert result is not None, "get_files_provider_config should return config for vertex_ai" + assert result['vertex_project'] == 'test-project-123' + assert result['vertex_location'] == 'us-central1' + assert result['vertex_credentials'] == '/path/to/service_account.json' + + # Test that non-vertex_ai providers still work as before + result_openai = get_files_provider_config('openai') + assert result_openai is None # Should return None when files_config is empty + + finally: + # Restore original config + if original_config is not None: + proxy_config.config = original_config + else: + delattr(proxy_config, 'config') + + # Restore original files_config + litellm.proxy.openai_files_endpoints.files_endpoints.files_config = original_files_config + + +def test_get_files_provider_config_vertex_ai_no_model_list(): + """ + Test that get_files_provider_config returns None when no model_list is available + This ensures graceful handling when proxy_config is not properly initialized + """ + from litellm.proxy.openai_files_endpoints.files_endpoints import get_files_provider_config + from litellm.proxy.proxy_server import proxy_config + + # Mock proxy_config without model_list + original_config = getattr(proxy_config, 'config', None) + proxy_config.config = {} + + try: + result = get_files_provider_config('vertex_ai') + assert result is None, "get_files_provider_config should return None when no model_list" + + finally: + # Restore original config + if original_config is not None: + proxy_config.config = original_config + else: + delattr(proxy_config, 'config') + + +def test_get_files_provider_config_vertex_ai_no_vertex_models(): + """ + Test that get_files_provider_config returns None when no vertex_ai models are in model_list + This ensures the function handles cases where only non-vertex models are configured + """ + from litellm.proxy.openai_files_endpoints.files_endpoints import get_files_provider_config + from litellm.proxy.proxy_server import proxy_config + + # Mock the proxy_config with a model_list containing only non-Vertex AI models + mock_config = { + 'model_list': [ + { + 'model_name': 'gpt-3.5-turbo', + 'litellm_params': { + 'model': 'openai/gpt-3.5-turbo', + 'api_key': 'test-key' + } + }, + { + 'model_name': 'claude-3', + 'litellm_params': { + 'model': 'anthropic/claude-3', + 'api_key': 'test-key' + } + } + ] + } + + # Mock proxy_config.config + original_config = getattr(proxy_config, 'config', None) + proxy_config.config = mock_config + + try: + result = get_files_provider_config('vertex_ai') + assert result is None, "get_files_provider_config should return None when no vertex_ai models in model_list" + + finally: + # Restore original config + if original_config is not None: + proxy_config.config = original_config + else: + delattr(proxy_config, 'config') + + +def test_get_files_provider_config_vertex_ai_partial_config(): + """ + Test that get_files_provider_config handles partial Vertex AI configuration gracefully + This ensures the function works even when some vertex_ai parameters are missing + """ + from litellm.proxy.openai_files_endpoints.files_endpoints import get_files_provider_config + from litellm.proxy.proxy_server import proxy_config + + # Mock the proxy_config with partial Vertex AI configuration + mock_config = { + 'model_list': [ + { + 'model_name': 'gemini-2.5-flash', + 'litellm_params': { + 'model': 'vertex_ai/gemini-2.5-flash', + 'vertex_project': 'test-project-123', + # Missing vertex_location and vertex_credentials + } + } + ] + } + + # Mock proxy_config.config + original_config = getattr(proxy_config, 'config', None) + proxy_config.config = mock_config + + try: + result = get_files_provider_config('vertex_ai') + + assert result is not None, "get_files_provider_config should return config even with partial vertex_ai params" + assert result['vertex_project'] == 'test-project-123' + assert 'vertex_location' not in result + assert 'vertex_credentials' not in result + + finally: + # Restore original config + if original_config is not None: + proxy_config.config = original_config + else: + delattr(proxy_config, 'config') diff --git a/tests/test_litellm/proxy/openai_files_endpoint/test_vertex_ai_files_regression.py b/tests/test_litellm/proxy/openai_files_endpoint/test_vertex_ai_files_regression.py new file mode 100644 index 00000000000..e67d70d7602 --- /dev/null +++ b/tests/test_litellm/proxy/openai_files_endpoint/test_vertex_ai_files_regression.py @@ -0,0 +1,271 @@ +""" +Regression tests for Vertex AI file upload functionality in the proxy. + +This module contains tests to ensure that the fix for Vertex AI file uploads +in the proxy server continues to work and prevents regression of the issue +where get_files_provider_config returned None for vertex_ai provider. +""" + +import pytest +from unittest.mock import Mock, patch +from litellm.proxy.openai_files_endpoints.files_endpoints import get_files_provider_config + + +def test_vertex_ai_files_provider_config_never_returns_none_when_configured(): + """ + Regression test: Ensure that get_files_provider_config never returns None + for vertex_ai when properly configured in model_list. + + This test prevents regression of the bug where vertex_ai provider + always returned None, causing "Could not resolve project_id" errors. + """ + from litellm.proxy.proxy_server import proxy_config + + # Mock the proxy_config with a proper Vertex AI configuration + mock_config = { + 'model_list': [ + { + 'model_name': 'gemini-2.5-flash', + 'litellm_params': { + 'model': 'vertex_ai/gemini-2.5-flash', + 'vertex_project': 'test-project-123', + 'vertex_location': 'us-central1', + 'vertex_credentials': '/path/to/service_account.json' + } + } + ] + } + + # Mock proxy_config.config + original_config = getattr(proxy_config, 'config', None) + proxy_config.config = mock_config + + try: + result = get_files_provider_config('vertex_ai') + + # This should NEVER be None when vertex_ai is properly configured + assert result is not None, ( + "CRITICAL REGRESSION: get_files_provider_config returned None for vertex_ai " + "when it should return configuration. This would cause 'Could not resolve project_id' errors." + ) + + # Verify all expected parameters are present + assert 'vertex_project' in result + assert 'vertex_location' in result + assert 'vertex_credentials' in result + + finally: + # Restore original config + if original_config is not None: + proxy_config.config = original_config + else: + delattr(proxy_config, 'config') + + +def test_vertex_ai_files_provider_config_handles_multiple_vertex_models(): + """ + Test that get_files_provider_config correctly handles multiple Vertex AI models + in the model_list and returns configuration from the first one found. + """ + from litellm.proxy.proxy_server import proxy_config + + # Mock the proxy_config with multiple Vertex AI models + mock_config = { + 'model_list': [ + { + 'model_name': 'gemini-1.5-flash', + 'litellm_params': { + 'model': 'vertex_ai/gemini-1.5-flash', + 'vertex_project': 'project-1', + 'vertex_location': 'us-east1', + 'vertex_credentials': '/path/to/creds1.json' + } + }, + { + 'model_name': 'gemini-2.5-flash', + 'litellm_params': { + 'model': 'vertex_ai/gemini-2.5-flash', + 'vertex_project': 'project-2', + 'vertex_location': 'us-central1', + 'vertex_credentials': '/path/to/creds2.json' + } + } + ] + } + + # Mock proxy_config.config + original_config = getattr(proxy_config, 'config', None) + proxy_config.config = mock_config + + try: + result = get_files_provider_config('vertex_ai') + + assert result is not None, "Should return config when multiple vertex_ai models are present" + + # Should return config from the first vertex_ai model found + assert result['vertex_project'] == 'project-1' + assert result['vertex_location'] == 'us-east1' + assert result['vertex_credentials'] == '/path/to/creds1.json' + + finally: + # Restore original config + if original_config is not None: + proxy_config.config = original_config + else: + delattr(proxy_config, 'config') + + +def test_vertex_ai_files_provider_config_ignores_non_vertex_models(): + """ + Test that get_files_provider_config correctly identifies vertex_ai models + and ignores other model types when searching for configuration. + """ + from litellm.proxy.proxy_server import proxy_config + + # Mock the proxy_config with mixed model types + mock_config = { + 'model_list': [ + { + 'model_name': 'gpt-3.5-turbo', + 'litellm_params': { + 'model': 'openai/gpt-3.5-turbo', + 'api_key': 'test-key' + } + }, + { + 'model_name': 'gemini-2.5-flash', + 'litellm_params': { + 'model': 'vertex_ai/gemini-2.5-flash', + 'vertex_project': 'test-project', + 'vertex_location': 'us-central1', + 'vertex_credentials': '/path/to/creds.json' + } + }, + { + 'model_name': 'claude-3', + 'litellm_params': { + 'model': 'anthropic/claude-3', + 'api_key': 'test-key' + } + } + ] + } + + # Mock proxy_config.config + original_config = getattr(proxy_config, 'config', None) + proxy_config.config = mock_config + + try: + result = get_files_provider_config('vertex_ai') + + assert result is not None, "Should find vertex_ai config even with mixed model types" + assert result['vertex_project'] == 'test-project' + + finally: + # Restore original config + if original_config is not None: + proxy_config.config = original_config + else: + delattr(proxy_config, 'config') + + +def test_vertex_ai_files_provider_config_handles_malformed_model_list(): + """ + Test that get_files_provider_config gracefully handles malformed model_list entries + without crashing. + """ + from litellm.proxy.proxy_server import proxy_config + + # Mock the proxy_config with malformed entries + mock_config = { + 'model_list': [ + # Missing litellm_params + { + 'model_name': 'gemini-2.5-flash' + }, + # Missing model field + { + 'model_name': 'gemini-1.5-flash', + 'litellm_params': { + 'vertex_project': 'test-project' + } + }, + # Valid vertex_ai model + { + 'model_name': 'gemini-2.0-flash', + 'litellm_params': { + 'model': 'vertex_ai/gemini-2.0-flash', + 'vertex_project': 'test-project', + 'vertex_location': 'us-central1', + 'vertex_credentials': '/path/to/creds.json' + } + } + ] + } + + # Mock proxy_config.config + original_config = getattr(proxy_config, 'config', None) + proxy_config.config = mock_config + + try: + result = get_files_provider_config('vertex_ai') + + # Should still work and find the valid vertex_ai model + assert result is not None, "Should handle malformed entries and find valid vertex_ai model" + assert result['vertex_project'] == 'test-project' + + finally: + # Restore original config + if original_config is not None: + proxy_config.config = original_config + else: + delattr(proxy_config, 'config') + + +def test_vertex_ai_files_provider_config_old_behavior_regression(): + """ + Regression test: Ensure that the old behavior of always returning None + for vertex_ai provider is completely eliminated. + + This test specifically checks that the function no longer has the old + hardcoded return None for vertex_ai. + """ + from litellm.proxy.proxy_server import proxy_config + + # Mock the proxy_config with a minimal but valid Vertex AI configuration + mock_config = { + 'model_list': [ + { + 'model_name': 'gemini-2.5-flash', + 'litellm_params': { + 'model': 'vertex_ai/gemini-2.5-flash', + 'vertex_project': 'minimal-project' + } + } + ] + } + + # Mock proxy_config.config + original_config = getattr(proxy_config, 'config', None) + proxy_config.config = mock_config + + try: + result = get_files_provider_config('vertex_ai') + + # The old behavior would always return None here + # The new behavior should return the configuration + assert result is not None, ( + "REGRESSION DETECTED: The old behavior of returning None for vertex_ai " + "has returned. This indicates the fix has been reverted." + ) + + # Verify we get the expected configuration + assert isinstance(result, dict), "Result should be a dictionary" + assert 'vertex_project' in result, "Should contain vertex_project" + + finally: + # Restore original config + if original_config is not None: + proxy_config.config = original_config + else: + delattr(proxy_config, 'config') From e9e548d797ed857e9ebbb9e0f6cde82ee0e98d6d Mon Sep 17 00:00:00 2001 From: Burt Holzman Date: Fri, 5 Sep 2025 16:47:12 -0500 Subject: [PATCH 37/73] Fix provider budgets --- .../spend_management_endpoints.py | 20 ++-- .../test_spend_management_endpoints.py | 94 +++++++++++++++++++ 2 files changed, 108 insertions(+), 6 deletions(-) diff --git a/litellm/proxy/spend_tracking/spend_management_endpoints.py b/litellm/proxy/spend_tracking/spend_management_endpoints.py index 7376c3a402b..affffaeea09 100644 --- a/litellm/proxy/spend_tracking/spend_management_endpoints.py +++ b/litellm/proxy/spend_tracking/spend_management_endpoints.py @@ -10,6 +10,7 @@ from fastapi import APIRouter, Depends, HTTPException, status import litellm from litellm._logging import verbose_proxy_logger +from litellm.router_strategy.budget_limiter import RouterBudgetLimiting from litellm.proxy._types import * from litellm.proxy._types import ProviderBudgetResponse, ProviderBudgetResponseObject from litellm.proxy.auth.user_api_key_auth import user_api_key_auth @@ -2765,16 +2766,23 @@ async def provider_budgets() -> ProviderBudgetResponse: provider_budget_response_dict: Dict[str, ProviderBudgetResponseObject] = {} for _provider, _budget_info in provider_budget_config.items(): - if llm_router.router_budget_logger is None: + router_budget_logger = next( + ( + cb + for cb in (llm_router.optional_callbacks or []) + if isinstance(cb, RouterBudgetLimiting) + ), + None, + ) + if router_budget_logger is None: raise ValueError("No router budget logger found") _provider_spend = ( - await llm_router.router_budget_logger._get_current_provider_spend( + await router_budget_logger._get_current_provider_spend(_provider) or 0.0 + ) + _provider_budget_ttl = ( + await router_budget_logger._get_current_provider_budget_reset_at( _provider ) - or 0.0 - ) - _provider_budget_ttl = await llm_router.router_budget_logger._get_current_provider_budget_reset_at( - _provider ) provider_budget_response_object = ProviderBudgetResponseObject( budget_limit=_budget_info.max_budget, diff --git a/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py b/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py index d9ae0665ead..069896e9fb4 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py +++ b/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py @@ -18,7 +18,9 @@ import litellm from litellm.proxy._types import SpendLogsPayload from litellm.proxy.hooks.proxy_track_cost_callback import _ProxyDBLogger from litellm.proxy.proxy_server import app, prisma_client +from litellm.proxy.spend_tracking import spend_management_endpoints from litellm.router import Router +from litellm.types.utils import BudgetConfig ignored_keys = [ "request_id", @@ -32,6 +34,18 @@ ignored_keys = [ "metadata.cold_storage_object_key", ] +MODEL_LIST = [ + { + "model_name": "azure-gpt-4o", + "litellm_params": { + "model": "azure/gpt-4o-mini", + "mock_response": "Hello, world!", + "tags": ["default"], + "base_model": "gpt-4o-mini", + }, + }, +] + @pytest.fixture def client(): @@ -43,6 +57,19 @@ def add_anthropic_api_key_to_env(monkeypatch): monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-api03-1234567890") +@pytest.fixture +def disable_budget_sync(monkeypatch): + """Disable periodic sync during tests""" + + async def noop(*a, **k): + return None + + monkeypatch.setattr( + "litellm.router_strategy.budget_limiter.RouterBudgetLimiting.periodic_sync_in_memory_spend_with_redis", + noop, + ) + + @pytest.mark.asyncio async def test_ui_view_spend_logs_with_user_id(client, monkeypatch): # Mock data for the test @@ -1318,3 +1345,70 @@ async def test_view_spend_tags_no_database(client, monkeypatch): # Check the actual error message structure assert "error" in data assert "Database not connected" in data["error"]["message"] + + +@pytest.mark.asyncio +async def test_provider_budget_under(disable_budget_sync): + """Test that router allows completion when under budget""" + provider_budget_config = { + "azure": BudgetConfig(max_budget=0.01, budget_duration="10d") + } + + router = Router( + enable_pre_call_checks=True, + provider_budget_config=provider_budget_config, + model_list=MODEL_LIST, + ) + + response = await router.acompletion( + model="azure-gpt-4o", + messages=[{"role": "user", "content": "Hello, world!"}], + ) + + assert response is not None + + +@pytest.mark.asyncio +async def test_provider_budget_over(disable_budget_sync): + """Test that router allows completion when over budget""" + provider_budget_config = { + "azure": BudgetConfig(max_budget=-0.01, budget_duration="10d") + } + + router = Router( + num_retries=0, + enable_pre_call_checks=True, + provider_budget_config=provider_budget_config, + model_list=MODEL_LIST, + ) + + with pytest.raises(Exception) as e: + response = await router.acompletion( + model="azure-gpt-4o", + messages=[{"role": "user", "content": "Hello, world!"}], + ) + assert "Exceeded budget for provider" in str(e.value) + + +@pytest.mark.asyncio +async def test_provider_budget_provider_budgets(disable_budget_sync): + """Test that provider_budgets() returns correct values""" + provider = "azure" + max_budget = -0.01 + budget_duration = "10d" + provider_budget_config = { + provider: BudgetConfig(max_budget=max_budget, budget_duration=budget_duration) + } + + router = Router( + num_retries=0, + enable_pre_call_checks=True, + provider_budget_config=provider_budget_config, + model_list=MODEL_LIST, + ) + + with patch("litellm.proxy.proxy_server.llm_router", router): + response = await spend_management_endpoints.provider_budgets() + provider_budget_response = response.providers[provider] + assert provider_budget_response.budget_limit == max_budget + assert provider_budget_response.time_period == budget_duration From c6626559a24a972754ac8dfa7c5d1d534a293965 Mon Sep 17 00:00:00 2001 From: Sashanken Date: Thu, 11 Sep 2025 13:05:48 -0700 Subject: [PATCH 38/73] Fixed Log Tab Key Alias filtering inaccurately for failed logs --- .../spend_management_endpoints.py | 9 + package-lock.json | 7979 ++++++++++++++++- package.json | 5 +- .../ui_unit_tests/log_filter_logic.test.tsx | 87 + .../ui_unit_tests/package-lock.json | 103 +- .../src/components/networking.tsx | 4 +- .../components/view_logs/log_filter_logic.tsx | 37 +- 7 files changed, 8186 insertions(+), 38 deletions(-) create mode 100644 tests/proxy_admin_ui_tests/ui_unit_tests/log_filter_logic.test.tsx diff --git a/litellm/proxy/spend_tracking/spend_management_endpoints.py b/litellm/proxy/spend_tracking/spend_management_endpoints.py index 7376c3a402b..897341ad383 100644 --- a/litellm/proxy/spend_tracking/spend_management_endpoints.py +++ b/litellm/proxy/spend_tracking/spend_management_endpoints.py @@ -1659,6 +1659,9 @@ async def ui_view_spend_logs( # noqa: PLR0915 model: Optional[str] = fastapi.Query( default=None, description="Filter logs by model" ), + key_alias: Optional[str] = fastapi.Query( + default=None, description="Filter logs by key alias" + ), ): """ View spend logs for UI with pagination support @@ -1726,6 +1729,12 @@ async def ui_view_spend_logs( # noqa: PLR0915 if model is not None: where_conditions["model"] = model + + if key_alias is not None: + where_conditions["metadata"] = { + "path": ["user_api_key_alias"], + "string_contains": key_alias + } if min_spend is not None or max_spend is not None: where_conditions["spend"] = {} diff --git a/package-lock.json b/package-lock.json index b271c5aa628..1b3c2a690a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,2700 @@ "react-copy-to-clipboard": "^5.1.0" }, "devDependencies": { - "@types/react-copy-to-clipboard": "^5.0.7" + "@babel/core": "^7.28.4", + "@babel/preset-env": "^7.28.3", + "@babel/preset-react": "^7.27.1", + "@babel/preset-typescript": "^7.27.1", + "@testing-library/jest-dom": "^6.8.0", + "@testing-library/react": "^14.3.1", + "@types/react-copy-to-clipboard": "^5.0.7", + "babel-jest": "^30.1.2", + "jest": "^29.7.0", + "jest-environment-jsdom": "^30.1.2" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", + "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "regexpu-core": "^6.2.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.10" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", + "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", + "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", + "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", + "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.4.tgz", + "integrity": "sha512-1yxmvN0MJHOhPVmAsmoW5liWwoILobu/d/ShymZmj867bAdxGbehIrew1DuLpw2Ukv+qDSSPQdYW1dLNE7t11A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", + "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", + "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz", + "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", + "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", + "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", + "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", + "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", + "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", + "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", + "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", + "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", + "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz", + "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz", + "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.0", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.0", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.3", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.0", + "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.28.0", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.3", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.27.1.tgz", + "integrity": "sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-transform-react-display-name": "^7.27.1", + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@babel/plugin-transform-react-pure-annotations": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract": { + "version": "30.1.2", + "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.1.2.tgz", + "integrity": "sha512-u8kTh/ZBl97GOmnGJLYK/1GuwAruMC4hoP6xuk/kwltmVWsA9u/6fH1/CsPVGt2O+Wn2yEjs8n1B1zZJ62Cx0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.1.2", + "@jest/fake-timers": "30.1.2", + "@jest/types": "30.0.5", + "@types/jsdom": "^21.1.7", + "@types/node": "*", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/@jest/environment": { + "version": "30.1.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.1.2.tgz", + "integrity": "sha512-N8t1Ytw4/mr9uN28OnVf0SYE2dGhaIxOVYcwsf9IInBKjvofAjbFRvedvBBlyTYk2knbJTiEjEJ2PyyDIBnd9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "30.1.2", + "@jest/types": "30.0.5", + "@types/node": "*", + "jest-mock": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/@jest/fake-timers": { + "version": "30.1.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.1.2.tgz", + "integrity": "sha512-Beljfv9AYkr9K+ETX9tvV61rJTY706BhBUtiaepQHeEGfe0DbpvUA5Z3fomwc5Xkhns6NWrcFDZn+72fLieUnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.5", + "@sinonjs/fake-timers": "^13.0.0", + "@types/node": "*", + "jest-message-util": "30.1.0", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/@sinclair/typebox": { + "version": "0.34.41", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", + "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/ci-info": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/jest-message-util": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.1.0.tgz", + "integrity": "sha512-HizKDGG98cYkWmaLUHChq4iN+oCENohQLb7Z5guBPumYs+/etonmNFlg1Ps6yN9LTPyZn+M+b/9BbnHx3WTMDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.0.5", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.0.5", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/jest-mock": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz", + "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.5", + "@types/node": "*", + "jest-util": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/jest-util": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", + "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.5", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/picomatch": { + "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" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/pretty-format": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", + "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "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", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "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" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "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.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@prisma/debug": { @@ -53,6 +2746,220 @@ "@prisma/debug": "5.17.0" } }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@testing-library/dom": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", + "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.1.3", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/@testing-library/dom/node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.8.0.tgz", + "integrity": "sha512-WgXcWzVM6idy5JaftTVC8Vs83NKRmGJz4Hqs4oyOuO2J4r/y79vvKZsb+CaGyCSEbUPI6OsewfPd0G1A0/TUZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/react": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.3.1.tgz", + "integrity": "sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^9.0.0", + "@types/react-dom": "^18.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jsdom": { + "version": "21.1.7", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", + "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/node": { + "version": "24.3.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.1.tgz", + "integrity": "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.10.0" + } + }, "node_modules/@types/prismjs": { "version": "1.26.5", "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", @@ -83,6 +2990,795 @@ "@types/react": "*" } }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-jest": { + "version": "30.1.2", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.1.2.tgz", + "integrity": "sha512-IQCus1rt9kaSh7PQxLYRY5NmkNrNlU2TpabzwV7T2jljnpdHOcmnYYv8QmE04Li4S3a2Lj8/yXyET5pBarPr6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "30.1.2", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.0", + "babel-preset-jest": "30.0.1", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0" + } + }, + "node_modules/babel-jest/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-jest/node_modules/@jest/transform": { + "version": "30.1.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.1.2.tgz", + "integrity": "sha512-UYYFGifSgfjujf1Cbd3iU/IQoSd6uwsj8XHj5DSDf5ERDcWMdJOPTkHWXj4U+Z/uMagyOQZ6Vne8C4nRIrCxqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.0.5", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.0", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.1.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.0.5", + "micromatch": "^4.0.8", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-jest/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-jest/node_modules/@sinclair/typebox": { + "version": "0.34.41", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", + "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-jest/node_modules/babel-plugin-istanbul": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/babel-jest/node_modules/ci-info": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-jest/node_modules/jest-haste-map": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.1.0.tgz", + "integrity": "sha512-JLeM84kNjpRkggcGpQLsV7B8W4LNUWz7oDNVnY1Vjj22b5/fAb3kk3htiD+4Na8bmJmjJR7rBtS2Rmq/NEcADg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.5", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.0.5", + "jest-worker": "30.1.0", + "micromatch": "^4.0.8", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/babel-jest/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-jest/node_modules/jest-util": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", + "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.5", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-jest/node_modules/jest-worker": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.1.0.tgz", + "integrity": "sha512-uvWcSjlwAAgIu133Tt77A05H7RIk3Ho8tZL50bQM2AkvLdluw9NG48lRCl3Dt+MOH719n/0nnb5YxUwcuJiKRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.0.5", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-jest/node_modules/picomatch": { + "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" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/babel-jest/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/babel-jest/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.0.1.tgz", + "integrity": "sha512-zTPME3pI50NsFW8ZBaVIOeAxzEY7XHlmWeXXu9srI+9kNfzCUTy8MFan46xOGZY8NZThMqq+e3qZUKsvXbasnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", + "@types/babel__core": "^7.20.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.0.1.tgz", + "integrity": "sha512-+YHejD5iTWI46cZmcc/YtX4gaKBtdqCHCVfuVinizVpbmyjO3zYmeuyFdfA8duRqQZfgCAMlsfmkVbJ+e2MAJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "30.0.1", + "babel-preset-current-node-syntax": "^1.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz", + "integrity": "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001737", + "electron-to-chromium": "^1.5.211", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001741", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz", + "integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -91,6 +3787,58 @@ "node": ">=6" } }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/copy-to-clipboard": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", @@ -99,17 +3847,2649 @@ "toggle-selection": "^1.0.6" } }, + "node_modules/core-js-compat": { + "version": "3.45.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.1.tgz", + "integrity": "sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.25.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "dev": true }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/dedent": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", + "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.215", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.215.tgz", + "integrity": "sha512-TIvGp57UpeNetj/wV/xpFNpWGb0b/ROw372lHPx5Aafx02gjTBtWnEEcaSX3W2dLM3OSdGGyHX/cHl01JQsLaQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "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" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/jest-config/node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-config/node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/jest-config/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-config/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-environment-jsdom": { + "version": "30.1.2", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.1.2.tgz", + "integrity": "sha512-LXsfAh5+mDTuXDONGl1ZLYxtJEaS06GOoxJb2arcJTjIfh1adYg8zLD8f6P0df8VmjvCaMrLmc1PgHUI/YUTbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.1.2", + "@jest/environment-jsdom-abstract": "30.1.2", + "@types/jsdom": "^21.1.7", + "@types/node": "*", + "jsdom": "^26.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-environment-jsdom/node_modules/@jest/environment": { + "version": "30.1.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.1.2.tgz", + "integrity": "sha512-N8t1Ytw4/mr9uN28OnVf0SYE2dGhaIxOVYcwsf9IInBKjvofAjbFRvedvBBlyTYk2knbJTiEjEJ2PyyDIBnd9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "30.1.2", + "@jest/types": "30.0.5", + "@types/node": "*", + "jest-mock": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@jest/fake-timers": { + "version": "30.1.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.1.2.tgz", + "integrity": "sha512-Beljfv9AYkr9K+ETX9tvV61rJTY706BhBUtiaepQHeEGfe0DbpvUA5Z3fomwc5Xkhns6NWrcFDZn+72fLieUnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.5", + "@sinonjs/fake-timers": "^13.0.0", + "@types/node": "*", + "jest-message-util": "30.1.0", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@sinclair/typebox": { + "version": "0.34.41", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", + "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-environment-jsdom/node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/jest-environment-jsdom/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-environment-jsdom/node_modules/ci-info": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-environment-jsdom/node_modules/jest-message-util": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.1.0.tgz", + "integrity": "sha512-HizKDGG98cYkWmaLUHChq4iN+oCENohQLb7Z5guBPumYs+/etonmNFlg1Ps6yN9LTPyZn+M+b/9BbnHx3WTMDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.0.5", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.0.5", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/jest-mock": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz", + "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.5", + "@types/node": "*", + "jest-util": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/jest-util": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", + "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.5", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/picomatch": { + "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" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-environment-jsdom/node_modules/pretty-format": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", + "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-leak-detector/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "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": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -121,6 +6501,187 @@ "loose-envify": "cli.js" } }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "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", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.20.tgz", + "integrity": "sha512-7gK6zSXEH6neM212JgfYFXe+GmZQM+fia5SsusuBIUgnPheLFBmIPhtFoAQRj8/7wASYQnbDlHPVwY0BefoFgA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "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" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nwsapi": { + "version": "2.2.22", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.22.tgz", + "integrity": "sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==", + "dev": true, + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -129,6 +6690,305 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "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/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "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" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "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" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, "node_modules/prism-react-renderer": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", @@ -156,6 +7016,20 @@ "node": ">=16.13" } }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -166,10 +7040,38 @@ "react-is": "^16.13.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", "peer": true, "dependencies": { "loose-envify": "^1.1.0" @@ -190,15 +7092,1084 @@ "react": "^15.3.0 || 16 || 17 || 18" } }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "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" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toggle-selection": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici-types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 640f15dc28c..6f1d3a0af00 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,9 @@ "react-copy-to-clipboard": "^5.1.0" }, "devDependencies": { - "@types/react-copy-to-clipboard": "^5.0.7" + "@testing-library/jest-dom": "^6.8.0", + "@testing-library/react": "^14.3.1", + "@types/react-copy-to-clipboard": "^5.0.7", + "jest": "^29.7.0" } } diff --git a/tests/proxy_admin_ui_tests/ui_unit_tests/log_filter_logic.test.tsx b/tests/proxy_admin_ui_tests/ui_unit_tests/log_filter_logic.test.tsx new file mode 100644 index 00000000000..81a627c40d2 --- /dev/null +++ b/tests/proxy_admin_ui_tests/ui_unit_tests/log_filter_logic.test.tsx @@ -0,0 +1,87 @@ +import { uiSpendLogsCall } from '../../../ui/litellm-dashboard/src/components/networking'; + +// Mock the networking module +jest.mock('../../../ui/litellm-dashboard/src/components/networking', () => ({ + uiSpendLogsCall: jest.fn(), +})); + +const mockUiSpendLogsCall = uiSpendLogsCall as jest.MockedFunction; + +describe('Key Alias Filtering Integration Test', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should call API with correct key_alias parameter', async () => { + // Mock API response with both success and failure logs + const mockResponse = { + data: [ + { request_id: 'req-1', status: 'success', metadata: { user_api_key_alias: 'test-key' } }, + { request_id: 'req-2', status: 'failure', metadata: { user_api_key_alias: 'test-key' } } + ], + total: 2, + page: 1, + page_size: 50, + total_pages: 1 + }; + + mockUiSpendLogsCall.mockResolvedValueOnce(mockResponse); + + // Simulate the API call that would happen when filtering by key alias + const result = await uiSpendLogsCall( + 'test-token', + undefined, + undefined, + undefined, + '2024-01-15 09:00:00', + '2024-01-15 11:00:00', + 1, + 50, + undefined, + undefined, + undefined, + undefined, + 'test-key-alias' // key_alias - this is the fix + ); + + // Verify the API was called correctly + expect(mockUiSpendLogsCall).toHaveBeenCalledWith( + 'test-token', + undefined, + undefined, + undefined, + '2024-01-15 09:00:00', + '2024-01-15 11:00:00', + 1, + 50, + undefined, + undefined, + undefined, + undefined, + 'test-key-alias' // The key assertion - this parameter should be passed through + ); + + // Verify response contains both success and failure logs + expect(result.data).toHaveLength(2); + expect(result.data[0].status).toBe('success'); + expect(result.data[1].status).toBe('failure'); + }); + + it('should pass undefined for empty key alias', async () => { + mockUiSpendLogsCall.mockResolvedValueOnce({ data: [], total: 0, page: 1, page_size: 50, total_pages: 0 }); + + await uiSpendLogsCall( + 'test-token', undefined, undefined, undefined, + '2024-01-15 09:00:00', '2024-01-15 11:00:00', + 1, 50, undefined, undefined, undefined, undefined, + undefined // Empty string should become undefined + ); + + expect(mockUiSpendLogsCall).toHaveBeenCalledWith( + 'test-token', undefined, undefined, undefined, + '2024-01-15 09:00:00', '2024-01-15 11:00:00', + 1, 50, undefined, undefined, undefined, undefined, + undefined // Should be undefined for empty key alias + ); + }); +}); \ No newline at end of file diff --git a/tests/proxy_admin_ui_tests/ui_unit_tests/package-lock.json b/tests/proxy_admin_ui_tests/ui_unit_tests/package-lock.json index 6ced636f23d..b8d706fcf84 100644 --- a/tests/proxy_admin_ui_tests/ui_unit_tests/package-lock.json +++ b/tests/proxy_admin_ui_tests/ui_unit_tests/package-lock.json @@ -8,13 +8,14 @@ "name": "ui-unit-tests", "version": "1.0.0", "dependencies": { - "antd": "^5.0.0", + "@ant-design/icons": "^5.0.0", + "antd": "^5.12.5", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { + "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@types/antd": "^1.0.0", "@types/jest": "^29.5.0", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", @@ -25,6 +26,13 @@ "typescript": "^5.0.0" } }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -1167,6 +1175,33 @@ "node": ">=14" } }, + "node_modules/@testing-library/jest-dom": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.8.0.tgz", + "integrity": "sha512-WgXcWzVM6idy5JaftTVC8Vs83NKRmGJz4Hqs4oyOuO2J4r/y79vvKZsb+CaGyCSEbUPI6OsewfPd0G1A0/TUZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, "node_modules/@testing-library/react": { "version": "14.3.1", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.3.1.tgz", @@ -1194,16 +1229,6 @@ "node": ">= 10" } }, - "node_modules/@types/antd": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@types/antd/-/antd-1.0.4.tgz", - "integrity": "sha512-gp4PGQckP1kNjj2H6juhjKIVwkpXwCIyIvOlwp2DC6geuhVpDHEEB5gwH4hJabVgBAFtrjBPJ58VIRV9VV9W2g==", - "deprecated": "This is a stub types definition. antd provides its own type definitions, so you do not need this installed.", - "dev": true, - "dependencies": { - "antd": "*" - } - }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -2081,6 +2106,13 @@ "node": ">= 8" } }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, "node_modules/cssom": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", @@ -2974,6 +3006,16 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -4559,6 +4601,16 @@ "node": ">=6" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -5552,6 +5604,20 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", @@ -5965,6 +6031,19 @@ "node": ">=6" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index c204ca8f893..8855b1b8435 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -2633,7 +2633,8 @@ export const uiSpendLogsCall = async ( user_id?: string, end_user?: string, status_filter?: string, - model?: string + model?: string, + keyAlias?: string ) => { try { // Construct base URL @@ -2652,6 +2653,7 @@ export const uiSpendLogsCall = async ( if (end_user) queryParams.append("end_user", end_user); if (status_filter) queryParams.append("status_filter", status_filter); if (model) queryParams.append("model", model); + if (keyAlias) queryParams.append("key_alias", keyAlias); // Append query parameters to URL if any exist const queryString = queryParams.toString(); if (queryString) { 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 73b9d944083..e46e78bf35d 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 @@ -60,6 +60,7 @@ export function useLogFilterLogic({ const performSearch = useCallback(async (filters: LogFilterState, page = 1) => { if (!accessToken) return; + console.log("Filters being sent to API:", filters); const currentTimestamp = Date.now(); lastSearchTimestamp.current = currentTimestamp; @@ -81,7 +82,8 @@ export function useLogFilterLogic({ filters[FILTER_KEYS.USER_ID] || undefined, filters[FILTER_KEYS.END_USER] || undefined, filters[FILTER_KEYS.STATUS] || undefined, - filters[FILTER_KEYS.MODEL] || undefined + filters[FILTER_KEYS.MODEL] || undefined, + filters[FILTER_KEYS.KEY_ALIAS] || undefined ); if (currentTimestamp === lastSearchTimestamp.current && response.data) { @@ -123,6 +125,19 @@ export function useLogFilterLogic({ }); return; } + + // Only do client-side filtering if no backend filters are active + const hasBackendFilters = + filters[FILTER_KEYS.KEY_ALIAS] || + filters[FILTER_KEYS.KEY_HASH] || + filters[FILTER_KEYS.REQUEST_ID] || + filters[FILTER_KEYS.USER_ID] || + filters[FILTER_KEYS.END_USER]; + + if (hasBackendFilters) { + // Backend is handling filtering, don't override the results + return; + } let filteredData = [...logs.data]; @@ -148,7 +163,7 @@ export function useLogFilterLogic({ log => log.model === filters[FILTER_KEYS.MODEL] ); } - + if (filters[FILTER_KEYS.KEY_HASH]) { filteredData = filteredData.filter( log => log.api_key === filters[FILTER_KEYS.KEY_HASH] @@ -161,24 +176,6 @@ export function useLogFilterLogic({ ); } - // Add key alias filtering - if (filters[FILTER_KEYS.KEY_ALIAS]) { - // We need to fetch the key info to get the key hash for the selected alias - try { - // Get the key hash for the selected alias - const selectedKey = filters[FILTER_KEYS.KEY_ALIAS] - - if (selectedKey) { - // Filter logs by the key hash - filteredData = filteredData.filter( - log => log.metadata?.user_api_key_alias === selectedKey - ); - } - } catch (error) { - console.error("Error fetching key info for alias:", error); - } - } - const newFilteredLogs: PaginatedResponse = { data: filteredData, total: logs.total, From 5c3d407b7a7764c85bd5304a8830cb30f1941858 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Thu, 11 Sep 2025 14:38:25 -0700 Subject: [PATCH 39/73] Revert "Add additionalProperties to vertex ai Schema definition" --- litellm/types/llms/vertex_ai.py | 1 - 1 file changed, 1 deletion(-) diff --git a/litellm/types/llms/vertex_ai.py b/litellm/types/llms/vertex_ai.py index 2687b79f727..f17a284ddfc 100644 --- a/litellm/types/llms/vertex_ai.py +++ b/litellm/types/llms/vertex_ai.py @@ -113,7 +113,6 @@ class Schema(TypedDict, total=False): pattern: str example: Any anyOf: List["Schema"] - additionalProperties: Any class FunctionDeclaration(TypedDict, total=False): From dda115cc6dc4298b0520f936f0bee1c1aa248550 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 11 Sep 2025 18:14:39 -0700 Subject: [PATCH 40/73] [Feat] Cost Tracking - Add support for Tiered Cost Tracking for Qwen API (Dashscope) (#14471) * add dashscope logo * docs fix * docs fix * fix supports_batch_calling * fix naming * fix input_cost_per_audio_token * use output_cost_per_reasoning_token * add tiered_pricing in get_model_info * test fixes * fix cost calc * ruff fix --- docs/my-website/docs/providers/dashscope.md | 2 +- litellm/llms/dashscope/cost_calculator.py | 157 ++++++++- ...odel_prices_and_context_window_backup.json | 298 +++++++++++++++++- litellm/types/utils.py | 1 + litellm/utils.py | 1 + model_prices_and_context_window.json | 298 +++++++++++++++++- .../test_dashscope_cost_calculator.py | 159 ++++++++++ .../src/components/provider_info_helpers.tsx | 9 +- 8 files changed, 893 insertions(+), 32 deletions(-) create mode 100644 tests/test_litellm/llms/dashscope/test_dashscope_cost_calculator.py diff --git a/docs/my-website/docs/providers/dashscope.md b/docs/my-website/docs/providers/dashscope.md index eb18fa32a47..565776d6c4c 100644 --- a/docs/my-website/docs/providers/dashscope.md +++ b/docs/my-website/docs/providers/dashscope.md @@ -1,4 +1,4 @@ -# Dashscope +# Dashscope (Qwen API) https://dashscope.console.aliyun.com/ **We support ALL Qwen models, just set `dashscope/` as a prefix when sending completion requests** diff --git a/litellm/llms/dashscope/cost_calculator.py b/litellm/llms/dashscope/cost_calculator.py index 0f4490cb3df..6f9ebe302f4 100644 --- a/litellm/llms/dashscope/cost_calculator.py +++ b/litellm/llms/dashscope/cost_calculator.py @@ -1,21 +1,158 @@ """ -Cost calculator for DeepSeek Chat models. +Cost calculator for Dashscope Chat models. -Handles prompt caching scenario. +Handles tiered pricing and prompt caching scenarios. """ -from typing import Tuple +from dataclasses import dataclass +from typing import List, Optional, Tuple -from litellm.litellm_core_utils.llm_cost_calc.utils import generic_cost_per_token -from litellm.types.utils import Usage +from litellm.types.utils import ModelInfo, Usage +from litellm.utils import get_model_info + + +@dataclass +class TokenBreakdown: + """Token breakdown for cost calculation.""" + text_tokens: int + cached_tokens: int + completion_tokens: int + reasoning_tokens: int + + +def _extract_token_breakdown(usage: Usage) -> TokenBreakdown: + """Extract token counts from usage, handling cached and reasoning tokens.""" + cached_tokens = 0 + if usage.prompt_tokens_details and hasattr(usage.prompt_tokens_details, "cached_tokens"): + cached_tokens = usage.prompt_tokens_details.cached_tokens or 0 + + text_tokens = usage.prompt_tokens - cached_tokens + + reasoning_tokens = 0 + if (hasattr(usage, "completion_tokens_details") and + usage.completion_tokens_details and + hasattr(usage.completion_tokens_details, "reasoning_tokens")): + reasoning_tokens = usage.completion_tokens_details.reasoning_tokens or 0 + + completion_tokens = (usage.completion_tokens or 0) - reasoning_tokens + + return TokenBreakdown(text_tokens, cached_tokens, completion_tokens, reasoning_tokens) + + +def _calculate_tiered_cost( + tokens: int, + tiered_pricing: List[dict], + cost_key: str, + fallback_cost_key: Optional[str] = None +) -> float: + """Calculate cost using tiered pricing structure.""" + if not tiered_pricing or tokens <= 0: + return 0.0 + + total_cost = 0.0 + tokens_processed = 0 + + for tier in tiered_pricing: + if tokens_processed >= tokens: + break + + tier_range = tier.get("range", []) + if len(tier_range) != 2: + continue + + range_start, range_end = tier_range + + if tokens <= range_start: + break + + tier_start = max(range_start, tokens_processed) + tier_end = min(range_end, tokens) + + if tier_end > tier_start: + tokens_in_tier = tier_end - tier_start + cost_per_token = tier.get(cost_key) or tier.get(fallback_cost_key, 0) + total_cost += tokens_in_tier * cost_per_token + tokens_processed = tier_end + + return total_cost + + +def _calculate_flat_cost(tokens: int, cost_per_token: float) -> float: + """Calculate cost using flat pricing.""" + return tokens * cost_per_token + + +def _calculate_prompt_cost(breakdown: TokenBreakdown, model_info: ModelInfo, tiered_pricing: Optional[List[dict]]) -> float: + """Calculate total prompt cost including cached tokens.""" + if tiered_pricing: + text_cost = _calculate_tiered_cost( + tokens=breakdown.text_tokens, + tiered_pricing=tiered_pricing, + cost_key="input_cost_per_token" + ) + cache_cost = _calculate_tiered_cost( + tokens=breakdown.cached_tokens, + tiered_pricing=tiered_pricing, + cost_key="cache_read_input_token_cost" + ) + return text_cost + cache_cost + + input_cost = model_info.get("input_cost_per_token", 0.0) + cache_cost = model_info.get("cache_read_input_token_cost", input_cost) or input_cost + + return (_calculate_flat_cost(tokens=breakdown.text_tokens, cost_per_token=input_cost) + + _calculate_flat_cost(tokens=breakdown.cached_tokens, cost_per_token=cache_cost)) + + +def _calculate_completion_cost(breakdown: TokenBreakdown, model_info: ModelInfo, tiered_pricing: Optional[List[dict]]) -> float: + """Calculate total completion cost including reasoning tokens.""" + if tiered_pricing: + completion_cost = _calculate_tiered_cost( + tokens=breakdown.completion_tokens, + tiered_pricing=tiered_pricing, + cost_key="output_cost_per_token" + ) + reasoning_cost = _calculate_tiered_cost( + tokens=breakdown.reasoning_tokens, + tiered_pricing=tiered_pricing, + cost_key="output_cost_per_reasoning_token", + fallback_cost_key="output_cost_per_token" + ) + return completion_cost + reasoning_cost + + output_cost = model_info.get("output_cost_per_token", 0.0) + reasoning_cost = model_info.get("output_cost_per_reasoning_token", output_cost) or output_cost + + return (_calculate_flat_cost(tokens=breakdown.completion_tokens, cost_per_token=output_cost) + + _calculate_flat_cost(tokens=breakdown.reasoning_tokens, cost_per_token=reasoning_cost)) def cost_per_token(model: str, usage: Usage) -> Tuple[float, float]: """ - Calculates the cost per token for a given model, prompt tokens, and completion tokens. - - Follows the same logic as Anthropic's cost per token calculation. + Calculate cost per token for Dashscope models. + + Supports both tiered and flat pricing with cached and reasoning tokens. + + Args: + model: Model name without provider prefix + usage: LiteLLM Usage block + + Returns: + Tuple[float, float] - (prompt_cost_in_usd, completion_cost_in_usd) """ - return generic_cost_per_token( - model=model, usage=usage, custom_llm_provider="deepseek" + model_info = get_model_info(model=model, custom_llm_provider="dashscope") + breakdown = _extract_token_breakdown(usage) + tiered_pricing = model_info.get("tiered_pricing") if isinstance(model_info.get("tiered_pricing"), list) else None + + prompt_cost = _calculate_prompt_cost( + breakdown=breakdown, + model_info=model_info, + tiered_pricing=tiered_pricing ) + completion_cost = _calculate_completion_cost( + breakdown=breakdown, + model_info=model_info, + tiered_pricing=tiered_pricing + ) + + return prompt_cost, completion_cost diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index c7fabb0ed9f..972e212e693 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -6,6 +6,7 @@ "input_cost_per_token": 0.0, "output_cost_per_token": 0.0, "output_cost_per_reasoning_token": 0.0, + "input_cost_per_audio_token": 0.0, "litellm_provider": "one of https://docs.litellm.ai/docs/providers", "mode": "one of: chat, embedding, completion, image_generation, audio_transcription, audio_speech, image_generation, moderation, rerank", "supports_function_calling": true, @@ -19045,34 +19046,43 @@ "max_tokens": 32768, "max_input_tokens": 30720, "max_output_tokens": 8192, + "input_cost_per_token": 1.6e-06, + "output_cost_per_token": 6.4e-06, "litellm_provider": "dashscope", "supports_function_calling": true, "supports_tool_choice": true, "supports_reasoning": true, "mode": "chat", - "source": "https://bailian.console.alibabacloud.com/?spm=a2c63.p38356.0.0.4a615d7bjSUCb4&tab=doc#/doc/?type=model&url=https%3A%2F%2Fwww.alibabacloud.com%2Fhelp%2Fen%2Fdoc-detail%2F2840914.html" + "source": "https://www.alibabacloud.com/help/en/model-studio/models" }, "dashscope/qwen-plus-latest": { - "max_tokens": 131072, - "max_input_tokens": 129024, - "max_output_tokens": 16384, + "max_tokens": 1000000, + "max_input_tokens": 997952, + "max_output_tokens": 32768, "litellm_provider": "dashscope", "supports_function_calling": true, "supports_tool_choice": true, "supports_reasoning": true, "mode": "chat", - "source": "https://bailian.console.alibabacloud.com/?spm=a2c63.p38356.0.0.4a615d7bjSUCb4&tab=doc#/doc/?type=model&url=https%3A%2F%2Fwww.alibabacloud.com%2Fhelp%2Fen%2Fdoc-detail%2F2840914.html" + "tiered_pricing": [ + {"range": [0, 256e3], "input_cost_per_token": 4e-07, "output_cost_per_token": 1.2e-06, "output_cost_per_reasoning_token": 4e-06}, + {"range": [256e3, 1e6], "input_cost_per_token": 1.2e-06, "output_cost_per_token": 3.6e-06, "output_cost_per_reasoning_token": 1.2e-05} + ], + "source": "https://www.alibabacloud.com/help/en/model-studio/models" }, "dashscope/qwen-turbo-latest": { - "max_tokens": 131072, - "max_input_tokens": 129024, + "max_tokens": 1000000, + "max_input_tokens": 1000000, "max_output_tokens": 16384, + "input_cost_per_token": 5e-08, + "output_cost_per_token": 2e-07, + "output_cost_per_reasoning_token": 5e-07, "litellm_provider": "dashscope", "supports_function_calling": true, "supports_tool_choice": true, "supports_reasoning": true, "mode": "chat", - "source": "https://bailian.console.alibabacloud.com/?spm=a2c63.p38356.0.0.4a615d7bjSUCb4&tab=doc#/doc/?type=model&url=https%3A%2F%2Fwww.alibabacloud.com%2Fhelp%2Fen%2Fdoc-detail%2F2840914.html" + "source": "https://www.alibabacloud.com/help/en/model-studio/models" }, "dashscope/qwen3-30b-a3b": { "max_tokens": 131072, @@ -19083,7 +19093,277 @@ "supports_tool_choice": true, "supports_reasoning": true, "mode": "chat", - "source": "https://bailian.console.alibabacloud.com/?spm=a2c63.p38356.0.0.4a615d7bjSUCb4&tab=doc#/doc/?type=model&url=https%3A%2F%2Fwww.alibabacloud.com%2Fhelp%2Fen%2Fdoc-detail%2F2840914.html" + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen3-max-preview": { + "max_tokens": 262144, + "max_input_tokens": 258048, + "max_output_tokens": 65536, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "tiered_pricing": [ + {"range": [0, 32e3], "input_cost_per_token": 1.2e-06, "output_cost_per_token": 6e-06}, + {"range": [32e3, 128e3], "input_cost_per_token": 2.4e-06, "output_cost_per_token": 1.2e-05}, + {"range": [128e3, 252e3], "input_cost_per_token": 3.0e-06, "output_cost_per_token": 1.5e-05} + ], + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-plus": { + "max_tokens": 131072, + "max_input_tokens": 129024, + "max_output_tokens": 16384, + "input_cost_per_token": 4e-07, + "output_cost_per_token": 1.2e-06, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-flash": { + "max_tokens": 1000000, + "max_input_tokens": 997952, + "max_output_tokens": 32768, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "supports_caching": true, + "mode": "chat", + "tiered_pricing": [ + {"range": [0, 256e3], "input_cost_per_token": 5e-08, "output_cost_per_token": 4e-07}, + {"range": [256e3, 1e6], "input_cost_per_token": 2.5e-07, "output_cost_per_token": 2e-06} + ], + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-coder": { + "max_tokens": 1000000, + "max_input_tokens": 1000000, + "max_output_tokens": 16384, + "input_cost_per_token": 3e-07, + "output_cost_per_token": 1.5e-06, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen3-coder-plus": { + "max_tokens": 1000000, + "max_input_tokens": 997952, + "max_output_tokens": 65536, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "supports_caching": true, + "mode": "chat", + "tiered_pricing": [ + {"range": [0, 32e3], "input_cost_per_token": 1e-06, "output_cost_per_token": 5e-06, "cache_read_input_token_cost": 1e-07}, + {"range": [32e3, 128e3], "input_cost_per_token": 1.8e-06, "output_cost_per_token": 9e-06, "cache_read_input_token_cost": 1.8e-07}, + {"range": [128e3, 256e3], "input_cost_per_token": 3e-06, "output_cost_per_token": 1.5e-05, "cache_read_input_token_cost": 3e-07}, + {"range": [256e3, 1e6], "input_cost_per_token": 6e-06, "output_cost_per_token": 6e-05, "cache_read_input_token_cost": 6e-07} + ], + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen3-coder-plus-2025-07-22": { + "max_tokens": 1000000, + "max_input_tokens": 997952, + "max_output_tokens": 65536, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "tiered_pricing": [ + {"range": [0, 32e3], "input_cost_per_token": 1e-06, "output_cost_per_token": 5e-06}, + {"range": [32e3, 128e3], "input_cost_per_token": 1.8e-06, "output_cost_per_token": 9e-06}, + {"range": [128e3, 256e3], "input_cost_per_token": 3e-06, "output_cost_per_token": 1.5e-05}, + {"range": [256e3, 1e6], "input_cost_per_token": 6e-06, "output_cost_per_token": 6e-05} + ], + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen3-coder-flash": { + "max_tokens": 1000000, + "max_input_tokens": 997952, + "max_output_tokens": 65536, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "supports_caching": true, + "mode": "chat", + "tiered_pricing": [ + {"range": [0, 32e3], "input_cost_per_token": 3e-07, "output_cost_per_token": 1.5e-06, "cache_read_input_token_cost": 8e-08}, + {"range": [32e3, 128e3], "input_cost_per_token": 5e-07, "output_cost_per_token": 2.5e-06, "cache_read_input_token_cost": 1.2e-07}, + {"range": [128e3, 256e3], "input_cost_per_token": 8e-07, "output_cost_per_token": 4e-06, "cache_read_input_token_cost": 2e-07}, + {"range": [256e3, 1e6], "input_cost_per_token": 1.6e-06, "output_cost_per_token": 9.6e-06, "cache_read_input_token_cost": 4e-07} + ], + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen3-coder-flash-2025-07-28": { + "max_tokens": 1000000, + "max_input_tokens": 997952, + "max_output_tokens": 65536, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "tiered_pricing": [ + {"range": [0, 32e3], "input_cost_per_token": 3e-07, "output_cost_per_token": 1.5e-06}, + {"range": [32e3, 128e3], "input_cost_per_token": 5e-07, "output_cost_per_token": 2.5e-06}, + {"range": [128e3, 256e3], "input_cost_per_token": 8e-07, "output_cost_per_token": 4e-06}, + {"range": [256e3, 1e6], "input_cost_per_token": 1.6e-06, "output_cost_per_token": 9.6e-06} + ], + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-plus-2025-09-11": { + "max_tokens": 1000000, + "max_input_tokens": 997952, + "max_output_tokens": 32768, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "tiered_pricing": [ + {"range": [0, 256e3], "input_cost_per_token": 4e-07, "output_cost_per_token": 1.2e-06, "output_cost_per_reasoning_token": 4e-06}, + {"range": [256e3, 1e6], "input_cost_per_token": 1.2e-06, "output_cost_per_token": 3.6e-06, "output_cost_per_reasoning_token": 1.2e-05} + ], + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-plus-2025-07-28": { + "max_tokens": 1000000, + "max_input_tokens": 997952, + "max_output_tokens": 32768, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "tiered_pricing": [ + {"range": [0, 256e3], "input_cost_per_token": 4e-07, "output_cost_per_token": 1.2e-06, "output_cost_per_reasoning_token": 4e-06}, + {"range": [256e3, 1e6], "input_cost_per_token": 1.2e-06, "output_cost_per_token": 3.6e-06, "output_cost_per_reasoning_token": 1.2e-05} + ], + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-plus-2025-07-14": { + "max_tokens": 131072, + "max_input_tokens": 129024, + "max_output_tokens": 16384, + "input_cost_per_token": 4e-07, + "output_cost_per_token": 1.2e-06, + "output_cost_per_reasoning_token": 4e-06, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-plus-2025-04-28": { + "max_tokens": 131072, + "max_input_tokens": 129024, + "max_output_tokens": 16384, + "input_cost_per_token": 4e-07, + "output_cost_per_token": 1.2e-06, + "output_cost_per_reasoning_token": 4e-06, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-plus-2025-01-25": { + "max_tokens": 131072, + "max_input_tokens": 129024, + "max_output_tokens": 8192, + "input_cost_per_token": 4e-07, + "output_cost_per_token": 1.2e-06, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-flash-2025-07-28": { + "max_tokens": 1000000, + "max_input_tokens": 997952, + "max_output_tokens": 32768, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "supports_caching": true, + "mode": "chat", + "tiered_pricing": [ + {"range": [0, 256e3], "input_cost_per_token": 5e-08, "output_cost_per_token": 4e-07}, + {"range": [256e3, 1e6], "input_cost_per_token": 2.5e-07, "output_cost_per_token": 2e-06} + ], + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-turbo": { + "max_tokens": 131072, + "max_input_tokens": 129024, + "max_output_tokens": 16384, + "input_cost_per_token": 5e-08, + "output_cost_per_token": 2e-07, + "output_cost_per_reasoning_token": 5e-07, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-turbo-2025-04-28": { + "max_tokens": 1000000, + "max_input_tokens": 1000000, + "max_output_tokens": 16384, + "input_cost_per_token": 5e-08, + "output_cost_per_token": 2e-07, + "output_cost_per_reasoning_token": 5e-07, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-turbo-2024-11-01": { + "max_tokens": 1000000, + "max_input_tokens": 1000000, + "max_output_tokens": 8192, + "input_cost_per_token": 5e-08, + "output_cost_per_token": 2e-07, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwq-plus": { + "max_tokens": 131072, + "max_input_tokens": 98304, + "max_output_tokens": 8192, + "max_chain_of_thought_tokens": 32768, + "input_cost_per_token": 8e-07, + "output_cost_per_token": 2.4e-06, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "source": "https://www.alibabacloud.com/help/en/model-studio/models" }, "moonshot/moonshot-v1-8k": { "max_tokens": 8192, diff --git a/litellm/types/utils.py b/litellm/types/utils.py index c6f7098a1a7..e54516371e5 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -162,6 +162,7 @@ class ModelInfoBase(ProviderSpecificModelInfo, total=False): SearchContextCostPerQuery ] # Cost for using web search tool citation_cost_per_token: Optional[float] # Cost per citation token for Perplexity + tiered_pricing: Optional[List[Dict[str, Any]]] # Tiered pricing structure for models like Dashscope litellm_provider: Required[str] mode: Required[ Literal[ diff --git a/litellm/utils.py b/litellm/utils.py index be26405b43b..84d86bea647 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -4902,6 +4902,7 @@ def _get_model_info_helper( # noqa: PLR0915 citation_cost_per_token=_model_info.get( "citation_cost_per_token", None ), + tiered_pricing=_model_info.get("tiered_pricing", None), litellm_provider=_model_info.get( "litellm_provider", custom_llm_provider ), diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index c7fabb0ed9f..972e212e693 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -6,6 +6,7 @@ "input_cost_per_token": 0.0, "output_cost_per_token": 0.0, "output_cost_per_reasoning_token": 0.0, + "input_cost_per_audio_token": 0.0, "litellm_provider": "one of https://docs.litellm.ai/docs/providers", "mode": "one of: chat, embedding, completion, image_generation, audio_transcription, audio_speech, image_generation, moderation, rerank", "supports_function_calling": true, @@ -19045,34 +19046,43 @@ "max_tokens": 32768, "max_input_tokens": 30720, "max_output_tokens": 8192, + "input_cost_per_token": 1.6e-06, + "output_cost_per_token": 6.4e-06, "litellm_provider": "dashscope", "supports_function_calling": true, "supports_tool_choice": true, "supports_reasoning": true, "mode": "chat", - "source": "https://bailian.console.alibabacloud.com/?spm=a2c63.p38356.0.0.4a615d7bjSUCb4&tab=doc#/doc/?type=model&url=https%3A%2F%2Fwww.alibabacloud.com%2Fhelp%2Fen%2Fdoc-detail%2F2840914.html" + "source": "https://www.alibabacloud.com/help/en/model-studio/models" }, "dashscope/qwen-plus-latest": { - "max_tokens": 131072, - "max_input_tokens": 129024, - "max_output_tokens": 16384, + "max_tokens": 1000000, + "max_input_tokens": 997952, + "max_output_tokens": 32768, "litellm_provider": "dashscope", "supports_function_calling": true, "supports_tool_choice": true, "supports_reasoning": true, "mode": "chat", - "source": "https://bailian.console.alibabacloud.com/?spm=a2c63.p38356.0.0.4a615d7bjSUCb4&tab=doc#/doc/?type=model&url=https%3A%2F%2Fwww.alibabacloud.com%2Fhelp%2Fen%2Fdoc-detail%2F2840914.html" + "tiered_pricing": [ + {"range": [0, 256e3], "input_cost_per_token": 4e-07, "output_cost_per_token": 1.2e-06, "output_cost_per_reasoning_token": 4e-06}, + {"range": [256e3, 1e6], "input_cost_per_token": 1.2e-06, "output_cost_per_token": 3.6e-06, "output_cost_per_reasoning_token": 1.2e-05} + ], + "source": "https://www.alibabacloud.com/help/en/model-studio/models" }, "dashscope/qwen-turbo-latest": { - "max_tokens": 131072, - "max_input_tokens": 129024, + "max_tokens": 1000000, + "max_input_tokens": 1000000, "max_output_tokens": 16384, + "input_cost_per_token": 5e-08, + "output_cost_per_token": 2e-07, + "output_cost_per_reasoning_token": 5e-07, "litellm_provider": "dashscope", "supports_function_calling": true, "supports_tool_choice": true, "supports_reasoning": true, "mode": "chat", - "source": "https://bailian.console.alibabacloud.com/?spm=a2c63.p38356.0.0.4a615d7bjSUCb4&tab=doc#/doc/?type=model&url=https%3A%2F%2Fwww.alibabacloud.com%2Fhelp%2Fen%2Fdoc-detail%2F2840914.html" + "source": "https://www.alibabacloud.com/help/en/model-studio/models" }, "dashscope/qwen3-30b-a3b": { "max_tokens": 131072, @@ -19083,7 +19093,277 @@ "supports_tool_choice": true, "supports_reasoning": true, "mode": "chat", - "source": "https://bailian.console.alibabacloud.com/?spm=a2c63.p38356.0.0.4a615d7bjSUCb4&tab=doc#/doc/?type=model&url=https%3A%2F%2Fwww.alibabacloud.com%2Fhelp%2Fen%2Fdoc-detail%2F2840914.html" + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen3-max-preview": { + "max_tokens": 262144, + "max_input_tokens": 258048, + "max_output_tokens": 65536, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "tiered_pricing": [ + {"range": [0, 32e3], "input_cost_per_token": 1.2e-06, "output_cost_per_token": 6e-06}, + {"range": [32e3, 128e3], "input_cost_per_token": 2.4e-06, "output_cost_per_token": 1.2e-05}, + {"range": [128e3, 252e3], "input_cost_per_token": 3.0e-06, "output_cost_per_token": 1.5e-05} + ], + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-plus": { + "max_tokens": 131072, + "max_input_tokens": 129024, + "max_output_tokens": 16384, + "input_cost_per_token": 4e-07, + "output_cost_per_token": 1.2e-06, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-flash": { + "max_tokens": 1000000, + "max_input_tokens": 997952, + "max_output_tokens": 32768, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "supports_caching": true, + "mode": "chat", + "tiered_pricing": [ + {"range": [0, 256e3], "input_cost_per_token": 5e-08, "output_cost_per_token": 4e-07}, + {"range": [256e3, 1e6], "input_cost_per_token": 2.5e-07, "output_cost_per_token": 2e-06} + ], + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-coder": { + "max_tokens": 1000000, + "max_input_tokens": 1000000, + "max_output_tokens": 16384, + "input_cost_per_token": 3e-07, + "output_cost_per_token": 1.5e-06, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen3-coder-plus": { + "max_tokens": 1000000, + "max_input_tokens": 997952, + "max_output_tokens": 65536, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "supports_caching": true, + "mode": "chat", + "tiered_pricing": [ + {"range": [0, 32e3], "input_cost_per_token": 1e-06, "output_cost_per_token": 5e-06, "cache_read_input_token_cost": 1e-07}, + {"range": [32e3, 128e3], "input_cost_per_token": 1.8e-06, "output_cost_per_token": 9e-06, "cache_read_input_token_cost": 1.8e-07}, + {"range": [128e3, 256e3], "input_cost_per_token": 3e-06, "output_cost_per_token": 1.5e-05, "cache_read_input_token_cost": 3e-07}, + {"range": [256e3, 1e6], "input_cost_per_token": 6e-06, "output_cost_per_token": 6e-05, "cache_read_input_token_cost": 6e-07} + ], + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen3-coder-plus-2025-07-22": { + "max_tokens": 1000000, + "max_input_tokens": 997952, + "max_output_tokens": 65536, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "tiered_pricing": [ + {"range": [0, 32e3], "input_cost_per_token": 1e-06, "output_cost_per_token": 5e-06}, + {"range": [32e3, 128e3], "input_cost_per_token": 1.8e-06, "output_cost_per_token": 9e-06}, + {"range": [128e3, 256e3], "input_cost_per_token": 3e-06, "output_cost_per_token": 1.5e-05}, + {"range": [256e3, 1e6], "input_cost_per_token": 6e-06, "output_cost_per_token": 6e-05} + ], + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen3-coder-flash": { + "max_tokens": 1000000, + "max_input_tokens": 997952, + "max_output_tokens": 65536, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "supports_caching": true, + "mode": "chat", + "tiered_pricing": [ + {"range": [0, 32e3], "input_cost_per_token": 3e-07, "output_cost_per_token": 1.5e-06, "cache_read_input_token_cost": 8e-08}, + {"range": [32e3, 128e3], "input_cost_per_token": 5e-07, "output_cost_per_token": 2.5e-06, "cache_read_input_token_cost": 1.2e-07}, + {"range": [128e3, 256e3], "input_cost_per_token": 8e-07, "output_cost_per_token": 4e-06, "cache_read_input_token_cost": 2e-07}, + {"range": [256e3, 1e6], "input_cost_per_token": 1.6e-06, "output_cost_per_token": 9.6e-06, "cache_read_input_token_cost": 4e-07} + ], + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen3-coder-flash-2025-07-28": { + "max_tokens": 1000000, + "max_input_tokens": 997952, + "max_output_tokens": 65536, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "tiered_pricing": [ + {"range": [0, 32e3], "input_cost_per_token": 3e-07, "output_cost_per_token": 1.5e-06}, + {"range": [32e3, 128e3], "input_cost_per_token": 5e-07, "output_cost_per_token": 2.5e-06}, + {"range": [128e3, 256e3], "input_cost_per_token": 8e-07, "output_cost_per_token": 4e-06}, + {"range": [256e3, 1e6], "input_cost_per_token": 1.6e-06, "output_cost_per_token": 9.6e-06} + ], + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-plus-2025-09-11": { + "max_tokens": 1000000, + "max_input_tokens": 997952, + "max_output_tokens": 32768, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "tiered_pricing": [ + {"range": [0, 256e3], "input_cost_per_token": 4e-07, "output_cost_per_token": 1.2e-06, "output_cost_per_reasoning_token": 4e-06}, + {"range": [256e3, 1e6], "input_cost_per_token": 1.2e-06, "output_cost_per_token": 3.6e-06, "output_cost_per_reasoning_token": 1.2e-05} + ], + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-plus-2025-07-28": { + "max_tokens": 1000000, + "max_input_tokens": 997952, + "max_output_tokens": 32768, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "tiered_pricing": [ + {"range": [0, 256e3], "input_cost_per_token": 4e-07, "output_cost_per_token": 1.2e-06, "output_cost_per_reasoning_token": 4e-06}, + {"range": [256e3, 1e6], "input_cost_per_token": 1.2e-06, "output_cost_per_token": 3.6e-06, "output_cost_per_reasoning_token": 1.2e-05} + ], + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-plus-2025-07-14": { + "max_tokens": 131072, + "max_input_tokens": 129024, + "max_output_tokens": 16384, + "input_cost_per_token": 4e-07, + "output_cost_per_token": 1.2e-06, + "output_cost_per_reasoning_token": 4e-06, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-plus-2025-04-28": { + "max_tokens": 131072, + "max_input_tokens": 129024, + "max_output_tokens": 16384, + "input_cost_per_token": 4e-07, + "output_cost_per_token": 1.2e-06, + "output_cost_per_reasoning_token": 4e-06, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-plus-2025-01-25": { + "max_tokens": 131072, + "max_input_tokens": 129024, + "max_output_tokens": 8192, + "input_cost_per_token": 4e-07, + "output_cost_per_token": 1.2e-06, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-flash-2025-07-28": { + "max_tokens": 1000000, + "max_input_tokens": 997952, + "max_output_tokens": 32768, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "supports_caching": true, + "mode": "chat", + "tiered_pricing": [ + {"range": [0, 256e3], "input_cost_per_token": 5e-08, "output_cost_per_token": 4e-07}, + {"range": [256e3, 1e6], "input_cost_per_token": 2.5e-07, "output_cost_per_token": 2e-06} + ], + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-turbo": { + "max_tokens": 131072, + "max_input_tokens": 129024, + "max_output_tokens": 16384, + "input_cost_per_token": 5e-08, + "output_cost_per_token": 2e-07, + "output_cost_per_reasoning_token": 5e-07, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-turbo-2025-04-28": { + "max_tokens": 1000000, + "max_input_tokens": 1000000, + "max_output_tokens": 16384, + "input_cost_per_token": 5e-08, + "output_cost_per_token": 2e-07, + "output_cost_per_reasoning_token": 5e-07, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwen-turbo-2024-11-01": { + "max_tokens": 1000000, + "max_input_tokens": 1000000, + "max_output_tokens": 8192, + "input_cost_per_token": 5e-08, + "output_cost_per_token": 2e-07, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "source": "https://www.alibabacloud.com/help/en/model-studio/models" + }, + "dashscope/qwq-plus": { + "max_tokens": 131072, + "max_input_tokens": 98304, + "max_output_tokens": 8192, + "max_chain_of_thought_tokens": 32768, + "input_cost_per_token": 8e-07, + "output_cost_per_token": 2.4e-06, + "litellm_provider": "dashscope", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "mode": "chat", + "source": "https://www.alibabacloud.com/help/en/model-studio/models" }, "moonshot/moonshot-v1-8k": { "max_tokens": 8192, diff --git a/tests/test_litellm/llms/dashscope/test_dashscope_cost_calculator.py b/tests/test_litellm/llms/dashscope/test_dashscope_cost_calculator.py new file mode 100644 index 00000000000..73e992e22d1 --- /dev/null +++ b/tests/test_litellm/llms/dashscope/test_dashscope_cost_calculator.py @@ -0,0 +1,159 @@ +""" +Test suite for Dashscope cost calculation functionality. + +Tests the cost calculation for Dashscope models including: +- Tiered pricing based on input token ranges +- Caching discounts +- Reasoning tokens +- Standard flat pricing fallback +""" + +import json +import math +import os +import sys + +import pytest + +# Add the project root to Python path +sys.path.insert(0, os.path.abspath("../../../..")) + +import litellm +from litellm.llms.dashscope.cost_calculator import ( + cost_per_token as dashscope_cost_per_token, +) +from litellm.types.utils import ( + CompletionTokensDetailsWrapper, + PromptTokensDetailsWrapper, + Usage, +) + + +class TestDashscopeCostCalculator: + """Test suite for Dashscope cost calculation functionality.""" + + @pytest.fixture(autouse=True) + def setup_model_cost_map(self): + """Set up the model cost map for testing.""" + # Ensure we use local model cost map for consistent testing + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + + # Find the project root directory and load model cost map + current_dir = os.path.dirname(os.path.abspath(__file__)) + project_root = current_dir + while not os.path.exists(os.path.join(project_root, "model_prices_and_context_window.json")): + parent = os.path.dirname(project_root) + if parent == project_root: # Reached filesystem root + break + project_root = parent + + model_cost_path = os.path.join(project_root, "model_prices_and_context_window.json") + with open(model_cost_path, "r") as f: + model_cost_map = json.load(f) + litellm.model_cost = model_cost_map + + def test_flat_pricing_basic_cost_calculation(self): + """Test basic cost calculation for flat pricing models (qwen-max).""" + usage = Usage( + prompt_tokens=1000, + completion_tokens=500, + total_tokens=1500 + ) + + prompt_cost, completion_cost = dashscope_cost_per_token( + model="qwen-max", + usage=usage + ) + + # Expected costs for qwen-max: + # Input: 1000 tokens * $1.6e-6 = $0.0016 + # Output: 500 tokens * $6.4e-6 = $0.0032 + expected_prompt_cost = 1000 * 1.6e-6 + expected_completion_cost = 500 * 6.4e-6 + + assert math.isclose(prompt_cost, expected_prompt_cost, rel_tol=1e-10) + assert math.isclose(completion_cost, expected_completion_cost, rel_tol=1e-10) + + def test_tiered_pricing_single_tier(self): + """Test tiered pricing when all tokens fall within first tier.""" + usage = Usage( + prompt_tokens=20000, # Within first tier (0-32K) + completion_tokens=1000, + total_tokens=21000 + ) + + prompt_cost, completion_cost = dashscope_cost_per_token( + model="qwen3-coder-plus", + usage=usage + ) + + # Expected costs for qwen3-coder-plus (tier 1): + # Input: 20,000 tokens * $1e-6 = $0.02 + # Output: 1,000 tokens * $5e-6 = $0.005 + expected_prompt_cost = 20000 * 1e-6 + expected_completion_cost = 1000 * 5e-6 + + assert math.isclose(prompt_cost, expected_prompt_cost, rel_tol=1e-10) + assert math.isclose(completion_cost, expected_completion_cost, rel_tol=1e-10) + + def test_tiered_pricing_multiple_tiers(self): + """Test tiered pricing when tokens span multiple tiers.""" + usage = Usage( + prompt_tokens=150000, # Spans tiers 1 (0-32K), 2 (32K-128K), 3 (128K-256K) + completion_tokens=2000, + total_tokens=152000 + ) + + prompt_cost, completion_cost = dashscope_cost_per_token( + model="qwen3-coder-plus", + usage=usage + ) + + # Expected input cost calculation: + # Tier 1 (0-32K): 32,000 tokens * $1e-6 = $0.032 + # Tier 2 (32K-128K): 96,000 tokens * $1.8e-6 = $0.1728 + # Tier 3 (128K-256K): 22,000 tokens * $3e-6 = $0.066 + # Total input cost = $0.032 + $0.1728 + $0.066 = $0.2708 + + expected_prompt_cost = (32000 * 1e-6) + (96000 * 1.8e-6) + (22000 * 3e-6) + expected_completion_cost = 2000 * 5e-6 # All in tier 1 for output + + assert math.isclose(prompt_cost, expected_prompt_cost, rel_tol=1e-10) + assert math.isclose(completion_cost, expected_completion_cost, rel_tol=1e-10) + + def test_tiered_pricing_with_caching(self): + """Test tiered pricing with cached tokens.""" + prompt_tokens_details = PromptTokensDetailsWrapper( + cached_tokens=10000 # 10K cached tokens + ) + + usage = Usage( + prompt_tokens=50000, # 40K regular + 10K cached = 50K total + completion_tokens=1000, + total_tokens=51000, + prompt_tokens_details=prompt_tokens_details + ) + + prompt_cost, completion_cost = dashscope_cost_per_token( + model="qwen3-coder-plus", + usage=usage + ) + + # Expected cost calculation: + # Regular tokens: 40,000 (32K in tier 1 + 8K in tier 2) + # - Tier 1: 32,000 * $1e-6 = $0.032 + # - Tier 2: 8,000 * $1.8e-6 = $0.0144 + # Cached tokens: 10,000 in tier 1 at discounted rate + # - Tier 1 cached: 10,000 * $1e-7 = $0.001 + # Total input cost = $0.032 + $0.0144 + $0.001 = $0.0474 + + regular_tokens = 40000 + cached_tokens = 10000 + + expected_regular_cost = (32000 * 1e-6) + (8000 * 1.8e-6) + expected_cached_cost = cached_tokens * 1e-7 # Tier 1 cached rate + expected_prompt_cost = expected_regular_cost + expected_cached_cost + expected_completion_cost = 1000 * 5e-6 + + assert math.isclose(prompt_cost, expected_prompt_cost, rel_tol=1e-10) + assert math.isclose(completion_cost, expected_completion_cost, rel_tol=1e-10) \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx index 31f5b10043b..17aa637af89 100644 --- a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx +++ b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx @@ -11,8 +11,9 @@ export enum Providers { Azure = "Azure", Azure_AI_Studio = "Azure AI Foundry (Studio)", Cerebras = "Cerebras", - Cohere = "Cohere", - Databricks = "Databricks", + Cohere = "Cohere", + Dashscope = "Dashscope", + Databricks = "Databricks (Qwen API)", DeepInfra = "DeepInfra", Deepgram = "Deepgram", Deepseek = "Deepseek", @@ -37,7 +38,7 @@ export enum Providers { Vertex_AI = "Vertex AI (Anthropic, Gemini, etc.)", VolcEngine = "VolcEngine", Voyage = "Voyage AI", - xAI = "xAI", + xAI = "xAI", } export const provider_map: Record = { @@ -56,6 +57,7 @@ export const provider_map: Record = { OpenAI_Text_Compatible: "text-completion-openai", Vertex_AI: "vertex_ai", Databricks: "databricks", + Dashscope: "dashscope", xAI: "xai", Deepseek: "deepseek", Ollama: "ollama", @@ -91,6 +93,7 @@ export const providerLogoMap: Record = { [Providers.Cerebras]: `${asset_logos_folder}cerebras.svg`, [Providers.Cohere]: `${asset_logos_folder}cohere.svg`, [Providers.Databricks]: `${asset_logos_folder}databricks.svg`, + [Providers.Dashscope]: `${asset_logos_folder}dashscope.svg`, [Providers.Deepseek]: `${asset_logos_folder}deepseek.svg`, [Providers.FireworksAI]: `${asset_logos_folder}fireworks.svg`, [Providers.Groq]: `${asset_logos_folder}groq.svg`, From 805069c287376cbc9ff82d08affb461bcbff36de Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 11 Sep 2025 18:52:02 -0700 Subject: [PATCH 41/73] fix(adapters/streaming_iterator.py): Don't send content block after message delta block is sent Fixes https://github.com/BerriAI/litellm/issues/14315 --- litellm/constants.py | 10 +- .../adapters/streaming_iterator.py | 133 +++---- litellm/proxy/_new_secret_config.yaml | 3 + .../hooks/parallel_request_limiter_v3.py | 42 ++- .../test_content_after_stop_reason.py | 343 ++++++++++++++++++ 5 files changed, 448 insertions(+), 83 deletions(-) create mode 100644 tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_content_after_stop_reason.py diff --git a/litellm/constants.py b/litellm/constants.py index 75c25d9ea9e..c0ce0f265b5 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -15,7 +15,7 @@ DEFAULT_SQS_FLUSH_INTERVAL_SECONDS = int( os.getenv("DEFAULT_SQS_FLUSH_INTERVAL_SECONDS", 10) ) DEFAULT_NUM_WORKERS_LITELLM_PROXY = int( - os.getenv("DEFAULT_NUM_WORKERS_LITELLM_PROXY", os.cpu_count() or 4) + os.getenv("DEFAULT_NUM_WORKERS_LITELLM_PROXY", 1) ) DEFAULT_SQS_BATCH_SIZE = int(os.getenv("DEFAULT_SQS_BATCH_SIZE", 512)) SQS_SEND_MESSAGE_ACTION = "SendMessage" @@ -60,7 +60,9 @@ DEFAULT_REASONING_EFFORT_MINIMAL_THINKING_BUDGET_GEMINI_2_5_PRO = int( os.getenv("DEFAULT_REASONING_EFFORT_MINIMAL_THINKING_BUDGET_GEMINI_2_5_PRO", 128) ) DEFAULT_REASONING_EFFORT_MINIMAL_THINKING_BUDGET_GEMINI_2_5_FLASH_LITE = int( - os.getenv("DEFAULT_REASONING_EFFORT_MINIMAL_THINKING_BUDGET_GEMINI_2_5_FLASH_LITE", 512) + os.getenv( + "DEFAULT_REASONING_EFFORT_MINIMAL_THINKING_BUDGET_GEMINI_2_5_FLASH_LITE", 512 + ) ) # Generic fallback for unknown models @@ -949,7 +951,9 @@ LITELLM_CLI_SESSION_TOKEN_PREFIX = "litellm-session-token" DB_SPEND_UPDATE_JOB_NAME = "db_spend_update_job" PROMETHEUS_EMIT_BUDGET_METRICS_JOB_NAME = "prometheus_emit_budget_metrics" CLOUDZERO_EXPORT_USAGE_DATA_JOB_NAME = "cloudzero_export_usage_data" -CLOUDZERO_MAX_FETCHED_DATA_RECORDS = int(os.getenv("CLOUDZERO_MAX_FETCHED_DATA_RECORDS", 50000)) +CLOUDZERO_MAX_FETCHED_DATA_RECORDS = int( + os.getenv("CLOUDZERO_MAX_FETCHED_DATA_RECORDS", 50000) +) SPEND_LOG_CLEANUP_JOB_NAME = "spend_log_cleanup" SPEND_LOG_RUN_LOOPS = int(os.getenv("SPEND_LOG_RUN_LOOPS", 500)) SPEND_LOG_CLEANUP_BATCH_SIZE = int(os.getenv("SPEND_LOG_CLEANUP_BATCH_SIZE", 1000)) diff --git a/litellm/llms/anthropic/experimental_pass_through/adapters/streaming_iterator.py b/litellm/llms/anthropic/experimental_pass_through/adapters/streaming_iterator.py index aa95183bb6c..e4191a945f3 100644 --- a/litellm/llms/anthropic/experimental_pass_through/adapters/streaming_iterator.py +++ b/litellm/llms/anthropic/experimental_pass_through/adapters/streaming_iterator.py @@ -28,10 +28,6 @@ class AnthropicStreamWrapper(AdapterCompletionStreamWrapper): TextBlock, ) - def __init__(self, completion_stream: Any, model: str): - super().__init__(completion_stream) - self.model = model - sent_first_chunk: bool = False sent_content_block_start: bool = False sent_content_block_finish: bool = False @@ -39,6 +35,7 @@ class AnthropicStreamWrapper(AdapterCompletionStreamWrapper): sent_last_message: bool = False holding_chunk: Optional[Any] = None holding_stop_reason_chunk: Optional[Any] = None + queued_usage_chunk: bool = False current_content_block_index: int = 0 current_content_block_start: ContentBlockContentBlockDict = TextBlock( type="text", @@ -47,6 +44,10 @@ class AnthropicStreamWrapper(AdapterCompletionStreamWrapper): pending_new_content_block: bool = False chunk_queue: deque = deque() # Queue for buffering multiple chunks + def __init__(self, completion_stream: Any, model: str): + super().__init__(completion_stream) + self.model = model + def __next__(self): from .transformation import LiteLLMAnthropicMessagesAdapter @@ -217,77 +218,83 @@ class AnthropicStreamWrapper(AdapterCompletionStreamWrapper): # Queue the merged chunk and reset self.chunk_queue.append(merged_chunk) + self.queued_usage_chunk = True self.holding_stop_reason_chunk = None return self.chunk_queue.popleft() # Check if this processed chunk has a stop_reason - hold it for next chunk - if should_start_new_block and not self.sent_content_block_finish: - # Queue the sequence: content_block_stop -> content_block_start -> current_chunk + if not self.queued_usage_chunk: + if should_start_new_block and not self.sent_content_block_finish: + # Queue the sequence: content_block_stop -> content_block_start -> current_chunk - # 1. Stop current content block - self.chunk_queue.append( - { - "type": "content_block_stop", - "index": max(self.current_content_block_index - 1, 0), - } - ) + # 1. Stop current content block + self.chunk_queue.append( + { + "type": "content_block_stop", + "index": max(self.current_content_block_index - 1, 0), + } + ) - # 2. Start new content block - self.chunk_queue.append( - { - "type": "content_block_start", - "index": self.current_content_block_index, - "content_block": self.current_content_block_start, - } - ) + # 2. Start new content block + self.chunk_queue.append( + { + "type": "content_block_start", + "index": self.current_content_block_index, + "content_block": self.current_content_block_start, + } + ) - # 3. Queue the current chunk (don't lose it!) - self.chunk_queue.append(processed_chunk) - - # Reset state for new block - self.sent_content_block_finish = False - - # Return the first queued item - return self.chunk_queue.popleft() - - if ( - processed_chunk["type"] == "message_delta" - and self.sent_content_block_finish is False - ): - # Queue both the content_block_stop and the holding chunk - self.chunk_queue.append( - { - "type": "content_block_stop", - "index": self.current_content_block_index, - } - ) - self.sent_content_block_finish = True - if processed_chunk.get("delta", {}).get("stop_reason") is not None: - - self.holding_stop_reason_chunk = processed_chunk - else: + # 3. Queue the current chunk (don't lose it!) self.chunk_queue.append(processed_chunk) - return self.chunk_queue.popleft() - elif self.holding_chunk is not None: - # Queue both chunks - self.chunk_queue.append(self.holding_chunk) - self.chunk_queue.append(processed_chunk) - self.holding_chunk = None - return self.chunk_queue.popleft() - else: - # Queue the current chunk - self.chunk_queue.append(processed_chunk) - return self.chunk_queue.popleft() + + # Reset state for new block + self.sent_content_block_finish = False + + # Return the first queued item + return self.chunk_queue.popleft() + + if ( + processed_chunk["type"] == "message_delta" + and self.sent_content_block_finish is False + ): + # Queue both the content_block_stop and the holding chunk + self.chunk_queue.append( + { + "type": "content_block_stop", + "index": self.current_content_block_index, + } + ) + self.sent_content_block_finish = True + if ( + processed_chunk.get("delta", {}).get("stop_reason") + is not None + ): + + self.holding_stop_reason_chunk = processed_chunk + else: + self.chunk_queue.append(processed_chunk) + return self.chunk_queue.popleft() + elif self.holding_chunk is not None: + # Queue both chunks + self.chunk_queue.append(self.holding_chunk) + self.chunk_queue.append(processed_chunk) + self.holding_chunk = None + return self.chunk_queue.popleft() + else: + # Queue the current chunk + self.chunk_queue.append(processed_chunk) + return self.chunk_queue.popleft() # Handle any remaining held chunks after stream ends - if self.holding_stop_reason_chunk is not None: - self.chunk_queue.append(self.holding_stop_reason_chunk) - self.holding_stop_reason_chunk = None + if not self.queued_usage_chunk: + if self.holding_stop_reason_chunk is not None: + self.chunk_queue.append(self.holding_stop_reason_chunk) + self.holding_stop_reason_chunk = None - if self.holding_chunk is not None: - self.chunk_queue.append(self.holding_chunk) - self.holding_chunk = None + if self.holding_chunk is not None: + self.chunk_queue.append(self.holding_chunk) + self.holding_chunk = None if not self.sent_last_message: self.sent_last_message = True diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index c785dd05c40..691f67d5778 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -7,3 +7,6 @@ model_list: - model_name: wildcard_models/* litellm_params: model: openai/* + - model_name: xai-grok-3 + litellm_params: + model: xai/grok-3 diff --git a/litellm/proxy/hooks/parallel_request_limiter_v3.py b/litellm/proxy/hooks/parallel_request_limiter_v3.py index b3840761d2a..b3fa71a1c15 100644 --- a/litellm/proxy/hooks/parallel_request_limiter_v3.py +++ b/litellm/proxy/hooks/parallel_request_limiter_v3.py @@ -6,6 +6,7 @@ This is currently in development and not yet ready for production. import os from datetime import datetime +from math import floor from typing import ( TYPE_CHECKING, Any, @@ -17,7 +18,7 @@ from typing import ( Union, cast, ) -from math import floor + from fastapi import HTTPException from litellm import DualCache @@ -95,6 +96,7 @@ end return results """ + class RateLimitDescriptorRateLimitObject(TypedDict, total=False): requests_per_unit: Optional[int] tokens_per_unit: Optional[int] @@ -480,10 +482,15 @@ class _PROXY_MaxParallelRequestsHandler_v3(CustomLogger): }, ) ) - + # Team Member rate limits - if user_api_key_dict.user_id and (user_api_key_dict.team_member_rpm_limit is not None or user_api_key_dict.team_member_tpm_limit is not None): - team_member_value = f"{user_api_key_dict.team_id}:{user_api_key_dict.user_id}" + if user_api_key_dict.user_id and ( + user_api_key_dict.team_member_rpm_limit is not None + or user_api_key_dict.team_member_tpm_limit is not None + ): + team_member_value = ( + f"{user_api_key_dict.team_id}:{user_api_key_dict.user_id}" + ) descriptors.append( RateLimitDescriptor( key="team_member", @@ -557,13 +564,13 @@ class _PROXY_MaxParallelRequestsHandler_v3(CustomLogger): # Find which descriptor hit the limit for i, status in enumerate(response["statuses"]): if status["code"] == "OVER_LIMIT": - descriptor = descriptors[floor(i/2)] + descriptor = descriptors[floor(i / 2)] raise HTTPException( status_code=429, detail=f"Rate limit exceeded for {descriptor['key']}: {descriptor['value']}. Remaining: {status['limit_remaining']}", headers={ "retry-after": str(self.window_size), - "rate_limit_type": str(status["rate_limit_type"]) + "rate_limit_type": str(status["rate_limit_type"]), }, # Retry after 1 minute ) @@ -613,7 +620,9 @@ class _PROXY_MaxParallelRequestsHandler_v3(CustomLogger): # Check if script is available if self.token_increment_script is None: - verbose_proxy_logger.debug("TTL preservation script not available, using regular pipeline") + verbose_proxy_logger.debug( + "TTL preservation script not available, using regular pipeline" + ) await self.internal_usage_cache.dual_cache.async_increment_cache_pipeline( increment_list=pipeline_operations, litellm_parent_otel_span=parent_otel_span, @@ -628,7 +637,7 @@ class _PROXY_MaxParallelRequestsHandler_v3(CustomLogger): for op in pipeline_operations: # Convert None TTL to 0 for Lua script ttl_value = op["ttl"] if op["ttl"] is not None else 0 - + verbose_proxy_logger.debug( f"Executing TTL-preserving increment for key={op['key']}, " f"increment={op['increment_value']}, ttl={ttl_value}" @@ -693,16 +702,15 @@ class _PROXY_MaxParallelRequestsHandler_v3(CustomLogger): ) # Get metadata from kwargs - user_api_key = kwargs["litellm_params"]["metadata"].get("user_api_key") - user_api_key_user_id = kwargs["litellm_params"]["metadata"].get( - "user_api_key_user_id" + litellm_metadata = kwargs["litellm_params"]["metadata"] + if litellm_metadata is None: + return + user_api_key = litellm_metadata.get("user_api_key") + user_api_key_user_id = litellm_metadata.get("user_api_key_user_id") + user_api_key_team_id = litellm_metadata.get("user_api_key_team_id") + user_api_key_end_user_id = kwargs.get("user") or litellm_metadata.get( + "user_api_key_end_user_id" ) - user_api_key_team_id = kwargs["litellm_params"]["metadata"].get( - "user_api_key_team_id" - ) - user_api_key_end_user_id = kwargs.get("user") or kwargs["litellm_params"][ - "metadata" - ].get("user_api_key_end_user_id") model_group = get_model_group_from_litellm_kwargs(kwargs) # Get total tokens from response diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_content_after_stop_reason.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_content_after_stop_reason.py new file mode 100644 index 00000000000..4a170d666f5 --- /dev/null +++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_content_after_stop_reason.py @@ -0,0 +1,343 @@ +""" +Test for AnthropicStreamWrapper handling content blocks that exist after message_delta with stop_reason and usage. + +This tests the scenario where a streaming response includes: +1. Initial content blocks +2. A message_delta chunk with stop_reason and usage +3. Additional content blocks after the stop_reason + +The wrapper should properly handle this by: +- Holding the stop_reason chunk until usage is available +- Merging usage into the stop_reason chunk +- Properly managing content_block_stop/start events for subsequent content +""" + +import os +import sys +from typing import List + +import pytest + +sys.path.insert(0, os.path.abspath("../../../../..")) + +from litellm.llms.anthropic.experimental_pass_through.adapters.streaming_iterator import ( + AnthropicStreamWrapper, +) +from litellm.types.utils import Delta, ModelResponse, StreamingChoices, Usage + + +class MockCompletionStreamWithContentAfterStopReason: + """Mock stream that simulates content blocks existing after message_delta with stop_reason and usage.""" + + def __init__(self): + self.responses = [ + # Initial text content + ModelResponse( + stream=True, + choices=[ + StreamingChoices( + delta=Delta(content="Hello"), index=0, finish_reason=None + ) + ], + ), + ModelResponse( + stream=True, + choices=[ + StreamingChoices( + delta=Delta(content=" world"), index=0, finish_reason=None + ) + ], + ), + # Message delta with stop_reason AND usage (this is how it actually comes from the API) + ModelResponse( + stream=True, + choices=[ + StreamingChoices( + delta=Delta(content=""), index=0, finish_reason="stop" + ) + ], + usage=Usage(prompt_tokens=230, completion_tokens=65, total_tokens=295), + ), + # Additional content after the stop_reason - this simulates the scenario + # where there might be additional content blocks after the main response + ModelResponse( + stream=True, + choices=[ + StreamingChoices( + delta=Delta(content=" Additional content"), + index=0, + finish_reason=None, + ) + ], + ), + ] + self.index = 0 + + def __iter__(self): + return self + + def __next__(self): + if self.index >= len(self.responses): + raise StopIteration + response = self.responses[self.index] + self.index += 1 + return response + + def __aiter__(self): + return self + + async def __anext__(self): + if self.index >= len(self.responses): + raise StopAsyncIteration + response = self.responses[self.index] + self.index += 1 + return response + + +def test_anthropic_stream_wrapper_content_after_stop_reason(): + """Test that AnthropicStreamWrapper properly handles content blocks after message_delta with stop_reason.""" + + wrapper = AnthropicStreamWrapper( + completion_stream=MockCompletionStreamWithContentAfterStopReason(), + model="claude-3", + ) + + chunks = [] + chunk_types = [] + + # Collect all chunks + for chunk in wrapper: + chunks.append(chunk) + chunk_types.append(chunk.get("type")) + + # Verify the expected sequence of chunk types + expected_types = [ + "message_start", # Initial message start + "content_block_start", # Start of first content block + "content_block_delta", # "Hello" + "content_block_delta", # " world" + "content_block_stop", # End of first content block due to stop_reason + "message_delta", # Stop reason with merged usage + "message_stop", # Final message stop + ] + + print(f"Actual chunk types: {chunk_types}") + print(f"Expected chunk types: {expected_types}") + + # Verify we have the expected number of chunks + assert len(chunk_types) >= len( + expected_types + ), f"Expected at least {len(expected_types)} chunks, got {len(chunk_types)}" + + # Verify key chunk types are present + assert "message_start" in chunk_types + assert "content_block_start" in chunk_types + assert "content_block_delta" in chunk_types + assert "content_block_stop" in chunk_types + assert "message_delta" in chunk_types + assert "message_stop" in chunk_types + + # Find the message_delta chunk with stop_reason + message_delta_chunk = None + for chunk in chunks: + if chunk.get("type") == "message_delta": + message_delta_chunk = chunk + break + + assert message_delta_chunk is not None, "message_delta chunk not found" + + # Verify that the message_delta chunk has both stop_reason and usage + delta = message_delta_chunk.get("delta", {}) + usage = message_delta_chunk.get("usage", {}) + + assert ( + delta.get("stop_reason") == "end_turn" + ), f"Expected stop_reason 'end_turn', got {delta.get('stop_reason')}" + assert ( + usage.get("input_tokens") == 230 + ), f"Expected input_tokens 230, got {usage.get('input_tokens')}" + assert ( + usage.get("output_tokens") == 65 + ), f"Expected output_tokens 65, got {usage.get('output_tokens')}" + + # Verify content_block_stop comes before message_delta + content_block_stop_index = None + message_delta_index = None + + for i, chunk_type in enumerate(chunk_types): + if chunk_type == "content_block_stop" and content_block_stop_index is None: + content_block_stop_index = i + elif chunk_type == "message_delta": + message_delta_index = i + + assert content_block_stop_index is not None, "content_block_stop not found" + assert message_delta_index is not None, "message_delta not found" + assert ( + content_block_stop_index < message_delta_index + ), "content_block_stop should come before message_delta" + + +@pytest.mark.asyncio +async def test_async_anthropic_stream_wrapper_content_after_stop_reason(): + """Test async version of AnthropicStreamWrapper handling content blocks after message_delta with stop_reason.""" + + wrapper = AnthropicStreamWrapper( + completion_stream=MockCompletionStreamWithContentAfterStopReason(), + model="claude-3", + ) + + chunks = [] + chunk_types = [] + + # Collect all chunks asynchronously + async for chunk in wrapper: + chunks.append(chunk) + chunk_types.append(chunk.get("type")) + + print(f"Async - Actual chunk types: {chunk_types}") + + # Verify key chunk types are present + assert "message_start" in chunk_types + assert "content_block_start" in chunk_types + assert "content_block_delta" in chunk_types + assert "content_block_stop" in chunk_types + assert "message_delta" in chunk_types + assert "message_stop" in chunk_types + + # Find the message_delta chunk with stop_reason + message_delta_chunk = None + for chunk in chunks: + if chunk.get("type") == "message_delta": + message_delta_chunk = chunk + break + + assert message_delta_chunk is not None, "message_delta chunk not found" + + # Verify that the message_delta chunk has both stop_reason and usage + delta = message_delta_chunk.get("delta", {}) + usage = message_delta_chunk.get("usage", {}) + + assert ( + delta.get("stop_reason") == "end_turn" + ), f"Expected stop_reason 'end_turn', got {delta.get('stop_reason')}" + assert ( + usage.get("input_tokens") == 230 + ), f"Expected input_tokens 230, got {usage.get('input_tokens')}" + assert ( + usage.get("output_tokens") == 65 + ), f"Expected output_tokens 65, got {usage.get('output_tokens')}" + + +def test_usage_merging_behavior(): + """Test that usage information is properly merged with stop_reason chunk.""" + + wrapper = AnthropicStreamWrapper( + completion_stream=MockCompletionStreamWithContentAfterStopReason(), + model="claude-3", + ) + + # Process chunks and look specifically for the usage merging behavior + chunks = [] + for chunk in wrapper: + chunks.append(chunk) + # If this is a message_delta with stop_reason, verify it has usage + if ( + chunk.get("type") == "message_delta" + and chunk.get("delta", {}).get("stop_reason") is not None + ): + + usage = chunk.get("usage", {}) + assert ( + usage.get("input_tokens") is not None + ), "Usage should be merged with stop_reason chunk" + assert ( + usage.get("output_tokens") is not None + ), "Usage should be merged with stop_reason chunk" + break + + +def test_sse_wrapper_with_content_after_stop_reason(): + """Test SSE wrapper formatting for the content after stop_reason scenario.""" + + wrapper = AnthropicStreamWrapper( + completion_stream=MockCompletionStreamWithContentAfterStopReason(), + model="claude-3", + ) + + # Get SSE formatted chunks + sse_chunks = [] + for chunk in wrapper.anthropic_sse_wrapper(): + sse_chunks.append(chunk) + if len(sse_chunks) >= 10: # Limit to avoid infinite loops in tests + break + + # Verify all chunks are properly formatted as bytes + for chunk in sse_chunks: + assert isinstance(chunk, bytes), "SSE chunks should be bytes" + + # Decode and verify SSE format + chunk_str = chunk.decode("utf-8") + lines = chunk_str.split("\n") + + # Should have event and data lines + assert any( + line.startswith("event: ") for line in lines + ), f"Missing event line in: {chunk_str}" + assert any( + line.startswith("data: ") for line in lines + ), f"Missing data line in: {chunk_str}" + + +@pytest.mark.asyncio +async def test_async_sse_wrapper_with_content_after_stop_reason(): + """Test async SSE wrapper formatting for the content after stop_reason scenario.""" + + wrapper = AnthropicStreamWrapper( + completion_stream=MockCompletionStreamWithContentAfterStopReason(), + model="claude-3", + ) + + # Get SSE formatted chunks asynchronously + sse_chunks = [] + async for chunk in wrapper.async_anthropic_sse_wrapper(): + sse_chunks.append(chunk) + if len(sse_chunks) >= 10: # Limit to avoid infinite loops in tests + break + + # Verify all chunks are properly formatted as bytes + for chunk in sse_chunks: + assert isinstance(chunk, bytes), "Async SSE chunks should be bytes" + + # Decode and verify SSE format + chunk_str = chunk.decode("utf-8") + lines = chunk_str.split("\n") + + # Should have event and data lines + assert any( + line.startswith("event: ") for line in lines + ), f"Missing event line in: {chunk_str}" + assert any( + line.startswith("data: ") for line in lines + ), f"Missing data line in: {chunk_str}" + + +if __name__ == "__main__": + # Run a quick test + test_anthropic_stream_wrapper_content_after_stop_reason() + print("✅ Sync test passed") + + import asyncio + + asyncio.run(test_async_anthropic_stream_wrapper_content_after_stop_reason()) + print("✅ Async test passed") + + test_usage_merging_behavior() + print("✅ Usage merging test passed") + + test_sse_wrapper_with_content_after_stop_reason() + print("✅ SSE wrapper test passed") + + asyncio.run(test_async_sse_wrapper_with_content_after_stop_reason()) + print("✅ Async SSE wrapper test passed") + + print("🎉 All tests passed!") From 0f6898ad0a9d8769bb26038f80107b309c8fb91e Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 11 Sep 2025 19:10:00 -0700 Subject: [PATCH 42/73] fix(key_management_endpoints.py): check if key is a hashed token or sk key before lookup Fixes https://github.com/BerriAI/litellm/issues/13887 --- .../key_management_endpoints.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index bd8faf34be8..a7f15cc6c19 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -346,6 +346,7 @@ def handle_key_type(data: GenerateKeyRequest, data_json: dict) -> dict: data_json["allowed_routes"] = ["info_routes"] return data_json + async def validate_team_id_used_in_service_account_request( team_id: Optional[str], prisma_client: Optional[PrismaClient], @@ -358,13 +359,13 @@ async def validate_team_id_used_in_service_account_request( status_code=400, detail="team_id is required for service account keys. Please specify `team_id` in the request body.", ) - + if prisma_client is None: raise HTTPException( status_code=400, detail="prisma_client is required for service account keys. Please specify `prisma_client` in the request body.", ) - + # check if team_id exists in the database team = await prisma_client.db.litellm_teamtable.find_unique( where={"team_id": team_id}, @@ -376,6 +377,7 @@ async def validate_team_id_used_in_service_account_request( ) return True + async def _common_key_generation_helper( # noqa: PLR0915 data: GenerateKeyRequest, user_api_key_dict: UserAPIKeyAuth, @@ -557,7 +559,7 @@ async def _common_key_generation_helper( # noqa: PLR0915 status_code=400, detail={ "error": f"Invalid key format. LiteLLM Virtual Key must start with 'sk-'. Received: {data.key}" - } + }, ) response = await generate_key_helper_fn( @@ -2885,7 +2887,10 @@ async def unblock_key( param="key", code=status.HTTP_400_BAD_REQUEST, ) - hashed_token = hash_token(token=data.key) + if data.key.startswith("sk-"): + hashed_token = hash_token(token=data.key) + else: + hashed_token = data.key if litellm.store_audit_logs is True: # make an audit log for key update From 0c8b311155f01f5075ca4deb09ac3a5b390ad2c6 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 11 Sep 2025 19:15:15 -0700 Subject: [PATCH 43/73] test: add unit testing for both flows on key unblock --- .../test_key_management_endpoints.py | 245 +++++++++++++++--- 1 file changed, 202 insertions(+), 43 deletions(-) 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 3a597adef06..2747fab77fa 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 @@ -183,7 +183,9 @@ async def test_budget_reset_and_expires_at_first_of_month(monkeypatch): assert ( response_date.month == expected_month ), f"Expected month {expected_month}, got {response_date.month} for {key}" - assert response_date.day == 1, f"Expected day 1, got {response_date.day} for {key}" + assert ( + response_date.day == 1 + ), f"Expected day 1, got {response_date.day} for {key}" @pytest.mark.asyncio @@ -507,7 +509,6 @@ def test_get_new_token_with_invalid_key(): assert "New key must start with 'sk-'" in str(exc_info.value.detail) - @pytest.mark.asyncio async def test_generate_service_account_requires_team_id(): with pytest.raises(HTTPException): @@ -529,11 +530,12 @@ async def test_generate_service_account_works_with_team_id(): from unittest.mock import patch # Mock the database and router dependencies from proxy_server - with patch('litellm.proxy.proxy_server.prisma_client') as mock_prisma, \ - patch('litellm.proxy.proxy_server.llm_router') as mock_router, \ - patch('litellm.proxy.proxy_server.premium_user', False), \ - patch('litellm.proxy.management_endpoints.key_management_endpoints.generate_key_helper_fn') as mock_generate_key: - + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server.llm_router" + ) as mock_router, patch("litellm.proxy.proxy_server.premium_user", False), patch( + "litellm.proxy.management_endpoints.key_management_endpoints.generate_key_helper_fn" + ) as mock_generate_key: + # Configure mocks mock_prisma.return_value = AsyncMock() mock_router.return_value = None @@ -542,9 +544,9 @@ async def test_generate_service_account_works_with_team_id(): "key": "sk-test-key", "expires": None, "user_id": "test-user", - "team_id": "IJ" + "team_id": "IJ", } - + # This should not raise an exception since team_id is provided await _common_key_generation_helper( data=GenerateKeyRequest( @@ -559,7 +561,6 @@ async def test_generate_service_account_works_with_team_id(): ) - @pytest.mark.asyncio async def test_update_service_account_requires_team_id(): data = UpdateKeyRequest(key="sk-1", metadata={"service_account_id": "sa"}) @@ -571,7 +572,9 @@ async def test_update_service_account_requires_team_id(): @pytest.mark.asyncio async def test_update_service_account_works_with_team_id(): - data = UpdateKeyRequest(key="sk-1", metadata={"service_account_id": "sa"}, team_id="IJ") + data = UpdateKeyRequest( + key="sk-1", metadata={"service_account_id": "sa"}, team_id="IJ" + ) existing_key = LiteLLM_VerificationToken(token="hashed") await prepare_key_update_data(data=data, existing_key_row=existing_key) @@ -580,22 +583,22 @@ async def test_update_service_account_works_with_team_id(): @pytest.mark.asyncio async def test_validate_team_id_used_in_service_account_request_requires_team_id(): """ - Test that validate_team_id_used_in_service_account_request raises HTTPException + Test that validate_team_id_used_in_service_account_request raises HTTPException when team_id is None for service account key generation. """ from litellm.proxy.management_endpoints.key_management_endpoints import ( validate_team_id_used_in_service_account_request, ) - + mock_prisma_client = AsyncMock() - + # Test that HTTPException is raised when team_id is None with pytest.raises(HTTPException) as exc_info: await validate_team_id_used_in_service_account_request( team_id=None, prisma_client=mock_prisma_client, ) - + assert exc_info.value.status_code == 400 assert "team_id is required for service account keys" in str(exc_info.value.detail) @@ -603,7 +606,7 @@ async def test_validate_team_id_used_in_service_account_request_requires_team_id @pytest.mark.asyncio async def test_validate_team_id_used_in_service_account_request_requires_prisma_client(): """ - Test that validate_team_id_used_in_service_account_request raises HTTPException + Test that validate_team_id_used_in_service_account_request raises HTTPException when prisma_client is None for service account key generation. """ from litellm.proxy.management_endpoints.key_management_endpoints import ( @@ -616,78 +619,76 @@ async def test_validate_team_id_used_in_service_account_request_requires_prisma_ team_id="test-team-id", prisma_client=None, ) - + assert exc_info.value.status_code == 400 - assert "prisma_client is required for service account keys" in str(exc_info.value.detail) + assert "prisma_client is required for service account keys" in str( + exc_info.value.detail + ) @pytest.mark.asyncio async def test_validate_team_id_used_in_service_account_request_checks_team_exists(): """ - Test that validate_team_id_used_in_service_account_request validates that + Test that validate_team_id_used_in_service_account_request validates that the team_id exists in the database for service account key generation. """ from litellm.proxy.management_endpoints.key_management_endpoints import ( validate_team_id_used_in_service_account_request, ) - + mock_prisma_client = AsyncMock() - + # Mock the database query to return None (team doesn't exist) mock_find_unique = AsyncMock(return_value=None) mock_prisma_client.db.litellm_teamtable.find_unique = mock_find_unique - + # Test that HTTPException is raised when team doesn't exist in DB with pytest.raises(HTTPException) as exc_info: await validate_team_id_used_in_service_account_request( team_id="non-existent-team-id", prisma_client=mock_prisma_client, ) - + assert exc_info.value.status_code == 400 assert "team_id does not exist in the database" in str(exc_info.value.detail) - + # Verify the database was queried with the correct parameters - mock_find_unique.assert_called_once_with( - where={"team_id": "non-existent-team-id"} - ) + mock_find_unique.assert_called_once_with(where={"team_id": "non-existent-team-id"}) @pytest.mark.asyncio async def test_validate_team_id_used_in_service_account_request_success(): """ - Test that validate_team_id_used_in_service_account_request returns True + Test that validate_team_id_used_in_service_account_request returns True when team_id exists in the database for service account key generation. """ from litellm.proxy.management_endpoints.key_management_endpoints import ( validate_team_id_used_in_service_account_request, ) - + mock_prisma_client = AsyncMock() - + # Mock the database query to return a team object (team exists) mock_team = {"team_id": "existing-team-id", "team_name": "Test Team"} mock_find_unique = AsyncMock(return_value=mock_team) mock_prisma_client.db.litellm_teamtable.find_unique = mock_find_unique - + # Test that function returns True when team exists result = await validate_team_id_used_in_service_account_request( team_id="existing-team-id", prisma_client=mock_prisma_client, ) - + assert result is True - + # Verify the database was queried with the correct parameters - mock_find_unique.assert_called_once_with( - where={"team_id": "existing-team-id"} - ) + mock_find_unique.assert_called_once_with(where={"team_id": "existing-team-id"}) @pytest.mark.asyncio async def test_generate_service_account_key_endpoint_validation(): """ - Test that the /key/service-account/generate endpoint properly validates + Test that the /key/service-account/generate endpoint properly validates team_id requirement and team existence in database. """ from unittest.mock import patch @@ -705,16 +706,16 @@ async def test_generate_service_account_key_endpoint_validation(): ), litellm_changed_by=None, ) - + assert exc_info.value.status_code == 400 assert "team_id is required for service account keys" in str(exc_info.value.detail) - - # Test case 2: Team doesn't exist in database - with patch('litellm.proxy.proxy_server.prisma_client') as mock_prisma: + + # Test case 2: Team doesn't exist in database + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma: # Mock team not found mock_find_unique = AsyncMock(return_value=None) mock_prisma.db.litellm_teamtable.find_unique = mock_find_unique - + with pytest.raises(HTTPException) as exc_info: await generate_service_account_key_fn( data=GenerateKeyRequest(team_id="non-existent-team"), @@ -723,7 +724,165 @@ async def test_generate_service_account_key_endpoint_validation(): ), litellm_changed_by=None, ) - + assert exc_info.value.status_code == 400 assert "team_id does not exist in the database" in str(exc_info.value.detail) + +@pytest.mark.asyncio +async def test_unblock_key_supports_both_sk_and_hashed_tokens(monkeypatch): + """ + Test that the unblock_key endpoint correctly handles both sk- prefixed tokens + and hashed tokens by properly converting sk- tokens to hashed format before + database operations. + """ + from unittest.mock import AsyncMock, MagicMock + + from litellm.proxy._types import BlockKeyRequest + from litellm.proxy.management_endpoints.key_management_endpoints import unblock_key + + # Mock dependencies + mock_prisma_client = AsyncMock() + mock_user_api_key_cache = MagicMock() + mock_proxy_logging_obj = MagicMock() + + # Use a proper 64-character hex hash for testing + test_hashed_token = ( + "a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd" + ) + + # Mock the key record that will be returned from database + mock_key_record = MagicMock() + mock_key_record.token = test_hashed_token + mock_key_record.blocked = False + mock_key_record.model_dump_json.return_value = ( + f'{{"token": "{test_hashed_token}", "blocked": false}}' + ) + + # Mock database operations + 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 get_key_object and _cache_key_object functions + mock_key_object = MagicMock() + mock_key_object.blocked = True # Initially blocked + + # Mock hash_token function + def mock_hash_token(token): + if token == "sk-test123456789": + return test_hashed_token + return token + + # Apply monkeypatch + 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 + ) # Disable audit logs for simpler test + + # Mock get_key_object and _cache_key_object + 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, + ) + + # Create mock request and user auth + mock_request = MagicMock() + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.PROXY_ADMIN, api_key="sk-admin", user_id="admin_user" + ) + + # Test Case 1: Using sk- prefixed token + sk_token_request = BlockKeyRequest(key="sk-test123456789") + + result = await unblock_key( + data=sk_token_request, + http_request=mock_request, + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + + # Verify that the database update was called with hashed token + mock_prisma_client.db.litellm_verificationtoken.update.assert_called_with( + where={"token": test_hashed_token}, data={"blocked": False} + ) + + assert result == mock_key_record + assert mock_key_object.blocked == False # Should be updated to unblocked + + # Reset mocks for second test + mock_prisma_client.db.litellm_verificationtoken.update.reset_mock() + mock_key_object.blocked = True # Reset to blocked state + + # Test Case 2: Using already hashed token + hashed_token_request = BlockKeyRequest(key=test_hashed_token) + + result = await unblock_key( + data=hashed_token_request, + http_request=mock_request, + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + + # Verify that the database update was called with the same hashed token + mock_prisma_client.db.litellm_verificationtoken.update.assert_called_with( + where={"token": test_hashed_token}, data={"blocked": False} + ) + + assert result == mock_key_record + assert mock_key_object.blocked == False # Should be updated to unblocked + + +@pytest.mark.asyncio +async def test_unblock_key_invalid_key_format(monkeypatch): + """ + Test that unblock_key properly validates key format and raises appropriate errors + for invalid keys. + """ + from litellm.proxy._types import BlockKeyRequest + from litellm.proxy.management_endpoints.key_management_endpoints import unblock_key + from litellm.proxy.utils import ProxyException + + # Mock prisma_client to avoid DB connection error + mock_prisma_client = AsyncMock() + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) + + # Mock request and user auth + mock_request = MagicMock() + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.PROXY_ADMIN, api_key="sk-admin", user_id="admin_user" + ) + + # Test with invalid key format + invalid_key_request = BlockKeyRequest(key="invalid-key-format") + + with pytest.raises(ProxyException) as exc_info: + await unblock_key( + data=invalid_key_request, + http_request=mock_request, + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + + assert exc_info.value.code == "400" + assert "Invalid key format" in str(exc_info.value.message) From 51d5255452f7c49f01521b57189ea9ec8fbfd490 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 11 Sep 2025 19:39:06 -0700 Subject: [PATCH 44/73] [Bug]: Azure OpenAI & AI Foundry Reject Image Generation Payload Due to extra_body Injection in LiteLLM v1.76.3 (#14475) * add request body azure img gen * fix test_get_optional_params_image_gen_filters_empty_values * test_azure_image_generation_request_body * test_azure_image_generation_request_body --- litellm/utils.py | 37 ++++++++++++------- .../request_payloads/azure_gpt_image_1.json | 4 ++ .../image_gen_tests/test_image_generation.py | 31 ++++++++++++++++ tests/test_litellm/test_utils.py | 9 +++++ 4 files changed, 68 insertions(+), 13 deletions(-) create mode 100644 tests/image_gen_tests/request_payloads/azure_gpt_image_1.json diff --git a/litellm/utils.py b/litellm/utils.py index 84d86bea647..0d2fe5d4d64 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -2501,6 +2501,23 @@ def get_optional_params_transcription( return optional_params +def _map_openai_size_to_vertex_ai_aspect_ratio(size: Optional[str]) -> str: + """Map OpenAI size parameter to Vertex AI aspectRatio.""" + if size is None: + return "1:1" + + # Map OpenAI size strings to Vertex AI aspect ratio strings + # Vertex AI accepts: "1:1", "9:16", "16:9", "4:3", "3:4" + size_to_aspect_ratio = { + "256x256": "1:1", # Square + "512x512": "1:1", # Square + "1024x1024": "1:1", # Square (default) + "1792x1024": "16:9", # Landscape + "1024x1792": "9:16", # Portrait + } + return size_to_aspect_ratio.get(size, "1:1") # Default to square if size not recognized + + def get_optional_params_image_gen( model: Optional[str] = None, n: Optional[int] = None, @@ -2614,19 +2631,7 @@ def get_optional_params_image_gen( # Map OpenAI size parameter to Vertex AI aspectRatio if size is not None: - # Map OpenAI size strings to Vertex AI aspect ratio strings - # Vertex AI accepts: "1:1", "9:16", "16:9", "4:3", "3:4" - size_to_aspect_ratio = { - "256x256": "1:1", # Square - "512x512": "1:1", # Square - "1024x1024": "1:1", # Square (default) - "1792x1024": "16:9", # Landscape - "1024x1792": "9:16", # Portrait - } - aspect_ratio = size_to_aspect_ratio.get( - size, "1:1" - ) # Default to square if size not recognized - optional_params["aspectRatio"] = aspect_ratio + optional_params["aspectRatio"] = _map_openai_size_to_vertex_ai_aspect_ratio(size) openai_params: list[str] = list(default_params.keys()) if provider_config is not None: @@ -2642,6 +2647,12 @@ def get_optional_params_image_gen( openai_params=openai_params, additional_drop_params=additional_drop_params, ) + # remove keys with None or empty dict/list values to avoid sending empty payloads + optional_params = { + k: v + for k, v in optional_params.items() + if v is not None and (not isinstance(v, (dict, list)) or len(v) > 0) + } return optional_params diff --git a/tests/image_gen_tests/request_payloads/azure_gpt_image_1.json b/tests/image_gen_tests/request_payloads/azure_gpt_image_1.json new file mode 100644 index 00000000000..1ad57bc0d4c --- /dev/null +++ b/tests/image_gen_tests/request_payloads/azure_gpt_image_1.json @@ -0,0 +1,4 @@ +{ + "model": "gpt-image-1", + "prompt": "test prompt" +} diff --git a/tests/image_gen_tests/test_image_generation.py b/tests/image_gen_tests/test_image_generation.py index 61843d3151e..c3900342954 100644 --- a/tests/image_gen_tests/test_image_generation.py +++ b/tests/image_gen_tests/test_image_generation.py @@ -5,6 +5,7 @@ import logging import os import sys import traceback +from unittest.mock import AsyncMock, patch sys.path.insert( @@ -329,3 +330,33 @@ async def test_aiml_image_generation_with_dynamic_api_key(): assert captured_json_data is not None assert captured_json_data["prompt"] == "A cute baby sea otter" assert captured_json_data["model"] == "flux-pro/v1.1" + +@pytest.mark.asyncio +async def test_azure_image_generation_request_body(): + from litellm import aimage_generation + test_dir = os.path.dirname(__file__) + expected_path = os.path.join( + test_dir, "request_payloads", "azure_gpt_image_1.json" + ) + with open(expected_path, "r") as f: + expected_body = json.load(f) + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + new_callable=AsyncMock, + ) as mock_post: + mock_post.side_effect = Exception("test") + + with pytest.raises(Exception): + await aimage_generation( + model="azure/gpt-image-1", + prompt="test prompt", + api_base="https://example.azure.com", + api_key="test-key", + api_version="2025-04-01-preview", + ) + + mock_post.assert_called_once() + call_args = mock_post.call_args + request_json = call_args.kwargs.get("json", {}) + assert request_json == expected_body diff --git a/tests/test_litellm/test_utils.py b/tests/test_litellm/test_utils.py index 2bf2935901e..f9f150b6234 100644 --- a/tests/test_litellm/test_utils.py +++ b/tests/test_litellm/test_utils.py @@ -84,6 +84,15 @@ def test_get_optional_params_image_gen_vertex_ai_size(): assert optional_params["sampleCount"] == 1 +def test_get_optional_params_image_gen_filters_empty_values(): + optional_params = get_optional_params_image_gen( + model="gpt-image-1", + custom_llm_provider="openai", + extra_body={}, + ) + assert optional_params == {} + + def test_all_model_configs(): from litellm.llms.vertex_ai.vertex_ai_partner_models.ai21.transformation import ( VertexAIAi21Config, From 69ef062f5565f9411e8648d758a52da878cc52c6 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 11 Sep 2025 19:56:44 -0700 Subject: [PATCH 45/73] fix tiered_pricing test --- ...odel_prices_and_context_window_backup.json | 5 ----- model_prices_and_context_window.json | 5 ----- tests/test_litellm/test_utils.py | 20 +++++++++++++++++++ 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 972e212e693..46331c7cff7 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -19132,7 +19132,6 @@ "supports_function_calling": true, "supports_tool_choice": true, "supports_reasoning": true, - "supports_caching": true, "mode": "chat", "tiered_pricing": [ {"range": [0, 256e3], "input_cost_per_token": 5e-08, "output_cost_per_token": 4e-07}, @@ -19161,7 +19160,6 @@ "supports_function_calling": true, "supports_tool_choice": true, "supports_reasoning": true, - "supports_caching": true, "mode": "chat", "tiered_pricing": [ {"range": [0, 32e3], "input_cost_per_token": 1e-06, "output_cost_per_token": 5e-06, "cache_read_input_token_cost": 1e-07}, @@ -19196,7 +19194,6 @@ "supports_function_calling": true, "supports_tool_choice": true, "supports_reasoning": true, - "supports_caching": true, "mode": "chat", "tiered_pricing": [ {"range": [0, 32e3], "input_cost_per_token": 3e-07, "output_cost_per_token": 1.5e-06, "cache_read_input_token_cost": 8e-08}, @@ -19302,7 +19299,6 @@ "supports_function_calling": true, "supports_tool_choice": true, "supports_reasoning": true, - "supports_caching": true, "mode": "chat", "tiered_pricing": [ {"range": [0, 256e3], "input_cost_per_token": 5e-08, "output_cost_per_token": 4e-07}, @@ -19355,7 +19351,6 @@ "max_tokens": 131072, "max_input_tokens": 98304, "max_output_tokens": 8192, - "max_chain_of_thought_tokens": 32768, "input_cost_per_token": 8e-07, "output_cost_per_token": 2.4e-06, "litellm_provider": "dashscope", diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 972e212e693..46331c7cff7 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -19132,7 +19132,6 @@ "supports_function_calling": true, "supports_tool_choice": true, "supports_reasoning": true, - "supports_caching": true, "mode": "chat", "tiered_pricing": [ {"range": [0, 256e3], "input_cost_per_token": 5e-08, "output_cost_per_token": 4e-07}, @@ -19161,7 +19160,6 @@ "supports_function_calling": true, "supports_tool_choice": true, "supports_reasoning": true, - "supports_caching": true, "mode": "chat", "tiered_pricing": [ {"range": [0, 32e3], "input_cost_per_token": 1e-06, "output_cost_per_token": 5e-06, "cache_read_input_token_cost": 1e-07}, @@ -19196,7 +19194,6 @@ "supports_function_calling": true, "supports_tool_choice": true, "supports_reasoning": true, - "supports_caching": true, "mode": "chat", "tiered_pricing": [ {"range": [0, 32e3], "input_cost_per_token": 3e-07, "output_cost_per_token": 1.5e-06, "cache_read_input_token_cost": 8e-08}, @@ -19302,7 +19299,6 @@ "supports_function_calling": true, "supports_tool_choice": true, "supports_reasoning": true, - "supports_caching": true, "mode": "chat", "tiered_pricing": [ {"range": [0, 256e3], "input_cost_per_token": 5e-08, "output_cost_per_token": 4e-07}, @@ -19355,7 +19351,6 @@ "max_tokens": 131072, "max_input_tokens": 98304, "max_output_tokens": 8192, - "max_chain_of_thought_tokens": 32768, "input_cost_per_token": 8e-07, "output_cost_per_token": 2.4e-06, "litellm_provider": "dashscope", diff --git a/tests/test_litellm/test_utils.py b/tests/test_litellm/test_utils.py index f9f150b6234..0c7aa527240 100644 --- a/tests/test_litellm/test_utils.py +++ b/tests/test_litellm/test_utils.py @@ -652,6 +652,26 @@ def test_aaamodel_prices_and_context_window_json_is_valid(): }, }, "supports_native_streaming": {"type": "boolean"}, + "tiered_pricing": { + "type": "array", + "items": { + "type": "object", + "properties": { + "range": { + "type": "array", + "items": {"type": "number"}, + "minItems": 2, + "maxItems": 2 + }, + "input_cost_per_token": {"type": "number"}, + "output_cost_per_token": {"type": "number"}, + "cache_read_input_token_cost": {"type": "number"}, + "output_cost_per_reasoning_token": {"type": "number"} + }, + "required": ["range"], + "additionalProperties": False + } + }, }, "additionalProperties": False, }, From 94038108ad75dc25efc2bafbd1d9a7b0165aaa1e Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 11 Sep 2025 20:00:54 -0700 Subject: [PATCH 46/73] fix dashscope api base --- .../add_model/provider_specific_fields.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx b/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx index ce19a248f83..a76d7b64ffe 100644 --- a/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx +++ b/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx @@ -206,6 +206,22 @@ const PROVIDER_CREDENTIAL_FIELDS: Record = required: true } ], + [Providers.Dashscope]: [ + { + key: "api_key", + label: "Dashscope API Key", + type: "password", + required: true + }, + { + key: "api_base", + label: "API Base", + placeholder: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", + defaultValue: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", + required: true, + tooltip: "The base URL for your Dashscope server. Defaults to https://dashscope-intl.aliyuncs.com/compatible-mode/v1 if not specified." + } + ], [Providers.OpenAI_Text_Compatible]: [ { key: "api_base", From d89152bb2ccce4ab7713091715dd0abc11dd9ce3 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 11 Sep 2025 20:04:24 -0700 Subject: [PATCH 47/73] feat(litellm_logging.py): support new litellm debug parameter - `litellm_request_debug` on requests enables printing raw request when flag is set to true on requests --- litellm/constants.py | 10 +++-- .../litellm_core_utils/get_litellm_params.py | 2 + litellm/litellm_core_utils/litellm_logging.py | 45 +++++++++++++------ litellm/main.py | 44 ++++++++++++------ litellm/types/utils.py | 3 +- 5 files changed, 73 insertions(+), 31 deletions(-) diff --git a/litellm/constants.py b/litellm/constants.py index 75c25d9ea9e..c0ce0f265b5 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -15,7 +15,7 @@ DEFAULT_SQS_FLUSH_INTERVAL_SECONDS = int( os.getenv("DEFAULT_SQS_FLUSH_INTERVAL_SECONDS", 10) ) DEFAULT_NUM_WORKERS_LITELLM_PROXY = int( - os.getenv("DEFAULT_NUM_WORKERS_LITELLM_PROXY", os.cpu_count() or 4) + os.getenv("DEFAULT_NUM_WORKERS_LITELLM_PROXY", 1) ) DEFAULT_SQS_BATCH_SIZE = int(os.getenv("DEFAULT_SQS_BATCH_SIZE", 512)) SQS_SEND_MESSAGE_ACTION = "SendMessage" @@ -60,7 +60,9 @@ DEFAULT_REASONING_EFFORT_MINIMAL_THINKING_BUDGET_GEMINI_2_5_PRO = int( os.getenv("DEFAULT_REASONING_EFFORT_MINIMAL_THINKING_BUDGET_GEMINI_2_5_PRO", 128) ) DEFAULT_REASONING_EFFORT_MINIMAL_THINKING_BUDGET_GEMINI_2_5_FLASH_LITE = int( - os.getenv("DEFAULT_REASONING_EFFORT_MINIMAL_THINKING_BUDGET_GEMINI_2_5_FLASH_LITE", 512) + os.getenv( + "DEFAULT_REASONING_EFFORT_MINIMAL_THINKING_BUDGET_GEMINI_2_5_FLASH_LITE", 512 + ) ) # Generic fallback for unknown models @@ -949,7 +951,9 @@ LITELLM_CLI_SESSION_TOKEN_PREFIX = "litellm-session-token" DB_SPEND_UPDATE_JOB_NAME = "db_spend_update_job" PROMETHEUS_EMIT_BUDGET_METRICS_JOB_NAME = "prometheus_emit_budget_metrics" CLOUDZERO_EXPORT_USAGE_DATA_JOB_NAME = "cloudzero_export_usage_data" -CLOUDZERO_MAX_FETCHED_DATA_RECORDS = int(os.getenv("CLOUDZERO_MAX_FETCHED_DATA_RECORDS", 50000)) +CLOUDZERO_MAX_FETCHED_DATA_RECORDS = int( + os.getenv("CLOUDZERO_MAX_FETCHED_DATA_RECORDS", 50000) +) SPEND_LOG_CLEANUP_JOB_NAME = "spend_log_cleanup" SPEND_LOG_RUN_LOOPS = int(os.getenv("SPEND_LOG_RUN_LOOPS", 500)) SPEND_LOG_CLEANUP_BATCH_SIZE = int(os.getenv("SPEND_LOG_CLEANUP_BATCH_SIZE", 1000)) diff --git a/litellm/litellm_core_utils/get_litellm_params.py b/litellm/litellm_core_utils/get_litellm_params.py index c354dea0241..c167c202e5d 100644 --- a/litellm/litellm_core_utils/get_litellm_params.py +++ b/litellm/litellm_core_utils/get_litellm_params.py @@ -62,6 +62,7 @@ def get_litellm_params( use_litellm_proxy: Optional[bool] = None, api_version: Optional[str] = None, max_retries: Optional[int] = None, + litellm_request_debug: Optional[bool] = None, **kwargs, ) -> dict: litellm_params = { @@ -118,5 +119,6 @@ def get_litellm_params( "vertex_credentials": kwargs.get("vertex_credentials"), "vertex_project": kwargs.get("vertex_project"), "use_litellm_proxy": use_litellm_proxy, + "litellm_request_debug": litellm_request_debug, } return litellm_params diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 19d7c5512ba..59dd09f2728 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -245,6 +245,7 @@ class Logging(LiteLLMLoggingBaseClass): global supabaseClient, promptLayerLogger, weightsBiasesLogger, logfireLogger, capture_exception, add_breadcrumb, lunaryLogger, logfireLogger, prometheusLogger, slack_app custom_pricing: bool = False stream_options = None + litellm_request_debug: bool = False def __init__( self, @@ -470,6 +471,7 @@ class Logging(LiteLLMLoggingBaseClass): **self.litellm_params, **scrub_sensitive_keys_in_metadata(litellm_params), } + self.litellm_request_debug = litellm_params.get("litellm_request_debug", False) self.logger_fn = litellm_params.get("logger_fn", None) verbose_logger.debug(f"self.optional_params: {self.optional_params}") @@ -907,13 +909,19 @@ class Logging(LiteLLMLoggingBaseClass): Prints the RAW curl command sent from LiteLLM """ - if _is_debugging_on(): + if _is_debugging_on() or self.litellm_request_debug: if json_logs: masked_headers = self._get_masked_headers(headers) - verbose_logger.debug( - "POST Request Sent from LiteLLM", - extra={"api_base": {api_base}, **masked_headers}, - ) + if self.litellm_request_debug: + verbose_logger.warning( # .warning ensures this shows up in all environments + "POST Request Sent from LiteLLM", + extra={"api_base": {api_base}, **masked_headers}, + ) + else: + verbose_logger.debug( + "POST Request Sent from LiteLLM", + extra={"api_base": {api_base}, **masked_headers}, + ) else: headers = additional_args.get("headers", {}) if headers is None: @@ -926,7 +934,12 @@ class Logging(LiteLLMLoggingBaseClass): additional_args=additional_args, data=data, ) - verbose_logger.debug(f"\033[92m{curl_command}\033[0m\n") + if self.litellm_request_debug: + verbose_logger.warning( + f"\033[92m{curl_command}\033[0m\n" + ) # .warning ensures this shows up in all environments + else: + verbose_logger.debug(f"\033[92m{curl_command}\033[0m\n") def _get_request_body(self, data: dict) -> str: return str(data) @@ -1714,12 +1727,16 @@ class Logging(LiteLLMLoggingBaseClass): response_obj=result, start_time=start_time, end_time=end_time, - litellm_call_id=current_call_id - if ( - current_call_id := litellm_params.get("litellm_call_id") - ) - is not None - else str(uuid.uuid4()), + litellm_call_id=( + current_call_id + if ( + current_call_id := litellm_params.get( + "litellm_call_id" + ) + ) + is not None + else str(uuid.uuid4()) + ), print_verbose=print_verbose, ) if callback == "wandb" and weightsBiasesLogger is not None: @@ -3367,6 +3384,7 @@ def _init_custom_logger_compatible_class( # noqa: PLR0915 return galileo_logger # type: ignore elif logging_integration == "cloudzero": from litellm.integrations.cloudzero.cloudzero import CloudZeroLogger + for callback in _in_memory_loggers: if isinstance(callback, CloudZeroLogger): return callback # type: ignore @@ -3594,6 +3612,7 @@ def get_custom_logger_compatible_class( # noqa: PLR0915 return callback elif logging_integration == "cloudzero": from litellm.integrations.cloudzero.cloudzero import CloudZeroLogger + for callback in _in_memory_loggers: if isinstance(callback, CloudZeroLogger): return callback @@ -4504,7 +4523,7 @@ def get_standard_logging_object_payload( def emit_standard_logging_payload(payload: StandardLoggingPayload): if os.getenv("LITELLM_PRINT_STANDARD_LOGGING_PAYLOAD"): - print(json.dumps(payload, indent=4)) # noqa + print(json.dumps(payload, indent=4)) # noqa def get_standard_logging_metadata( diff --git a/litellm/main.py b/litellm/main.py index d7395eb1457..6c81d3eded9 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -150,9 +150,9 @@ from .llms.custom_httpx.llm_http_handler import BaseLLMHTTPHandler from .llms.custom_llm import CustomLLM, custom_chat_llm_router from .llms.databricks.embed.handler import DatabricksEmbeddingHandler from .llms.deprecated_providers import aleph_alpha, palm +from .llms.gemini.common_utils import get_api_key_from_env from .llms.groq.chat.handler import GroqChatCompletion from .llms.heroku.chat.transformation import HerokuChatConfig -from .llms.gemini.common_utils import get_api_key_from_env from .llms.huggingface.embedding.handler import HuggingFaceEmbedding from .llms.nlp_cloud.chat.handler import completion as nlp_cloud_chat_completion from .llms.oci.chat.transformation import OCIChatConfig @@ -358,7 +358,9 @@ async def acompletion( logprobs: Optional[bool] = None, top_logprobs: Optional[int] = None, deployment_id=None, - reasoning_effort: Optional[Literal["none", "minimal", "low", "medium", "high", "default"]] = None, + reasoning_effort: Optional[ + Literal["none", "minimal", "low", "medium", "high", "default"] + ] = None, safety_identifier: Optional[str] = None, # set api_base, api_version, api_key base_url: Optional[str] = None, @@ -504,7 +506,9 @@ async def acompletion( } if custom_llm_provider is None: _, custom_llm_provider, _, _ = get_llm_provider( - model=model, custom_llm_provider=custom_llm_provider, api_base=completion_kwargs.get("base_url", None) + model=model, + custom_llm_provider=custom_llm_provider, + api_base=completion_kwargs.get("base_url", None), ) fallbacks = fallbacks or litellm.model_fallbacks @@ -899,7 +903,9 @@ def completion( # type: ignore # noqa: PLR0915 logit_bias: Optional[dict] = None, user: Optional[str] = None, # openai v1.0+ new params - reasoning_effort: Optional[Literal["none", "minimal", "low", "medium", "high", "default"]] = None, + reasoning_effort: Optional[ + Literal["none", "minimal", "low", "medium", "high", "default"] + ] = None, response_format: Optional[Union[dict, Type[BaseModel]]] = None, seed: Optional[int] = None, tools: Optional[List] = None, @@ -1116,10 +1122,12 @@ def completion( # type: ignore # noqa: PLR0915 ) if provider_specific_header is not None: - headers.update(ProviderSpecificHeaderUtils.get_provider_specific_headers( - provider_specific_header=provider_specific_header, - custom_llm_provider=custom_llm_provider, - )) + headers.update( + ProviderSpecificHeaderUtils.get_provider_specific_headers( + provider_specific_header=provider_specific_header, + custom_llm_provider=custom_llm_provider, + ) + ) if model_response is not None and hasattr(model_response, "_hidden_params"): model_response._hidden_params["custom_llm_provider"] = custom_llm_provider @@ -1325,6 +1333,7 @@ def completion( # type: ignore # noqa: PLR0915 azure_scope=kwargs.get("azure_scope"), max_retries=max_retries, timeout=timeout, + litellm_request_debug=kwargs.get("litellm_request_debug", False), ) cast(LiteLLMLoggingObj, logging).update_environment_variables( model=model, @@ -2712,9 +2721,7 @@ def completion( # type: ignore # noqa: PLR0915 ) api_key = ( - api_key - or litellm.api_key - or get_secret("VERCEL_AI_GATEWAY_API_KEY") + api_key or litellm.api_key or get_secret("VERCEL_AI_GATEWAY_API_KEY") ) vercel_site_url = get_secret("VERCEL_SITE_URL") or "https://litellm.ai" @@ -2730,7 +2737,7 @@ def completion( # type: ignore # noqa: PLR0915 vercel_headers.update(_headers) headers = vercel_headers - + ## Load Config config = litellm.VercelAIGatewayConfig.get_config() for k, v in config.items(): @@ -3712,7 +3719,9 @@ async def aembedding(*args, **kwargs) -> EmbeddingResponse: func_with_context = partial(ctx.run, func) _, custom_llm_provider, _, _ = get_llm_provider( - model=model, custom_llm_provider=custom_llm_provider, api_base=kwargs.get("api_base", None) + model=model, + custom_llm_provider=custom_llm_provider, + api_base=kwargs.get("api_base", None), ) # Await normally @@ -5780,7 +5789,14 @@ async def ahealth_check( input=input or ["test"], ), "audio_speech": lambda: litellm.aspeech( - **{**_filter_model_params(model_params), **({"voice": "alloy"} if "voice" not in _filter_model_params(model_params) else {})}, + **{ + **_filter_model_params(model_params), + **( + {"voice": "alloy"} + if "voice" not in _filter_model_params(model_params) + else {} + ), + }, input=prompt or "test", ), "audio_transcription": lambda: litellm.atranscription( diff --git a/litellm/types/utils.py b/litellm/types/utils.py index c6f7098a1a7..6452254d11f 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -1995,7 +1995,7 @@ class StandardLoggingGuardrailInformation(TypedDict, total=False): ] guardrail_request: Optional[dict] guardrail_response: Optional[Union[dict, str, List[dict]]] - guardrail_status: Literal["success", "failure","blocked"] + guardrail_status: Literal["success", "failure", "blocked"] start_time: Optional[float] end_time: Optional[float] duration: Optional[float] @@ -2123,6 +2123,7 @@ all_litellm_params = [ "metadata", "litellm_metadata", "litellm_trace_id", + "litellm_request_debug", "guardrails", "tags", "acompletion", From 1e230e87e3d60fbe5bc7b51504244d68a36c1f3b Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 11 Sep 2025 20:05:29 -0700 Subject: [PATCH 48/73] fixes for mypy --- litellm/responses/mcp/mcp_streaming_iterator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/litellm/responses/mcp/mcp_streaming_iterator.py b/litellm/responses/mcp/mcp_streaming_iterator.py index b0673880cec..8045d1c1dca 100644 --- a/litellm/responses/mcp/mcp_streaming_iterator.py +++ b/litellm/responses/mcp/mcp_streaming_iterator.py @@ -70,7 +70,8 @@ async def create_mcp_list_tools_events( mcp_tools_dict = [] for tool in filtered_mcp_tools: if hasattr(tool, 'model_dump') and callable(getattr(tool, 'model_dump')): - mcp_tools_dict.append(tool.model_dump()) + # Type cast to help mypy understand this is safe after hasattr check + mcp_tools_dict.append(cast(Any, tool).model_dump()) elif hasattr(tool, '__dict__'): mcp_tools_dict.append(tool.__dict__) else: From 48619f2b7bd6a71a86c4af57d151a21d1538d1ed Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 11 Sep 2025 20:07:26 -0700 Subject: [PATCH 49/73] fix(litellm_logging.py): support emitting raw response on self.litellm_request_debug is true Addresses https://github.com/BerriAI/litellm/issues/13814 --- litellm/litellm_core_utils/litellm_logging.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 59dd09f2728..2f21d280899 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -996,8 +996,14 @@ class Logging(LiteLLMLoggingBaseClass): self.model_call_details["additional_args"] = additional_args self.model_call_details["log_event_type"] = "post_api_call" + if self.litellm_request_debug: + attr = "warning" + else: + attr = "debug" + if json_logs: - verbose_logger.debug( + callattr = getattr(verbose_logger, attr) + callattr( "RAW RESPONSE:\n{}\n\n".format( self.model_call_details.get( "original_response", self.model_call_details @@ -1005,7 +1011,8 @@ class Logging(LiteLLMLoggingBaseClass): ), ) else: - print_verbose( + callattr = getattr(verbose_logger, attr) + callattr( "RAW RESPONSE:\n{}\n\n".format( self.model_call_details.get( "original_response", self.model_call_details From 32d87c242bf6942de3ac738f90ebcae4e8ffab08 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 11 Sep 2025 20:07:41 -0700 Subject: [PATCH 50/73] [Fixes] Using Qwen API Tiered Pricing (#14479) * fix: use dashscope cost calc * add qwen logo --- litellm/cost_calculator.py | 5 ++ litellm/llms/dashscope/cost_calculator.py | 33 +++++----- .../_experimental/out/assets/logos/qwen.png | Bin 0 -> 49453 bytes litellm/proxy/proxy_config.yaml | 4 +- .../test_dashscope_cost_calculator.py | 58 +++++++++++++----- .../out/assets/logos/qwen.png | Bin 0 -> 49453 bytes .../public/assets/logos/qwen.png | Bin 0 -> 49453 bytes 7 files changed, 63 insertions(+), 37 deletions(-) create mode 100644 litellm/proxy/_experimental/out/assets/logos/qwen.png create mode 100644 ui/litellm-dashboard/out/assets/logos/qwen.png create mode 100644 ui/litellm-dashboard/public/assets/logos/qwen.png diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index 01f3e2472f8..5d8f5faadf1 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -344,6 +344,11 @@ def cost_per_token( # noqa: PLR0915 return perplexity_cost_per_token(model=model, usage=usage_block) elif custom_llm_provider == "xai": return xai_cost_per_token(model=model, usage=usage_block) + elif custom_llm_provider == "dashscope": + from litellm.llms.dashscope.cost_calculator import ( + cost_per_token as dashscope_cost_per_token, + ) + return dashscope_cost_per_token(model=model, usage=usage_block) else: model_info = _cached_get_model_info_helper( model=model, custom_llm_provider=custom_llm_provider diff --git a/litellm/llms/dashscope/cost_calculator.py b/litellm/llms/dashscope/cost_calculator.py index 6f9ebe302f4..107eb7f5adf 100644 --- a/litellm/llms/dashscope/cost_calculator.py +++ b/litellm/llms/dashscope/cost_calculator.py @@ -45,36 +45,33 @@ def _calculate_tiered_cost( cost_key: str, fallback_cost_key: Optional[str] = None ) -> float: - """Calculate cost using tiered pricing structure.""" + """Calculate cost using tiered pricing structure. + + Finds the appropriate tier based on token count and applies that tier's rate to all tokens. + """ if not tiered_pricing or tokens <= 0: return 0.0 - total_cost = 0.0 - tokens_processed = 0 - + # Find the appropriate tier for the token count for tier in tiered_pricing: - if tokens_processed >= tokens: - break - tier_range = tier.get("range", []) if len(tier_range) != 2: continue range_start, range_end = tier_range - if tokens <= range_start: - break - - tier_start = max(range_start, tokens_processed) - tier_end = min(range_end, tokens) - - if tier_end > tier_start: - tokens_in_tier = tier_end - tier_start + # Check if tokens fall within this tier's range + if range_start <= tokens <= range_end: cost_per_token = tier.get(cost_key) or tier.get(fallback_cost_key, 0) - total_cost += tokens_in_tier * cost_per_token - tokens_processed = tier_end + return tokens * cost_per_token - return total_cost + # If no tier matches, use the last tier (highest tier) + if tiered_pricing: + last_tier = tiered_pricing[-1] + cost_per_token = last_tier.get(cost_key) or last_tier.get(fallback_cost_key, 0) + return tokens * cost_per_token + + return 0.0 def _calculate_flat_cost(tokens: int, cost_per_token: float) -> float: diff --git a/litellm/proxy/_experimental/out/assets/logos/qwen.png b/litellm/proxy/_experimental/out/assets/logos/qwen.png new file mode 100644 index 0000000000000000000000000000000000000000..d9feba46a28e64110d309b027a932328c62e5d95 GIT binary patch literal 49453 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 WSdSYrJr;`iChYdxTa{UOT=)&NoEbF$ literal 0 HcmV?d00001 diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index 54bfdac55f9..918e11eea62 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -9,9 +9,9 @@ model_list: - model_name: openai/* litellm_params: model: openai/* - - model_name: gemini/* + - model_name: dashscope/* litellm_params: - model: gemini/* + model: dashscope/* litellm_settings: diff --git a/tests/test_litellm/llms/dashscope/test_dashscope_cost_calculator.py b/tests/test_litellm/llms/dashscope/test_dashscope_cost_calculator.py index 73e992e22d1..8adc793ae74 100644 --- a/tests/test_litellm/llms/dashscope/test_dashscope_cost_calculator.py +++ b/tests/test_litellm/llms/dashscope/test_dashscope_cost_calculator.py @@ -96,10 +96,10 @@ class TestDashscopeCostCalculator: assert math.isclose(prompt_cost, expected_prompt_cost, rel_tol=1e-10) assert math.isclose(completion_cost, expected_completion_cost, rel_tol=1e-10) - def test_tiered_pricing_multiple_tiers(self): - """Test tiered pricing when tokens span multiple tiers.""" + def test_tiered_pricing_higher_tier(self): + """Test tiered pricing when tokens fall in higher tier (tier 3).""" usage = Usage( - prompt_tokens=150000, # Spans tiers 1 (0-32K), 2 (32K-128K), 3 (128K-256K) + prompt_tokens=150000, # Falls in tier 3 (128K-256K) completion_tokens=2000, total_tokens=152000 ) @@ -110,13 +110,13 @@ class TestDashscopeCostCalculator: ) # Expected input cost calculation: - # Tier 1 (0-32K): 32,000 tokens * $1e-6 = $0.032 - # Tier 2 (32K-128K): 96,000 tokens * $1.8e-6 = $0.1728 - # Tier 3 (128K-256K): 22,000 tokens * $3e-6 = $0.066 - # Total input cost = $0.032 + $0.1728 + $0.066 = $0.2708 + # 150,000 tokens falls in tier 3 (128K-256K), so all tokens are charged at tier 3 rate + # Input: 150,000 tokens * $3e-6 = $0.45 + # Output: 2,000 tokens falls in tier 1 (0-32K), so charged at tier 1 rate + # Output: 2,000 tokens * $5e-6 = $0.01 - expected_prompt_cost = (32000 * 1e-6) + (96000 * 1.8e-6) + (22000 * 3e-6) - expected_completion_cost = 2000 * 5e-6 # All in tier 1 for output + expected_prompt_cost = 150000 * 3e-6 # All tokens at tier 3 rate + expected_completion_cost = 2000 * 5e-6 # All tokens at tier 1 rate assert math.isclose(prompt_cost, expected_prompt_cost, rel_tol=1e-10) assert math.isclose(completion_cost, expected_completion_cost, rel_tol=1e-10) @@ -140,20 +140,44 @@ class TestDashscopeCostCalculator: ) # Expected cost calculation: - # Regular tokens: 40,000 (32K in tier 1 + 8K in tier 2) - # - Tier 1: 32,000 * $1e-6 = $0.032 - # - Tier 2: 8,000 * $1.8e-6 = $0.0144 - # Cached tokens: 10,000 in tier 1 at discounted rate - # - Tier 1 cached: 10,000 * $1e-7 = $0.001 - # Total input cost = $0.032 + $0.0144 + $0.001 = $0.0474 + # Regular tokens: 40,000 falls in tier 2 (32K-128K), so all charged at tier 2 rate + # - Regular: 40,000 * $1.8e-6 = $0.072 + # Cached tokens: 10,000 falls in tier 1 (0-32K), so charged at tier 1 cached rate + # - Cached: 10,000 * $1e-7 = $0.001 + # Total input cost = $0.072 + $0.001 = $0.073 regular_tokens = 40000 cached_tokens = 10000 - expected_regular_cost = (32000 * 1e-6) + (8000 * 1.8e-6) + expected_regular_cost = regular_tokens * 1.8e-6 # Tier 2 rate expected_cached_cost = cached_tokens * 1e-7 # Tier 1 cached rate expected_prompt_cost = expected_regular_cost + expected_cached_cost - expected_completion_cost = 1000 * 5e-6 + expected_completion_cost = 1000 * 5e-6 # Tier 1 rate + + assert math.isclose(prompt_cost, expected_prompt_cost, rel_tol=1e-10) + assert math.isclose(completion_cost, expected_completion_cost, rel_tol=1e-10) + + def test_tiered_pricing_highest_tier(self): + """Test tiered pricing when tokens exceed highest tier range.""" + usage = Usage( + prompt_tokens=2000000, # Exceeds tier 4 max (1M), should use tier 4 rate + completion_tokens=5000, + total_tokens=2005000 + ) + + prompt_cost, completion_cost = dashscope_cost_per_token( + model="qwen3-coder-plus", + usage=usage + ) + + # Expected cost calculation: + # 2,000,000 tokens exceeds tier 4 (256K-1M), so use tier 4 rate for all tokens + # Input: 2,000,000 tokens * $6e-6 = $12.0 + # Output: 5,000 tokens falls in tier 1 (0-32K), so charged at tier 1 rate + # Output: 5,000 tokens * $5e-6 = $0.025 + + expected_prompt_cost = 2000000 * 6e-6 # Tier 4 rate (highest tier) + expected_completion_cost = 5000 * 5e-6 # Tier 1 rate assert math.isclose(prompt_cost, expected_prompt_cost, rel_tol=1e-10) assert math.isclose(completion_cost, expected_completion_cost, rel_tol=1e-10) \ No newline at end of file diff --git a/ui/litellm-dashboard/out/assets/logos/qwen.png b/ui/litellm-dashboard/out/assets/logos/qwen.png new file mode 100644 index 0000000000000000000000000000000000000000..d9feba46a28e64110d309b027a932328c62e5d95 GIT binary patch literal 49453 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 WSdSYrJr;`iChYdxTa{UOT=)&NoEbF$ literal 0 HcmV?d00001 diff --git a/ui/litellm-dashboard/public/assets/logos/qwen.png b/ui/litellm-dashboard/public/assets/logos/qwen.png new file mode 100644 index 0000000000000000000000000000000000000000..d9feba46a28e64110d309b027a932328c62e5d95 GIT binary patch literal 49453 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 WSdSYrJr;`iChYdxTa{UOT=)&NoEbF$ literal 0 HcmV?d00001 From 698a2dd382f59face841ba387ae9d40ed41f47bc Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 11 Sep 2025 20:12:01 -0700 Subject: [PATCH 51/73] fix: convert_anthropic_tool_to_databricks_tool --- litellm/llms/databricks/chat/transformation.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/litellm/llms/databricks/chat/transformation.py b/litellm/llms/databricks/chat/transformation.py index 4852f2e7106..a1370074238 100644 --- a/litellm/llms/databricks/chat/transformation.py +++ b/litellm/llms/databricks/chat/transformation.py @@ -169,18 +169,20 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): if tool is None: return None - kwags: dict = { + # Build DatabricksFunction explicitly to avoid parameter conflicts + function_params: DatabricksFunction = { "name": tool["name"], "parameters": cast(dict, tool.get("input_schema") or {}) } - + + # Only add description if it exists description = tool.get("description") if description is not None: - kwags["description"] = cast(Union[dict, str], description) + function_params["description"] = cast(Union[dict, str], description) return DatabricksTool( type="function", - function=DatabricksFunction(name=tool["name"], **kwags), + function=function_params, ) def _map_openai_to_dbrx_tool(self, model: str, tools: List) -> List[DatabricksTool]: From c802c472b5598de67126d3c93b75ac08ca498c86 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 11 Sep 2025 20:17:39 -0700 Subject: [PATCH 52/73] docs(debugging.md): document new feature Closes https://github.com/BerriAI/litellm/issues/13814 --- docs/my-website/docs/proxy/debugging.md | 68 +++++++++++++++++++------ 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/docs/my-website/docs/proxy/debugging.md b/docs/my-website/docs/proxy/debugging.md index 5cca6541763..fbcac24a4d6 100644 --- a/docs/my-website/docs/proxy/debugging.md +++ b/docs/my-website/docs/proxy/debugging.md @@ -11,13 +11,13 @@ The proxy also supports json logs. [See here](#json-logs) **via cli** -```bash +```bash showLineNumbers $ litellm --debug ``` **via env** -```python +```python showLineNumbers os.environ["LITELLM_LOG"] = "INFO" ``` @@ -25,25 +25,25 @@ os.environ["LITELLM_LOG"] = "INFO" **via cli** -```bash +```bash showLineNumbers $ litellm --detailed_debug ``` **via env** -```python +```python showLineNumbers os.environ["LITELLM_LOG"] = "DEBUG" ``` ### Debug Logs Run the proxy with `--detailed_debug` to view detailed debug logs -```shell +```shell showLineNumbers litellm --config /path/to/config.yaml --detailed_debug ``` When making requests you should see the POST request sent by LiteLLM to the LLM on the Terminal output -```shell +```shell showLineNumbers POST Request Sent from LiteLLM: curl -X POST \ https://api.openai.com/v1/chat/completions \ @@ -51,25 +51,63 @@ https://api.openai.com/v1/chat/completions \ -d '{"model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": "this is a test request, write a short poem"}]}' ``` +## Debug single request + +Pass in `litellm_request_debug=True` in the request body + +```bash showLineNumbers +curl -L -X POST 'http://0.0.0.0:4000/chat/completions' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer sk-1234' \ +-d '{ + "model":"fake-openai-endpoint", + "messages": [{"role": "user","content": "How many r in the word strawberry?"}], + "litellm_request_debug": true +}' +``` + +This will emit the raw request sent by LiteLLM to the API Provider and raw response received from the API Provider for **just** this request in the logs. + + +```bash showLineNumbers +INFO: Uvicorn running on http://0.0.0.0:4000 (Press CTRL+C to quit) +20:14:06 - LiteLLM:WARNING: litellm_logging.py:938 - + +POST Request Sent from LiteLLM: +curl -X POST \ +https://exampleopenaiendpoint-production.up.railway.app/chat/completions \ +-H 'Authorization: Be****ey' -H 'Content-Type: application/json' \ +-d '{'model': 'fake', 'messages': [{'role': 'user', 'content': 'How many r in the word strawberry?'}], 'stream': False}' + + +20:14:06 - LiteLLM:WARNING: litellm_logging.py:1015 - RAW RESPONSE: +{"id":"chatcmpl-817fc08f0d6c451485d571dab39b26a1","object":"chat.completion","created":1677652288,"model":"gpt-3.5-turbo-0301","system_fingerprint":"fp_44709d6fcb","choices":[{"index":0,"message":{"role":"assistant","content":"\n\nHello there, how may I assist you today?"},"logprobs":null,"finish_reason":"stop"}],"usage":{"prompt_tokens":9,"completion_tokens":12,"total_tokens":21}} + + +INFO: 127.0.0.1:56155 - "POST /chat/completions HTTP/1.1" 200 OK + +``` + + ## JSON LOGS Set `JSON_LOGS="True"` in your env: -```bash +```bash showLineNumbers export JSON_LOGS="True" ``` **OR** Set `json_logs: true` in your yaml: -```yaml +```yaml showLineNumbers litellm_settings: json_logs: true ``` Start proxy -```bash +```bash showLineNumbers $ litellm ``` @@ -80,7 +118,7 @@ The proxy will now all logs in json format. Turn off fastapi's default 'INFO' logs 1. Turn on 'json logs' -```yaml +```yaml showLineNumbers litellm_settings: json_logs: true ``` @@ -89,20 +127,20 @@ litellm_settings: Only get logs if an error occurs. -```bash +```bash showLineNumbers LITELLM_LOG="ERROR" ``` 3. Start proxy -```bash +```bash showLineNumbers $ litellm ``` Expected Output: -```bash +```bash showLineNumbers # no info statements ``` @@ -119,14 +157,14 @@ This can be caused due to all your models hitting rate limit errors, causing the How to control this? - Adjust the cooldown time -```yaml +```yaml showLineNumbers router_settings: cooldown_time: 0 # 👈 KEY CHANGE ``` - Disable Cooldowns [NOT RECOMMENDED] -```yaml +```yaml showLineNumbers router_settings: disable_cooldowns: True ``` From f374103c46ad2c4d8f24b427ed956b8f37cc6935 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 11 Sep 2025 20:20:52 -0700 Subject: [PATCH 53/73] UI new build --- .../_buildManifest.js | 0 .../_ssgManifest.js | 0 .../out/_next/static/chunks/162-4e7640b4d68e1ae4.js | 1 + .../out/_next/static/chunks/162-714ca0ed10a07f66.js | 1 - .../out/_next/static/chunks/220-5061c4cea850d728.js | 8 ++++---- .../out/_next/static/chunks/866-9e1803a09e9ae8da.js | 2 +- .../out/_next/static/chunks/app/page-127adcf8da2b5294.js | 1 + .../out/_next/static/chunks/app/page-8dc8d9524a1f3965.js | 1 - .../out/_next/static/css/060d5ddee53e45ce.css | 3 --- .../out/_next/static/css/c528590c6415a94c.css | 3 +++ litellm/proxy/_experimental/out/index.html | 2 +- litellm/proxy/_experimental/out/index.txt | 4 ++-- litellm/proxy/_experimental/out/model_hub.txt | 4 ++-- .../{model_hub_table/index.html => model_hub_table.html} | 2 +- litellm/proxy/_experimental/out/model_hub_table.txt | 4 ++-- litellm/proxy/_experimental/out/onboarding.html | 1 + litellm/proxy/_experimental/out/onboarding.txt | 2 +- ui/litellm-dashboard/out/404.html | 2 +- .../_buildManifest.js | 0 .../_ssgManifest.js | 0 .../out/_next/static/chunks/162-4e7640b4d68e1ae4.js | 1 + .../out/_next/static/chunks/162-714ca0ed10a07f66.js | 1 - .../out/_next/static/chunks/220-5061c4cea850d728.js | 8 ++++---- .../out/_next/static/chunks/866-9e1803a09e9ae8da.js | 2 +- .../out/_next/static/chunks/app/page-127adcf8da2b5294.js | 1 + .../out/_next/static/chunks/app/page-8dc8d9524a1f3965.js | 1 - .../out/_next/static/css/060d5ddee53e45ce.css | 3 --- .../out/_next/static/css/c528590c6415a94c.css | 3 +++ ui/litellm-dashboard/out/index.html | 2 +- ui/litellm-dashboard/out/index.txt | 4 ++-- ui/litellm-dashboard/out/model_hub.html | 2 +- ui/litellm-dashboard/out/model_hub.txt | 4 ++-- ui/litellm-dashboard/out/model_hub_table.html | 2 +- ui/litellm-dashboard/out/model_hub_table.txt | 4 ++-- ui/litellm-dashboard/out/onboarding.html | 2 +- ui/litellm-dashboard/out/onboarding.txt | 2 +- 36 files changed, 42 insertions(+), 41 deletions(-) rename litellm/proxy/_experimental/out/_next/static/{0GF-OyXnYlAPMWfyPAZSs => FMlZjJYLUentCU02Wj6R_}/_buildManifest.js (100%) rename litellm/proxy/_experimental/out/_next/static/{0GF-OyXnYlAPMWfyPAZSs => FMlZjJYLUentCU02Wj6R_}/_ssgManifest.js (100%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/162-4e7640b4d68e1ae4.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/162-714ca0ed10a07f66.js rename ui/litellm-dashboard/out/_next/static/chunks/220-1c8d82f7ce7658c4.js => litellm/proxy/_experimental/out/_next/static/chunks/220-5061c4cea850d728.js (71%) rename ui/litellm-dashboard/out/_next/static/chunks/866-3523e0e07cf314f6.js => litellm/proxy/_experimental/out/_next/static/chunks/866-9e1803a09e9ae8da.js (99%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/page-127adcf8da2b5294.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/page-8dc8d9524a1f3965.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/css/060d5ddee53e45ce.css create mode 100644 litellm/proxy/_experimental/out/_next/static/css/c528590c6415a94c.css rename litellm/proxy/_experimental/out/{model_hub_table/index.html => model_hub_table.html} (92%) create mode 100644 litellm/proxy/_experimental/out/onboarding.html rename ui/litellm-dashboard/out/_next/static/{0GF-OyXnYlAPMWfyPAZSs => FMlZjJYLUentCU02Wj6R_}/_buildManifest.js (100%) rename ui/litellm-dashboard/out/_next/static/{0GF-OyXnYlAPMWfyPAZSs => FMlZjJYLUentCU02Wj6R_}/_ssgManifest.js (100%) create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/162-4e7640b4d68e1ae4.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/162-714ca0ed10a07f66.js rename litellm/proxy/_experimental/out/_next/static/chunks/220-1c8d82f7ce7658c4.js => ui/litellm-dashboard/out/_next/static/chunks/220-5061c4cea850d728.js (71%) rename litellm/proxy/_experimental/out/_next/static/chunks/866-3523e0e07cf314f6.js => ui/litellm-dashboard/out/_next/static/chunks/866-9e1803a09e9ae8da.js (99%) create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/app/page-127adcf8da2b5294.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/app/page-8dc8d9524a1f3965.js delete mode 100644 ui/litellm-dashboard/out/_next/static/css/060d5ddee53e45ce.css create mode 100644 ui/litellm-dashboard/out/_next/static/css/c528590c6415a94c.css diff --git a/litellm/proxy/_experimental/out/_next/static/0GF-OyXnYlAPMWfyPAZSs/_buildManifest.js b/litellm/proxy/_experimental/out/_next/static/FMlZjJYLUentCU02Wj6R_/_buildManifest.js similarity index 100% rename from litellm/proxy/_experimental/out/_next/static/0GF-OyXnYlAPMWfyPAZSs/_buildManifest.js rename to litellm/proxy/_experimental/out/_next/static/FMlZjJYLUentCU02Wj6R_/_buildManifest.js diff --git a/litellm/proxy/_experimental/out/_next/static/0GF-OyXnYlAPMWfyPAZSs/_ssgManifest.js b/litellm/proxy/_experimental/out/_next/static/FMlZjJYLUentCU02Wj6R_/_ssgManifest.js similarity index 100% rename from litellm/proxy/_experimental/out/_next/static/0GF-OyXnYlAPMWfyPAZSs/_ssgManifest.js rename to litellm/proxy/_experimental/out/_next/static/FMlZjJYLUentCU02Wj6R_/_ssgManifest.js diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/162-4e7640b4d68e1ae4.js b/litellm/proxy/_experimental/out/_next/static/chunks/162-4e7640b4d68e1ae4.js new file mode 100644 index 00000000000..e9bbcb3d1f6 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/162-4e7640b4d68e1ae4.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[162],{36724:function(e,t,n){n.d(t,{Dx:function(){return i.Z},Zb:function(){return s.Z},xv:function(){return r.Z},zx:function(){return a.Z}});var a=n(20831),s=n(12514),r=n(84264),i=n(96761)},19130:function(e,t,n){n.d(t,{RM:function(){return s.Z},SC:function(){return l.Z},iA:function(){return a.Z},pj:function(){return r.Z},ss:function(){return i.Z},xs:function(){return o.Z}});var a=n(21626),s=n(97214),r=n(28241),i=n(58834),o=n(69552),l=n(71876)},88658:function(e,t,n){n.d(t,{L:function(){return s}});var a=n(49817);let s=e=>{let t;let{apiKeySource:n,accessToken:s,apiKey:r,inputMessage:i,chatHistory:o,selectedTags:l,selectedVectorStores:c,selectedGuardrails:d,selectedMCPTools:m,endpointType:p,selectedModel:u,selectedSdk:g}=e,x="session"===n?s:r,h=window.location.origin,f=i||"Your prompt here",_=f.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n"),b=o.filter(e=>!e.isImage).map(e=>{let{role:t,content:n}=e;return{role:t,content:n}}),v={};l.length>0&&(v.tags=l),c.length>0&&(v.vector_stores=c),d.length>0&&(v.guardrails=d);let j=u||"your-model-name",y="azure"===g?'import openai\n\nclient = openai.AzureOpenAI(\n api_key="'.concat(x||"YOUR_LITELLM_API_KEY",'",\n azure_endpoint="').concat(h,'",\n api_version="2024-02-01"\n)'):'import openai\n\nclient = openai.OpenAI(\n api_key="'.concat(x||"YOUR_LITELLM_API_KEY",'",\n base_url="').concat(h,'"\n)');switch(p){case a.KP.CHAT:{let e=Object.keys(v).length>0,n="";if(e){let e=JSON.stringify({metadata:v},null,2).split("\n").map(e=>" ".repeat(4)+e).join("\n").trim();n=",\n extra_body=".concat(e)}let a=b.length>0?b:[{role:"user",content:f}];t='\nimport base64\n\n# Helper function to encode images to base64\ndef encode_image(image_path):\n with open(image_path, "rb") as image_file:\n return base64.b64encode(image_file.read()).decode(\'utf-8\')\n\n# Example with text only\nresponse = client.chat.completions.create(\n model="'.concat(j,'",\n messages=').concat(JSON.stringify(a,null,4)).concat(n,'\n)\n\nprint(response)\n\n# Example with image or PDF (uncomment and provide file path to use)\n# base64_file = encode_image("path/to/your/file.jpg") # or .pdf\n# response_with_file = client.chat.completions.create(\n# model="').concat(j,'",\n# messages=[\n# {\n# "role": "user",\n# "content": [\n# {\n# "type": "text",\n# "text": "').concat(_,'"\n# },\n# {\n# "type": "image_url",\n# "image_url": {\n# "url": f"data:image/jpeg;base64,{base64_file}" # or data:application/pdf;base64,{base64_file}\n# }\n# }\n# ]\n# }\n# ]').concat(n,"\n# )\n# print(response_with_file)\n");break}case a.KP.RESPONSES:{let e=Object.keys(v).length>0,n="";if(e){let e=JSON.stringify({metadata:v},null,2).split("\n").map(e=>" ".repeat(4)+e).join("\n").trim();n=",\n extra_body=".concat(e)}let a=b.length>0?b:[{role:"user",content:f}];t='\nimport base64\n\n# Helper function to encode images to base64\ndef encode_image(image_path):\n with open(image_path, "rb") as image_file:\n return base64.b64encode(image_file.read()).decode(\'utf-8\')\n\n# Example with text only\nresponse = client.responses.create(\n model="'.concat(j,'",\n input=').concat(JSON.stringify(a,null,4)).concat(n,'\n)\n\nprint(response.output_text)\n\n# Example with image or PDF (uncomment and provide file path to use)\n# base64_file = encode_image("path/to/your/file.jpg") # or .pdf\n# response_with_file = client.responses.create(\n# model="').concat(j,'",\n# input=[\n# {\n# "role": "user",\n# "content": [\n# {"type": "input_text", "text": "').concat(_,'"},\n# {\n# "type": "input_image",\n# "image_url": f"data:image/jpeg;base64,{base64_file}", # or data:application/pdf;base64,{base64_file}\n# },\n# ],\n# }\n# ]').concat(n,"\n# )\n# print(response_with_file.output_text)\n");break}case a.KP.IMAGE:t="azure"===g?"\n# NOTE: The Azure SDK does not have a direct equivalent to the multi-modal 'responses.create' method shown for OpenAI.\n# This snippet uses 'client.images.generate' and will create a new image based on your prompt.\n# It does not use the uploaded image, as 'client.images.generate' does not support image inputs in this context.\nimport os\nimport requests\nimport json\nimport time\nfrom PIL import Image\n\nresult = client.images.generate(\n model=\"".concat(j,'",\n prompt="').concat(i,'",\n n=1\n)\n\njson_response = json.loads(result.model_dump_json())\n\n# Set the directory for the stored image\nimage_dir = os.path.join(os.curdir, \'images\')\n\n# If the directory doesn\'t exist, create it\nif not os.path.isdir(image_dir):\n os.mkdir(image_dir)\n\n# Initialize the image path\nimage_filename = f"generated_image_{int(time.time())}.png"\nimage_path = os.path.join(image_dir, image_filename)\n\ntry:\n # Retrieve the generated image\n if json_response.get("data") && len(json_response["data"]) > 0 && json_response["data"][0].get("url"):\n image_url = json_response["data"][0]["url"]\n generated_image = requests.get(image_url).content\n with open(image_path, "wb") as image_file:\n image_file.write(generated_image)\n\n print(f"Image saved to {image_path}")\n # Display the image\n image = Image.open(image_path)\n image.show()\n else:\n print("Could not find image URL in response.")\n print("Full response:", json_response)\nexcept Exception as e:\n print(f"An error occurred: {e}")\n print("Full response:", json_response)\n'):"\nimport base64\nimport os\nimport time\nimport json\nfrom PIL import Image\nimport requests\n\n# Helper function to encode images to base64\ndef encode_image(image_path):\n with open(image_path, \"rb\") as image_file:\n return base64.b64encode(image_file.read()).decode('utf-8')\n\n# Helper function to create a file (simplified for this example)\ndef create_file(image_path):\n # In a real implementation, this would upload the file to OpenAI\n # For this example, we'll just return a placeholder ID\n return f\"file_{os.path.basename(image_path).replace('.', '_')}\"\n\n# The prompt entered by the user\nprompt = \"".concat(_,'"\n\n# Encode images to base64\nbase64_image1 = encode_image("body-lotion.png")\nbase64_image2 = encode_image("soap.png")\n\n# Create file IDs\nfile_id1 = create_file("body-lotion.png")\nfile_id2 = create_file("incense-kit.png")\n\nresponse = client.responses.create(\n model="').concat(j,'",\n input=[\n {\n "role": "user",\n "content": [\n {"type": "input_text", "text": prompt},\n {\n "type": "input_image",\n "image_url": f"data:image/jpeg;base64,{base64_image1}",\n },\n {\n "type": "input_image",\n "image_url": f"data:image/jpeg;base64,{base64_image2}",\n },\n {\n "type": "input_image",\n "file_id": file_id1,\n },\n {\n "type": "input_image",\n "file_id": file_id2,\n }\n ],\n }\n ],\n tools=[{"type": "image_generation"}],\n)\n\n# Process the response\nimage_generation_calls = [\n output\n for output in response.output\n if output.type == "image_generation_call"\n]\n\nimage_data = [output.result for output in image_generation_calls]\n\nif image_data:\n image_base64 = image_data[0]\n image_filename = f"edited_image_{int(time.time())}.png"\n with open(image_filename, "wb") as f:\n f.write(base64.b64decode(image_base64))\n print(f"Image saved to {image_filename}")\nelse:\n # If no image is generated, there might be a text response with an explanation\n text_response = [output.text for output in response.output if hasattr(output, \'text\')]\n if text_response:\n print("No image generated. Model response:")\n print("\\n".join(text_response))\n else:\n print("No image data found in response.")\n print("Full response for debugging:")\n print(response)\n');break;case a.KP.IMAGE_EDITS:t="azure"===g?'\nimport base64\nimport os\nimport time\nimport json\nfrom PIL import Image\nimport requests\n\n# Helper function to encode images to base64\ndef encode_image(image_path):\n with open(image_path, "rb") as image_file:\n return base64.b64encode(image_file.read()).decode(\'utf-8\')\n\n# The prompt entered by the user\nprompt = "'.concat(_,'"\n\n# Encode images to base64\nbase64_image1 = encode_image("body-lotion.png")\nbase64_image2 = encode_image("soap.png")\n\n# Create file IDs\nfile_id1 = create_file("body-lotion.png")\nfile_id2 = create_file("incense-kit.png")\n\nresponse = client.responses.create(\n model="').concat(j,'",\n input=[\n {\n "role": "user",\n "content": [\n {"type": "input_text", "text": prompt},\n {\n "type": "input_image",\n "image_url": f"data:image/jpeg;base64,{base64_image1}",\n },\n {\n "type": "input_image",\n "image_url": f"data:image/jpeg;base64,{base64_image2}",\n },\n {\n "type": "input_image",\n "file_id": file_id1,\n },\n {\n "type": "input_image",\n "file_id": file_id2,\n }\n ],\n }\n ],\n tools=[{"type": "image_generation"}],\n)\n\n# Process the response\nimage_generation_calls = [\n output\n for output in response.output\n if output.type == "image_generation_call"\n]\n\nimage_data = [output.result for output in image_generation_calls]\n\nif image_data:\n image_base64 = image_data[0]\n image_filename = f"edited_image_{int(time.time())}.png"\n with open(image_filename, "wb") as f:\n f.write(base64.b64decode(image_base64))\n print(f"Image saved to {image_filename}")\nelse:\n # If no image is generated, there might be a text response with an explanation\n text_response = [output.text for output in response.output if hasattr(output, \'text\')]\n if text_response:\n print("No image generated. Model response:")\n print("\\n".join(text_response))\n else:\n print("No image data found in response.")\n print("Full response for debugging:")\n print(response)\n'):"\nimport base64\nimport os\nimport time\n\n# Helper function to encode images to base64\ndef encode_image(image_path):\n with open(image_path, \"rb\") as image_file:\n return base64.b64encode(image_file.read()).decode('utf-8')\n\n# Helper function to create a file (simplified for this example)\ndef create_file(image_path):\n # In a real implementation, this would upload the file to OpenAI\n # For this example, we'll just return a placeholder ID\n return f\"file_{os.path.basename(image_path).replace('.', '_')}\"\n\n# The prompt entered by the user\nprompt = \"".concat(_,'"\n\n# Encode images to base64\nbase64_image1 = encode_image("body-lotion.png")\nbase64_image2 = encode_image("soap.png")\n\n# Create file IDs\nfile_id1 = create_file("body-lotion.png")\nfile_id2 = create_file("incense-kit.png")\n\nresponse = client.responses.create(\n model="').concat(j,'",\n input=[\n {\n "role": "user",\n "content": [\n {"type": "input_text", "text": prompt},\n {\n "type": "input_image",\n "image_url": f"data:image/jpeg;base64,{base64_image1}",\n },\n {\n "type": "input_image",\n "image_url": f"data:image/jpeg;base64,{base64_image2}",\n },\n {\n "type": "input_image",\n "file_id": file_id1,\n },\n {\n "type": "input_image",\n "file_id": file_id2,\n }\n ],\n }\n ],\n tools=[{"type": "image_generation"}],\n)\n\n# Process the response\nimage_generation_calls = [\n output\n for output in response.output\n if output.type == "image_generation_call"\n]\n\nimage_data = [output.result for output in image_generation_calls]\n\nif image_data:\n image_base64 = image_data[0]\n image_filename = f"edited_image_{int(time.time())}.png"\n with open(image_filename, "wb") as f:\n f.write(base64.b64decode(image_base64))\n print(f"Image saved to {image_filename}")\nelse:\n # If no image is generated, there might be a text response with an explanation\n text_response = [output.text for output in response.output if hasattr(output, \'text\')]\n if text_response:\n print("No image generated. Model response:")\n print("\\n".join(text_response))\n else:\n print("No image data found in response.")\n print("Full response for debugging:")\n print(response)\n');break;default:t="\n# Code generation for this endpoint is not implemented yet."}return"".concat(y,"\n").concat(t)}},49817:function(e,t,n){var a,s,r,i;n.d(t,{KP:function(){return s},vf:function(){return l}}),(r=a||(a={})).IMAGE_GENERATION="image_generation",r.CHAT="chat",r.RESPONSES="responses",r.IMAGE_EDITS="image_edits",r.ANTHROPIC_MESSAGES="anthropic_messages",(i=s||(s={})).IMAGE="image",i.CHAT="chat",i.RESPONSES="responses",i.IMAGE_EDITS="image_edits",i.ANTHROPIC_MESSAGES="anthropic_messages";let o={image_generation:"image",chat:"chat",responses:"responses",image_edits:"image_edits",anthropic_messages:"anthropic_messages"},l=e=>{if(console.log("getEndpointType:",e),Object.values(a).includes(e)){let t=o[e];return console.log("endpointType:",t),t}return"chat"}},29488:function(e,t,n){n.d(t,{Hc:function(){return i},Ui:function(){return r},e4:function(){return o},xd:function(){return l}});let a="litellm_mcp_auth_tokens",s=()=>{try{let e=localStorage.getItem(a);return e?JSON.parse(e):{}}catch(e){return console.error("Error reading MCP auth tokens from localStorage:",e),{}}},r=(e,t)=>{try{let n=s()[e];if(n&&n.serverAlias===t||n&&!t&&!n.serverAlias)return n.authValue;return null}catch(e){return console.error("Error getting MCP auth token:",e),null}},i=(e,t,n,r)=>{try{let i=s();i[e]={serverId:e,serverAlias:r,authValue:t,authType:n,timestamp:Date.now()},localStorage.setItem(a,JSON.stringify(i))}catch(e){console.error("Error storing MCP auth token:",e)}},o=e=>{try{let t=s();delete t[e],localStorage.setItem(a,JSON.stringify(t))}catch(e){console.error("Error removing MCP auth token:",e)}},l=()=>{try{localStorage.removeItem(a)}catch(e){console.error("Error clearing MCP auth tokens:",e)}}},8048:function(e,t,n){n.d(t,{C:function(){return m}});var a=n(57437),s=n(71594),r=n(24525),i=n(2265),o=n(19130),l=n(44633),c=n(86462),d=n(49084);function m(e){let{data:t=[],columns:n,isLoading:m=!1,table:p,defaultSorting:u=[]}=e,[g,x]=i.useState(u),[h]=i.useState("onChange"),[f,_]=i.useState({}),[b,v]=i.useState({}),j=(0,s.b7)({data:t,columns:n,state:{sorting:g,columnSizing:f,columnVisibility:b},columnResizeMode:h,onSortingChange:x,onColumnSizingChange:_,onColumnVisibilityChange:v,getCoreRowModel:(0,r.sC)(),getSortedRowModel:(0,r.tj)(),enableSorting:!0,enableColumnResizing:!0,defaultColumn:{minSize:40,maxSize:500}});return i.useEffect(()=>{p&&(p.current=j)},[j,p]),(0,a.jsx)("div",{className:"rounded-lg custom-border relative",children:(0,a.jsx)("div",{className:"overflow-x-auto",children:(0,a.jsx)("div",{className:"relative min-w-full",children:(0,a.jsxs)(o.iA,{className:"[&_td]:py-2 [&_th]:py-2 w-full",children:[(0,a.jsx)(o.ss,{children:j.getHeaderGroups().map(e=>(0,a.jsx)(o.SC,{children:e.headers.map(e=>{var t;return(0,a.jsxs)(o.xs,{className:"py-1 h-8 relative ".concat("actions"===e.id?"sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)] z-20 w-[120px] ml-8":""," ").concat((null===(t=e.column.columnDef.meta)||void 0===t?void 0:t.className)||""),style:{width:"actions"===e.id?120:e.getSize(),position:"actions"===e.id?"sticky":"relative",right:"actions"===e.id?0:"auto"},onClick:e.column.getCanSort()?e.column.getToggleSortingHandler():void 0,children:[(0,a.jsxs)("div",{className:"flex items-center justify-between gap-2",children:[(0,a.jsx)("div",{className:"flex items-center",children:e.isPlaceholder?null:(0,s.ie)(e.column.columnDef.header,e.getContext())}),"actions"!==e.id&&e.column.getCanSort()&&(0,a.jsx)("div",{className:"w-4",children:e.column.getIsSorted()?({asc:(0,a.jsx)(l.Z,{className:"h-4 w-4 text-blue-500"}),desc:(0,a.jsx)(c.Z,{className:"h-4 w-4 text-blue-500"})})[e.column.getIsSorted()]:(0,a.jsx)(d.Z,{className:"h-4 w-4 text-gray-400"})})]}),e.column.getCanResize()&&(0,a.jsx)("div",{onMouseDown:e.getResizeHandler(),onTouchStart:e.getResizeHandler(),className:"absolute right-0 top-0 h-full w-2 cursor-col-resize select-none touch-none ".concat(e.column.getIsResizing()?"bg-blue-500":"hover:bg-blue-200")})]},e.id)})},e.id))}),(0,a.jsx)(o.RM,{children:m?(0,a.jsx)(o.SC,{children:(0,a.jsx)(o.pj,{colSpan:n.length,className:"h-8 text-center",children:(0,a.jsx)("div",{className:"text-center text-gray-500",children:(0,a.jsx)("p",{children:"\uD83D\uDE85 Loading models..."})})})}):j.getRowModel().rows.length>0?j.getRowModel().rows.map(e=>(0,a.jsx)(o.SC,{children:e.getVisibleCells().map(e=>{var t;return(0,a.jsx)(o.pj,{className:"py-0.5 ".concat("actions"===e.column.id?"sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)] z-20 w-[120px] ml-8":""," ").concat((null===(t=e.column.columnDef.meta)||void 0===t?void 0:t.className)||""),style:{width:"actions"===e.column.id?120:e.column.getSize(),position:"actions"===e.column.id?"sticky":"relative",right:"actions"===e.column.id?0:"auto"},children:(0,s.ie)(e.column.columnDef.cell,e.getContext())},e.id)})},e.id)):(0,a.jsx)(o.SC,{children:(0,a.jsx)(o.pj,{colSpan:n.length,className:"h-8 text-center",children:(0,a.jsx)("div",{className:"text-center text-gray-500",children:(0,a.jsx)("p",{children:"No models found"})})})})})]})})})})}},65373:function(e,t,n){n.d(t,{Z:function(){return v}});var a=n(57437),s=n(27648),r=n(2265),i=n(89970),o=n(80795),l=n(19250),c=n(15883),d=n(46346),m=n(57400),p=n(91870),u=n(40428),g=n(83884),x=n(45524),h=n(3914);let f=async e=>{if(!e)return null;try{return await (0,l.getProxyUISettings)(e)}catch(e){return console.error("Error fetching proxy settings:",e),null}};var _=n(69734),b=n(29488),v=e=>{let{userID:t,userEmail:n,userRole:v,premiumUser:j,proxySettings:y,setProxySettings:N,accessToken:w,isPublicPage:A=!1,sidebarCollapsed:I=!1,onToggleSidebar:S}=e,k=(0,l.getProxyBaseUrl)(),[C,E]=(0,r.useState)(""),{logoUrl:M}=(0,_.F)();(0,r.useEffect)(()=>{(async()=>{if(w){let e=await f(w);console.log("response from fetchProxySettings",e),e&&N(e)}})()},[w]),(0,r.useEffect)(()=>{E((null==y?void 0:y.PROXY_LOGOUT_URL)||"")},[y]);let O=[{key:"user-info",label:(0,a.jsxs)("div",{className:"px-3 py-3 border-b border-gray-100",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between mb-3",children:[(0,a.jsxs)("div",{className:"flex items-center",children:[(0,a.jsx)(c.Z,{className:"mr-2 text-gray-700"}),(0,a.jsx)("span",{className:"text-sm font-semibold text-gray-900",children:t})]}),j?(0,a.jsx)(i.Z,{title:"Premium User",placement:"left",children:(0,a.jsxs)("div",{className:"flex items-center bg-gradient-to-r from-amber-500 to-yellow-500 text-white px-2 py-0.5 rounded-full cursor-help",children:[(0,a.jsx)(d.Z,{className:"mr-1 text-xs"}),(0,a.jsx)("span",{className:"text-xs font-medium",children:"Premium"})]})}):(0,a.jsx)(i.Z,{title:"Upgrade to Premium for advanced features",placement:"left",children:(0,a.jsxs)("div",{className:"flex items-center bg-gray-100 text-gray-500 px-2 py-0.5 rounded-full cursor-help",children:[(0,a.jsx)(d.Z,{className:"mr-1 text-xs"}),(0,a.jsx)("span",{className:"text-xs font-medium",children:"Standard"})]})})]}),(0,a.jsxs)("div",{className:"space-y-2",children:[(0,a.jsxs)("div",{className:"flex items-center text-sm",children:[(0,a.jsx)(m.Z,{className:"mr-2 text-gray-400 text-xs"}),(0,a.jsx)("span",{className:"text-gray-500 text-xs",children:"Role"}),(0,a.jsx)("span",{className:"ml-auto text-gray-700 font-medium",children:v})]}),(0,a.jsxs)("div",{className:"flex items-center text-sm",children:[(0,a.jsx)(p.Z,{className:"mr-2 text-gray-400 text-xs"}),(0,a.jsx)("span",{className:"text-gray-500 text-xs",children:"Email"}),(0,a.jsx)("span",{className:"ml-auto text-gray-700 font-medium truncate max-w-[150px]",title:n||"Unknown",children:n||"Unknown"})]})]})]})},{key:"logout",label:(0,a.jsxs)("div",{className:"flex items-center py-2 px-3 hover:bg-gray-50 rounded-md mx-1 my-1",onClick:()=>{(0,h.b)(),(0,b.xd)(),window.location.href=C},children:[(0,a.jsx)(u.Z,{className:"mr-3 text-gray-600"}),(0,a.jsx)("span",{className:"text-gray-800",children:"Logout"})]})}];return(0,a.jsx)("nav",{className:"bg-white border-b border-gray-200 sticky top-0 z-10",children:(0,a.jsx)("div",{className:"w-full",children:(0,a.jsxs)("div",{className:"flex items-center h-14 px-4",children:[" ",(0,a.jsxs)("div",{className:"flex items-center flex-shrink-0",children:[S&&(0,a.jsx)("button",{onClick:S,className:"flex items-center justify-center w-10 h-10 mr-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded transition-colors",title:I?"Expand sidebar":"Collapse sidebar",children:(0,a.jsx)("span",{className:"text-lg",children:I?(0,a.jsx)(g.Z,{}):(0,a.jsx)(x.Z,{})})}),(0,a.jsx)(s.default,{href:"/",className:"flex items-center",children:(0,a.jsx)("img",{src:M||"".concat(k,"/get_image"),alt:"LiteLLM Brand",className:"h-10 w-auto"})})]}),(0,a.jsxs)("div",{className:"flex items-center space-x-5 ml-auto",children:[(0,a.jsx)("a",{href:"https://docs.litellm.ai/docs/",target:"_blank",rel:"noopener noreferrer",className:"text-sm text-gray-600 hover:text-gray-900 transition-colors",children:"Docs"}),!A&&(0,a.jsx)(o.Z,{menu:{items:O,className:"min-w-[200px]",style:{padding:"8px",marginTop:"8px",borderRadius:"12px",boxShadow:"0 4px 24px rgba(0, 0, 0, 0.08)"}},overlayStyle:{minWidth:"200px"},children:(0,a.jsxs)("button",{className:"inline-flex items-center text-sm text-gray-600 hover:text-gray-900 transition-colors",children:["User",(0,a.jsx)("svg",{className:"ml-1 w-5 h-5 text-gray-500",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,a.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:1.5,d:"M19 9l-7 7-7-7"})})]})})]})]})})})}},42673:function(e,t,n){var a,s;n.d(t,{Cl:function(){return a},bK:function(){return d},cd:function(){return o},dr:function(){return l},fK:function(){return r},ph:function(){return c}}),n(2265),(s=a||(a={})).AIML="AI/ML API",s.Bedrock="Amazon Bedrock",s.Anthropic="Anthropic",s.AssemblyAI="AssemblyAI",s.SageMaker="AWS SageMaker",s.Azure="Azure",s.Azure_AI_Studio="Azure AI Foundry (Studio)",s.Cerebras="Cerebras",s.Cohere="Cohere",s.Dashscope="Dashscope",s.Databricks="Databricks (Qwen API)",s.DeepInfra="DeepInfra",s.Deepgram="Deepgram",s.Deepseek="Deepseek",s.ElevenLabs="ElevenLabs",s.FireworksAI="Fireworks AI",s.Google_AI_Studio="Google AI Studio",s.GradientAI="GradientAI",s.Groq="Groq",s.Hosted_Vllm="vllm",s.JinaAI="Jina AI",s.MistralAI="Mistral AI",s.Ollama="Ollama",s.OpenAI="OpenAI",s.OpenAI_Compatible="OpenAI-Compatible Endpoints (Together AI, etc.)",s.OpenAI_Text="OpenAI Text Completion",s.OpenAI_Text_Compatible="OpenAI-Compatible Text Completion Models (Together AI, etc.)",s.Openrouter="Openrouter",s.Perplexity="Perplexity",s.Sambanova="Sambanova",s.TogetherAI="TogetherAI",s.Triton="Triton",s.Vertex_AI="Vertex AI (Anthropic, Gemini, etc.)",s.VolcEngine="VolcEngine",s.Voyage="Voyage AI",s.xAI="xAI";let r={AIML:"aiml",OpenAI:"openai",OpenAI_Text:"text-completion-openai",Azure:"azure",Azure_AI_Studio:"azure_ai",Anthropic:"anthropic",Google_AI_Studio:"gemini",Bedrock:"bedrock",Groq:"groq",MistralAI:"mistral",Cohere:"cohere",OpenAI_Compatible:"openai",OpenAI_Text_Compatible:"text-completion-openai",Vertex_AI:"vertex_ai",Databricks:"databricks",Dashscope:"dashscope",xAI:"xai",Deepseek:"deepseek",Ollama:"ollama",AssemblyAI:"assemblyai",Cerebras:"cerebras",Sambanova:"sambanova",Perplexity:"perplexity",TogetherAI:"together_ai",Openrouter:"openrouter",FireworksAI:"fireworks_ai",GradientAI:"gradient_ai",Triton:"triton",Deepgram:"deepgram",ElevenLabs:"elevenlabs",SageMaker:"sagemaker_chat",Voyage:"voyage",JinaAI:"jina_ai",VolcEngine:"volcengine",DeepInfra:"deepinfra",Hosted_Vllm:"hosted_vllm"},i="/ui/assets/logos/",o={"AI/ML API":"".concat(i,"aiml_api.svg"),Anthropic:"".concat(i,"anthropic.svg"),AssemblyAI:"".concat(i,"assemblyai_small.png"),Azure:"".concat(i,"microsoft_azure.svg"),"Azure AI Foundry (Studio)":"".concat(i,"microsoft_azure.svg"),"Amazon Bedrock":"".concat(i,"bedrock.svg"),"AWS SageMaker":"".concat(i,"bedrock.svg"),Cerebras:"".concat(i,"cerebras.svg"),Cohere:"".concat(i,"cohere.svg"),"Databricks (Qwen API)":"".concat(i,"databricks.svg"),Dashscope:"".concat(i,"dashscope.svg"),Deepseek:"".concat(i,"deepseek.svg"),"Fireworks AI":"".concat(i,"fireworks.svg"),Groq:"".concat(i,"groq.svg"),"Google AI Studio":"".concat(i,"google.svg"),vllm:"".concat(i,"vllm.png"),"Mistral AI":"".concat(i,"mistral.svg"),Ollama:"".concat(i,"ollama.svg"),OpenAI:"".concat(i,"openai_small.svg"),"OpenAI Text Completion":"".concat(i,"openai_small.svg"),"OpenAI-Compatible Text Completion Models (Together AI, etc.)":"".concat(i,"openai_small.svg"),"OpenAI-Compatible Endpoints (Together AI, etc.)":"".concat(i,"openai_small.svg"),Openrouter:"".concat(i,"openrouter.svg"),Perplexity:"".concat(i,"perplexity-ai.svg"),Sambanova:"".concat(i,"sambanova.svg"),TogetherAI:"".concat(i,"togetherai.svg"),"Vertex AI (Anthropic, Gemini, etc.)":"".concat(i,"google.svg"),xAI:"".concat(i,"xai.svg"),GradientAI:"".concat(i,"gradientai.svg"),Triton:"".concat(i,"nvidia_triton.png"),Deepgram:"".concat(i,"deepgram.png"),ElevenLabs:"".concat(i,"elevenlabs.png"),"Voyage AI":"".concat(i,"voyage.webp"),"Jina AI":"".concat(i,"jina.png"),VolcEngine:"".concat(i,"volcengine.png"),DeepInfra:"".concat(i,"deepinfra.png")},l=e=>{if(!e)return{logo:"",displayName:"-"};if("gemini"===e.toLowerCase()){let e="Google AI Studio";return{logo:o[e],displayName:e}}let t=Object.keys(r).find(t=>r[t].toLowerCase()===e.toLowerCase());if(!t)return{logo:"",displayName:e};let n=a[t];return{logo:o[n],displayName:n}},c=e=>{if("AI/ML API"===e)return"aiml/flux-pro/v1.1";if("Vertex AI (Anthropic, Gemini, etc.)"===e)return"gemini-pro";if("Anthropic"==e||"Amazon Bedrock"==e)return"claude-3-opus";if("AWS SageMaker"==e)return"sagemaker/jumpstart-dft-meta-textgeneration-llama-2-7b";if("Google AI Studio"==e)return"gemini-pro";if("Azure AI Foundry (Studio)"==e)return"azure_ai/command-r-plus";else if("Azure"==e)return"azure/my-deployment";else if("Voyage AI"==e)return"voyage/";else if("Jina AI"==e)return"jina_ai/";else if("VolcEngine"==e)return"volcengine/";else if("DeepInfra"==e)return"deepinfra/";else return"gpt-3.5-turbo"},d=(e,t)=>{console.log("Provider key: ".concat(e));let n=r[e];console.log("Provider mapped to: ".concat(n));let a=[];return e&&"object"==typeof t&&(Object.entries(t).forEach(e=>{let[t,s]=e;null!==s&&"object"==typeof s&&"litellm_provider"in s&&(s.litellm_provider===n||s.litellm_provider.includes(n))&&a.push(t)}),"Cohere"==e&&(console.log("Adding cohere chat models"),Object.entries(t).forEach(e=>{let[t,n]=e;null!==n&&"object"==typeof n&&"litellm_provider"in n&&"cohere_chat"===n.litellm_provider&&a.push(t)})),"AWS SageMaker"==e&&(console.log("Adding sagemaker chat models"),Object.entries(t).forEach(e=>{let[t,n]=e;null!==n&&"object"==typeof n&&"litellm_provider"in n&&"sagemaker_chat"===n.litellm_provider&&a.push(t)}))),a}},72162:function(e,t,n){var a=n(57437),s=n(2265),r=n(19250),i=n(8048),o=n(36724),l=n(89970),c=n(3810),d=n(52787),m=n(82680),p=n(3477),u=n(17732),g=n(33245),x=n(78867),h=n(88658),f=n(49817),_=n(42673),b=n(65373),v=n(69734),j=n(9114);t.Z=e=>{var t,n;let{accessToken:y}=e,[N,w]=(0,s.useState)(null),[A,I]=(0,s.useState)("LiteLLM Gateway"),[S,k]=(0,s.useState)(null),[C,E]=(0,s.useState)(""),[M,O]=(0,s.useState)({}),[D,T]=(0,s.useState)(!0),[P,z]=(0,s.useState)(""),[L,Z]=(0,s.useState)([]),[G,R]=(0,s.useState)([]),[H,F]=(0,s.useState)([]),[K,V]=(0,s.useState)("I'm alive! ✓"),[U,W]=(0,s.useState)(!1),[q,J]=(0,s.useState)(null),[B,Y]=(0,s.useState)({}),$=(0,s.useRef)(null);(0,s.useEffect)(()=>{let e=async()=>{try{T(!0);let e=await (0,r.modelHubPublicModelsCall)();console.log("ModelHubData:",e),w(e)}catch(e){console.error("There was an error fetching the public model data",e),V("Service unavailable")}finally{T(!1)}};(async()=>{let e=await (0,r.getPublicModelHubInfo)();console.log("Public Model Hub Info:",e),I(e.docs_title),k(e.custom_docs_description),E(e.litellm_version),O(e.useful_links||{})})(),e()},[]),(0,s.useEffect)(()=>{},[P,L,G,H]);let Q=(0,s.useMemo)(()=>{if(!N)return[];let e=N;if(P.trim()){let t=P.toLowerCase(),n=t.split(/\s+/),a=N.filter(e=>{let a=e.model_group.toLowerCase();return!!a.includes(t)||n.every(e=>a.includes(e))});a.length>0&&(e=a.sort((e,n)=>{let a=e.model_group.toLowerCase(),s=n.model_group.toLowerCase(),r=a===t?1e3:0,i=s===t?1e3:0,o=a.startsWith(t)?100:0,l=s.startsWith(t)?100:0,c=t.split(/\s+/).every(e=>a.includes(e))?50:0,d=t.split(/\s+/).every(e=>s.includes(e))?50:0,m=a.length;return i+l+d+(1e3-s.length)-(r+o+c+(1e3-m))}))}return e.filter(e=>{let t=0===L.length||L.some(t=>e.providers.includes(t)),n=0===G.length||G.includes(e.mode||""),a=0===H.length||Object.entries(e).filter(e=>{let[t,n]=e;return t.startsWith("supports_")&&!0===n}).some(e=>{let[t]=e,n=t.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ");return H.includes(n)});return t&&n&&a})},[N,P,L,G,H]),X=e=>{J(e),W(!0)},ee=e=>{navigator.clipboard.writeText(e),j.Z.success("Copied to clipboard!")},et=e=>e.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" "),en=e=>Object.entries(e).filter(e=>{let[t,n]=e;return t.startsWith("supports_")&&!0===n}).map(e=>{let[t]=e;return t}),ea=e=>"$".concat((1e6*e).toFixed(4)),es=e=>e?e>=1e3?"".concat((e/1e3).toFixed(0),"K"):e.toString():"N/A",er=(e,t)=>{let n=[];return e&&n.push("RPM: ".concat(e.toLocaleString())),t&&n.push("TPM: ".concat(t.toLocaleString())),n.length>0?n.join(", "):"N/A"};return(0,a.jsx)(v.f,{accessToken:y,children:(0,a.jsxs)("div",{className:"min-h-screen bg-white",children:[(0,a.jsx)(b.Z,{userID:null,userEmail:null,userRole:null,premiumUser:!1,setProxySettings:Y,proxySettings:B,accessToken:y||null,isPublicPage:!0}),(0,a.jsxs)("div",{className:"w-full px-8 py-12",children:[(0,a.jsxs)(o.Zb,{className:"mb-10 p-8 bg-white border border-gray-200 rounded-lg shadow-sm",children:[(0,a.jsx)(o.Dx,{className:"text-2xl font-semibold mb-6 text-gray-900",children:"About"}),(0,a.jsx)("p",{className:"text-gray-700 mb-6 text-base leading-relaxed",children:S||"Proxy Server to call 100+ LLMs in the OpenAI format."}),(0,a.jsx)("div",{className:"flex items-center space-x-3 text-sm text-gray-600",children:(0,a.jsxs)("span",{className:"flex items-center",children:[(0,a.jsx)("span",{className:"w-4 h-4 mr-2",children:"\uD83D\uDD27"}),"Built with litellm: v",C]})})]}),M&&Object.keys(M).length>0&&(0,a.jsxs)(o.Zb,{className:"mb-10 p-8 bg-white border border-gray-200 rounded-lg shadow-sm",children:[(0,a.jsx)(o.Dx,{className:"text-2xl font-semibold mb-6 text-gray-900",children:"Useful Links"}),(0,a.jsx)("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6",children:Object.entries(M||{}).map(e=>{let[t,n]=e;return(0,a.jsxs)("button",{onClick:()=>window.open(n,"_blank"),className:"flex items-center space-x-3 text-blue-600 hover:text-blue-800 transition-colors p-3 rounded-lg hover:bg-blue-50 border border-gray-200",children:[(0,a.jsx)(p.Z,{className:"w-4 h-4"}),(0,a.jsx)(o.xv,{className:"text-sm font-medium",children:t})]},t)})})]}),(0,a.jsxs)(o.Zb,{className:"mb-10 p-8 bg-white border border-gray-200 rounded-lg shadow-sm",children:[(0,a.jsx)(o.Dx,{className:"text-2xl font-semibold mb-6 text-gray-900",children:"Health and Endpoint Status"}),(0,a.jsx)("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-6",children:(0,a.jsxs)(o.xv,{className:"text-green-600 font-medium text-sm",children:["Service status: ",K]})})]}),(0,a.jsxs)(o.Zb,{className:"p-8 bg-white border border-gray-200 rounded-lg shadow-sm",children:[(0,a.jsx)("div",{className:"flex justify-between items-center mb-8",children:(0,a.jsx)(o.Dx,{className:"text-2xl font-semibold text-gray-900",children:"Available Models"})}),(0,a.jsxs)("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8 p-6 bg-gray-50 rounded-lg border border-gray-200",children:[(0,a.jsxs)("div",{children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2 mb-3",children:[(0,a.jsx)(o.xv,{className:"text-sm font-medium text-gray-700",children:"Search Models:"}),(0,a.jsx)(l.Z,{title:"Smart search with relevance ranking - finds models containing your search terms, ranked by relevance. Try searching 'xai grok-4', 'claude-4', 'gpt-4', or 'sonnet'",placement:"top",children:(0,a.jsx)(g.Z,{className:"w-4 h-4 text-gray-400 cursor-help"})})]}),(0,a.jsxs)("div",{className:"relative",children:[(0,a.jsx)(u.Z,{className:"w-4 h-4 text-gray-400 absolute left-3 top-1/2 transform -translate-y-1/2"}),(0,a.jsx)("input",{type:"text",placeholder:"Search model names... (smart search enabled)",value:P,onChange:e=>z(e.target.value),className:"border border-gray-300 rounded-lg pl-10 pr-4 py-2 w-full text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white"})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"text-sm font-medium mb-3 text-gray-700",children:"Provider:"}),(0,a.jsx)(d.default,{mode:"multiple",value:L,onChange:e=>Z(e),placeholder:"Select providers",className:"w-full",size:"large",allowClear:!0,optionRender:e=>{let{logo:t}=(0,_.dr)(e.value);return(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[t&&(0,a.jsx)("img",{src:t,alt:e.label,className:"w-5 h-5 flex-shrink-0 object-contain",onError:e=>{e.target.style.display="none"}}),(0,a.jsx)("span",{className:"capitalize",children:e.label})]})},children:N&&(e=>{let t=new Set;return e.forEach(e=>{e.providers.forEach(e=>t.add(e))}),Array.from(t)})(N).map(e=>(0,a.jsx)(d.default.Option,{value:e,children:e},e))})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"text-sm font-medium mb-3 text-gray-700",children:"Mode:"}),(0,a.jsx)(d.default,{mode:"multiple",value:G,onChange:e=>R(e),placeholder:"Select modes",className:"w-full",size:"large",allowClear:!0,children:N&&(e=>{let t=new Set;return e.forEach(e=>{e.mode&&t.add(e.mode)}),Array.from(t)})(N).map(e=>(0,a.jsx)(d.default.Option,{value:e,children:e},e))})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"text-sm font-medium mb-3 text-gray-700",children:"Features:"}),(0,a.jsx)(d.default,{mode:"multiple",value:H,onChange:e=>F(e),placeholder:"Select features",className:"w-full",size:"large",allowClear:!0,children:N&&(e=>{let t=new Set;return e.forEach(e=>{Object.entries(e).filter(e=>{let[t,n]=e;return t.startsWith("supports_")&&!0===n}).forEach(e=>{let[n]=e,a=n.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ");t.add(a)})}),Array.from(t).sort()})(N).map(e=>(0,a.jsx)(d.default.Option,{value:e,children:e},e))})]})]}),(0,a.jsx)(i.C,{columns:[{header:"Model Name",accessorKey:"model_group",enableSorting:!0,cell:e=>{let{row:t}=e;return(0,a.jsx)("div",{className:"overflow-hidden",children:(0,a.jsx)(l.Z,{title:t.original.model_group,children:(0,a.jsx)(o.zx,{size:"xs",variant:"light",className:"font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left",onClick:()=>X(t.original),children:t.original.model_group})})})},size:150},{header:"Providers",accessorKey:"providers",enableSorting:!0,cell:e=>{let{row:t}=e,n=t.original.providers;return(0,a.jsx)("div",{className:"flex flex-wrap gap-1",children:n.map(e=>{let{logo:t}=(0,_.dr)(e);return(0,a.jsxs)("div",{className:"flex items-center space-x-1 px-2 py-1 bg-gray-100 rounded text-xs",children:[t&&(0,a.jsx)("img",{src:t,alt:e,className:"w-3 h-3 flex-shrink-0 object-contain",onError:e=>{e.target.style.display="none"}}),(0,a.jsx)("span",{className:"capitalize",children:e})]},e)})})},size:120},{header:"Mode",accessorKey:"mode",enableSorting:!0,cell:e=>{let{row:t}=e,n=t.original.mode;return(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)("span",{children:(e=>{switch(null==e?void 0:e.toLowerCase()){case"chat":return"\uD83D\uDCAC";case"rerank":return"\uD83D\uDD04";case"embedding":return"\uD83D\uDCC4";default:return"\uD83E\uDD16"}})(n||"")}),(0,a.jsx)(o.xv,{children:n||"Chat"})]})},size:100},{header:"Max Input",accessorKey:"max_input_tokens",enableSorting:!0,cell:e=>{let{row:t}=e;return(0,a.jsx)(o.xv,{className:"text-center",children:es(t.original.max_input_tokens)})},size:100,meta:{className:"text-center"}},{header:"Max Output",accessorKey:"max_output_tokens",enableSorting:!0,cell:e=>{let{row:t}=e;return(0,a.jsx)(o.xv,{className:"text-center",children:es(t.original.max_output_tokens)})},size:100,meta:{className:"text-center"}},{header:"Input $/1M",accessorKey:"input_cost_per_token",enableSorting:!0,cell:e=>{let{row:t}=e,n=t.original.input_cost_per_token;return(0,a.jsx)(o.xv,{className:"text-center",children:n?ea(n):"Free"})},size:100,meta:{className:"text-center"}},{header:"Output $/1M",accessorKey:"output_cost_per_token",enableSorting:!0,cell:e=>{let{row:t}=e,n=t.original.output_cost_per_token;return(0,a.jsx)(o.xv,{className:"text-center",children:n?ea(n):"Free"})},size:100,meta:{className:"text-center"}},{header:"Features",accessorKey:"supports_vision",enableSorting:!1,cell:e=>{let{row:t}=e,n=Object.entries(t.original).filter(e=>{let[t,n]=e;return t.startsWith("supports_")&&!0===n}).map(e=>{let[t]=e;return et(t)});return 0===n.length?(0,a.jsx)(o.xv,{className:"text-gray-400",children:"-"}):1===n.length?(0,a.jsx)("div",{className:"h-6 flex items-center",children:(0,a.jsx)(c.Z,{color:"blue",className:"text-xs",children:n[0]})}):(0,a.jsxs)("div",{className:"h-6 flex items-center space-x-1",children:[(0,a.jsx)(c.Z,{color:"blue",className:"text-xs",children:n[0]}),(0,a.jsx)(l.Z,{title:(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsx)("div",{className:"font-medium",children:"All Features:"}),n.map((e,t)=>(0,a.jsxs)("div",{className:"text-xs",children:["• ",e]},t))]}),trigger:"click",placement:"topLeft",children:(0,a.jsxs)("span",{className:"text-xs text-blue-600 cursor-pointer hover:text-blue-800 hover:underline",onClick:e=>e.stopPropagation(),children:["+",n.length-1]})})]})},size:120},{header:"Limits",accessorKey:"rpm",enableSorting:!0,cell:e=>{let{row:t}=e,n=t.original;return(0,a.jsx)(o.xv,{className:"text-xs text-gray-600",children:er(n.rpm,n.tpm)})},size:150}],data:Q,isLoading:D,table:$,defaultSorting:[{id:"model_group",desc:!1}]}),(0,a.jsx)("div",{className:"mt-8 text-center",children:(0,a.jsxs)(o.xv,{className:"text-sm text-gray-600",children:["Showing ",Q.length," of ",(null==N?void 0:N.length)||0," models"]})})]})]}),(0,a.jsx)(m.Z,{title:(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)("span",{children:(null==q?void 0:q.model_group)||"Model Details"}),q&&(0,a.jsx)(l.Z,{title:"Copy model name",children:(0,a.jsx)(x.Z,{onClick:()=>ee(q.model_group),className:"cursor-pointer text-gray-500 hover:text-blue-500 w-4 h-4"})})]}),width:1e3,open:U,footer:null,onOk:()=>{W(!1),J(null)},onCancel:()=>{W(!1),J(null)},children:q&&(0,a.jsxs)("div",{className:"space-y-6",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"text-lg font-semibold mb-4",children:"Model Overview"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4 mb-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"font-medium",children:"Model Name:"}),(0,a.jsx)(o.xv,{children:q.model_group})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"font-medium",children:"Mode:"}),(0,a.jsx)(o.xv,{children:q.mode||"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"font-medium",children:"Providers:"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:q.providers.map(e=>{let{logo:t}=(0,_.dr)(e);return(0,a.jsx)(c.Z,{color:"blue",children:(0,a.jsxs)("div",{className:"flex items-center space-x-1",children:[t&&(0,a.jsx)("img",{src:t,alt:e,className:"w-3 h-3 flex-shrink-0 object-contain",onError:e=>{e.target.style.display="none"}}),(0,a.jsx)("span",{className:"capitalize",children:e})]})},e)})})]})]}),q.model_group.includes("*")&&(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4",children:(0,a.jsxs)("div",{className:"flex items-start space-x-2",children:[(0,a.jsx)(g.Z,{className:"w-4 h-4 text-blue-600 mt-0.5 flex-shrink-0"}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"font-medium text-blue-900 mb-2",children:"Wildcard Routing"}),(0,a.jsxs)(o.xv,{className:"text-sm text-blue-800 mb-2",children:["This model uses wildcard routing. You can pass any value where you see the ",(0,a.jsx)("code",{className:"bg-blue-100 px-1 py-0.5 rounded text-xs",children:"*"})," symbol."]}),(0,a.jsxs)(o.xv,{className:"text-sm text-blue-800",children:["For example, with ",(0,a.jsx)("code",{className:"bg-blue-100 px-1 py-0.5 rounded text-xs",children:q.model_group}),", you can use any string (",(0,a.jsx)("code",{className:"bg-blue-100 px-1 py-0.5 rounded text-xs",children:q.model_group.replace("*","my-custom-value")}),") that matches this pattern."]})]})]})})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"text-lg font-semibold mb-4",children:"Token & Cost Information"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"font-medium",children:"Max Input Tokens:"}),(0,a.jsx)(o.xv,{children:(null===(t=q.max_input_tokens)||void 0===t?void 0:t.toLocaleString())||"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"font-medium",children:"Max Output Tokens:"}),(0,a.jsx)(o.xv,{children:(null===(n=q.max_output_tokens)||void 0===n?void 0:n.toLocaleString())||"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"font-medium",children:"Input Cost per 1M Tokens:"}),(0,a.jsx)(o.xv,{children:q.input_cost_per_token?ea(q.input_cost_per_token):"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"font-medium",children:"Output Cost per 1M Tokens:"}),(0,a.jsx)(o.xv,{children:q.output_cost_per_token?ea(q.output_cost_per_token):"Not specified"})]})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"text-lg font-semibold mb-4",children:"Capabilities"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:(()=>{let e=en(q),t=["green","blue","purple","orange","red","yellow"];return 0===e.length?(0,a.jsx)(o.xv,{className:"text-gray-500",children:"No special capabilities listed"}):e.map((e,n)=>(0,a.jsx)(c.Z,{color:t[n%t.length],children:et(e)},e))})()})]}),(q.tpm||q.rpm)&&(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"text-lg font-semibold mb-4",children:"Rate Limits"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[q.tpm&&(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"font-medium",children:"Tokens per Minute:"}),(0,a.jsx)(o.xv,{children:q.tpm.toLocaleString()})]}),q.rpm&&(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"font-medium",children:"Requests per Minute:"}),(0,a.jsx)(o.xv,{children:q.rpm.toLocaleString()})]})]})]}),q.supported_openai_params&&(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"text-lg font-semibold mb-4",children:"Supported OpenAI Parameters"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:q.supported_openai_params.map(e=>(0,a.jsx)(c.Z,{color:"green",children:e},e))})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"text-lg font-semibold mb-4",children:"Usage Example"}),(0,a.jsx)("div",{className:"bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto",children:(0,a.jsx)("pre",{className:"text-sm",children:(0,h.L)({apiKeySource:"custom",accessToken:null,apiKey:"your_api_key",inputMessage:"Hello, how are you?",chatHistory:[{role:"user",content:"Hello, how are you?",isImage:!1}],selectedTags:[],selectedVectorStores:[],selectedGuardrails:[],selectedMCPTools:[],endpointType:(0,f.vf)(q.mode||"chat"),selectedModel:q.model_group,selectedSdk:"openai"})})}),(0,a.jsx)("div",{className:"mt-2 text-right",children:(0,a.jsx)("button",{onClick:()=>{ee((0,h.L)({apiKeySource:"custom",accessToken:null,apiKey:"your_api_key",inputMessage:"Hello, how are you?",chatHistory:[{role:"user",content:"Hello, how are you?",isImage:!1}],selectedTags:[],selectedVectorStores:[],selectedGuardrails:[],selectedMCPTools:[],endpointType:(0,f.vf)(q.mode||"chat"),selectedModel:q.model_group,selectedSdk:"openai"}))},className:"text-sm text-blue-600 hover:text-blue-800 cursor-pointer",children:"Copy to clipboard"})})]})]})})]})})}},69734:function(e,t,n){n.d(t,{F:function(){return o},f:function(){return l}});var a=n(57437),s=n(2265),r=n(19250);let i=(0,s.createContext)(void 0),o=()=>{let e=(0,s.useContext)(i);if(!e)throw Error("useTheme must be used within a ThemeProvider");return e},l=e=>{let{children:t,accessToken:n}=e,[o,l]=(0,s.useState)(null);return(0,s.useEffect)(()=>{(async()=>{if(n)try{let t=(0,r.getProxyBaseUrl)(),a=await fetch(t?"".concat(t,"/get/ui_theme_settings"):"/get/ui_theme_settings",{method:"GET",headers:{Authorization:"Bearer ".concat(n),"Content-Type":"application/json"}});if(a.ok){var e;let t=await a.json();(null===(e=t.values)||void 0===e?void 0:e.logo_url)&&l(t.values.logo_url)}}catch(e){console.warn("Failed to load logo settings from backend:",e)}})()},[n]),(0,a.jsx)(i.Provider,{value:{logoUrl:o,setLogoUrl:l},children:t})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/162-714ca0ed10a07f66.js b/litellm/proxy/_experimental/out/_next/static/chunks/162-714ca0ed10a07f66.js deleted file mode 100644 index be51aa66580..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/162-714ca0ed10a07f66.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[162],{36724:function(e,t,n){n.d(t,{Dx:function(){return i.Z},Zb:function(){return s.Z},xv:function(){return r.Z},zx:function(){return a.Z}});var a=n(20831),s=n(12514),r=n(84264),i=n(96761)},19130:function(e,t,n){n.d(t,{RM:function(){return s.Z},SC:function(){return l.Z},iA:function(){return a.Z},pj:function(){return r.Z},ss:function(){return i.Z},xs:function(){return o.Z}});var a=n(21626),s=n(97214),r=n(28241),i=n(58834),o=n(69552),l=n(71876)},88658:function(e,t,n){n.d(t,{L:function(){return s}});var a=n(49817);let s=e=>{let t;let{apiKeySource:n,accessToken:s,apiKey:r,inputMessage:i,chatHistory:o,selectedTags:l,selectedVectorStores:c,selectedGuardrails:d,endpointType:m,selectedModel:p,selectedSdk:u}=e,g="session"===n?s:r,x=window.location.origin,h=i||"Your prompt here",f=h.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n"),_=o.filter(e=>!e.isImage).map(e=>{let{role:t,content:n}=e;return{role:t,content:n}}),b={};l.length>0&&(b.tags=l),c.length>0&&(b.vector_stores=c),d.length>0&&(b.guardrails=d);let v=p||"your-model-name",j="azure"===u?'import openai\n\nclient = openai.AzureOpenAI(\n api_key="'.concat(g||"YOUR_LITELLM_API_KEY",'",\n azure_endpoint="').concat(x,'",\n api_version="2024-02-01"\n)'):'import openai\n\nclient = openai.OpenAI(\n api_key="'.concat(g||"YOUR_LITELLM_API_KEY",'",\n base_url="').concat(x,'"\n)');switch(m){case a.KP.CHAT:{let e=Object.keys(b).length>0,n="";if(e){let e=JSON.stringify({metadata:b},null,2).split("\n").map(e=>" ".repeat(4)+e).join("\n").trim();n=",\n extra_body=".concat(e)}let a=_.length>0?_:[{role:"user",content:h}];t='\nimport base64\n\n# Helper function to encode images to base64\ndef encode_image(image_path):\n with open(image_path, "rb") as image_file:\n return base64.b64encode(image_file.read()).decode(\'utf-8\')\n\n# Example with text only\nresponse = client.chat.completions.create(\n model="'.concat(v,'",\n messages=').concat(JSON.stringify(a,null,4)).concat(n,'\n)\n\nprint(response)\n\n# Example with image or PDF (uncomment and provide file path to use)\n# base64_file = encode_image("path/to/your/file.jpg") # or .pdf\n# response_with_file = client.chat.completions.create(\n# model="').concat(v,'",\n# messages=[\n# {\n# "role": "user",\n# "content": [\n# {\n# "type": "text",\n# "text": "').concat(f,'"\n# },\n# {\n# "type": "image_url",\n# "image_url": {\n# "url": f"data:image/jpeg;base64,{base64_file}" # or data:application/pdf;base64,{base64_file}\n# }\n# }\n# ]\n# }\n# ]').concat(n,"\n# )\n# print(response_with_file)\n");break}case a.KP.RESPONSES:{let e=Object.keys(b).length>0,n="";if(e){let e=JSON.stringify({metadata:b},null,2).split("\n").map(e=>" ".repeat(4)+e).join("\n").trim();n=",\n extra_body=".concat(e)}let a=_.length>0?_:[{role:"user",content:h}];t='\nimport base64\n\n# Helper function to encode images to base64\ndef encode_image(image_path):\n with open(image_path, "rb") as image_file:\n return base64.b64encode(image_file.read()).decode(\'utf-8\')\n\n# Example with text only\nresponse = client.responses.create(\n model="'.concat(v,'",\n input=').concat(JSON.stringify(a,null,4)).concat(n,'\n)\n\nprint(response.output_text)\n\n# Example with image or PDF (uncomment and provide file path to use)\n# base64_file = encode_image("path/to/your/file.jpg") # or .pdf\n# response_with_file = client.responses.create(\n# model="').concat(v,'",\n# input=[\n# {\n# "role": "user",\n# "content": [\n# {"type": "input_text", "text": "').concat(f,'"},\n# {\n# "type": "input_image",\n# "image_url": f"data:image/jpeg;base64,{base64_file}", # or data:application/pdf;base64,{base64_file}\n# },\n# ],\n# }\n# ]').concat(n,"\n# )\n# print(response_with_file.output_text)\n");break}case a.KP.IMAGE:t="azure"===u?"\n# NOTE: The Azure SDK does not have a direct equivalent to the multi-modal 'responses.create' method shown for OpenAI.\n# This snippet uses 'client.images.generate' and will create a new image based on your prompt.\n# It does not use the uploaded image, as 'client.images.generate' does not support image inputs in this context.\nimport os\nimport requests\nimport json\nimport time\nfrom PIL import Image\n\nresult = client.images.generate(\n model=\"".concat(v,'",\n prompt="').concat(i,'",\n n=1\n)\n\njson_response = json.loads(result.model_dump_json())\n\n# Set the directory for the stored image\nimage_dir = os.path.join(os.curdir, \'images\')\n\n# If the directory doesn\'t exist, create it\nif not os.path.isdir(image_dir):\n os.mkdir(image_dir)\n\n# Initialize the image path\nimage_filename = f"generated_image_{int(time.time())}.png"\nimage_path = os.path.join(image_dir, image_filename)\n\ntry:\n # Retrieve the generated image\n if json_response.get("data") && len(json_response["data"]) > 0 && json_response["data"][0].get("url"):\n image_url = json_response["data"][0]["url"]\n generated_image = requests.get(image_url).content\n with open(image_path, "wb") as image_file:\n image_file.write(generated_image)\n\n print(f"Image saved to {image_path}")\n # Display the image\n image = Image.open(image_path)\n image.show()\n else:\n print("Could not find image URL in response.")\n print("Full response:", json_response)\nexcept Exception as e:\n print(f"An error occurred: {e}")\n print("Full response:", json_response)\n'):"\nimport base64\nimport os\nimport time\nimport json\nfrom PIL import Image\nimport requests\n\n# Helper function to encode images to base64\ndef encode_image(image_path):\n with open(image_path, \"rb\") as image_file:\n return base64.b64encode(image_file.read()).decode('utf-8')\n\n# Helper function to create a file (simplified for this example)\ndef create_file(image_path):\n # In a real implementation, this would upload the file to OpenAI\n # For this example, we'll just return a placeholder ID\n return f\"file_{os.path.basename(image_path).replace('.', '_')}\"\n\n# The prompt entered by the user\nprompt = \"".concat(f,'"\n\n# Encode images to base64\nbase64_image1 = encode_image("body-lotion.png")\nbase64_image2 = encode_image("soap.png")\n\n# Create file IDs\nfile_id1 = create_file("body-lotion.png")\nfile_id2 = create_file("incense-kit.png")\n\nresponse = client.responses.create(\n model="').concat(v,'",\n input=[\n {\n "role": "user",\n "content": [\n {"type": "input_text", "text": prompt},\n {\n "type": "input_image",\n "image_url": f"data:image/jpeg;base64,{base64_image1}",\n },\n {\n "type": "input_image",\n "image_url": f"data:image/jpeg;base64,{base64_image2}",\n },\n {\n "type": "input_image",\n "file_id": file_id1,\n },\n {\n "type": "input_image",\n "file_id": file_id2,\n }\n ],\n }\n ],\n tools=[{"type": "image_generation"}],\n)\n\n# Process the response\nimage_generation_calls = [\n output\n for output in response.output\n if output.type == "image_generation_call"\n]\n\nimage_data = [output.result for output in image_generation_calls]\n\nif image_data:\n image_base64 = image_data[0]\n image_filename = f"edited_image_{int(time.time())}.png"\n with open(image_filename, "wb") as f:\n f.write(base64.b64decode(image_base64))\n print(f"Image saved to {image_filename}")\nelse:\n # If no image is generated, there might be a text response with an explanation\n text_response = [output.text for output in response.output if hasattr(output, \'text\')]\n if text_response:\n print("No image generated. Model response:")\n print("\\n".join(text_response))\n else:\n print("No image data found in response.")\n print("Full response for debugging:")\n print(response)\n');break;case a.KP.IMAGE_EDITS:t="azure"===u?'\nimport base64\nimport os\nimport time\nimport json\nfrom PIL import Image\nimport requests\n\n# Helper function to encode images to base64\ndef encode_image(image_path):\n with open(image_path, "rb") as image_file:\n return base64.b64encode(image_file.read()).decode(\'utf-8\')\n\n# The prompt entered by the user\nprompt = "'.concat(f,'"\n\n# Encode images to base64\nbase64_image1 = encode_image("body-lotion.png")\nbase64_image2 = encode_image("soap.png")\n\n# Create file IDs\nfile_id1 = create_file("body-lotion.png")\nfile_id2 = create_file("incense-kit.png")\n\nresponse = client.responses.create(\n model="').concat(v,'",\n input=[\n {\n "role": "user",\n "content": [\n {"type": "input_text", "text": prompt},\n {\n "type": "input_image",\n "image_url": f"data:image/jpeg;base64,{base64_image1}",\n },\n {\n "type": "input_image",\n "image_url": f"data:image/jpeg;base64,{base64_image2}",\n },\n {\n "type": "input_image",\n "file_id": file_id1,\n },\n {\n "type": "input_image",\n "file_id": file_id2,\n }\n ],\n }\n ],\n tools=[{"type": "image_generation"}],\n)\n\n# Process the response\nimage_generation_calls = [\n output\n for output in response.output\n if output.type == "image_generation_call"\n]\n\nimage_data = [output.result for output in image_generation_calls]\n\nif image_data:\n image_base64 = image_data[0]\n image_filename = f"edited_image_{int(time.time())}.png"\n with open(image_filename, "wb") as f:\n f.write(base64.b64decode(image_base64))\n print(f"Image saved to {image_filename}")\nelse:\n # If no image is generated, there might be a text response with an explanation\n text_response = [output.text for output in response.output if hasattr(output, \'text\')]\n if text_response:\n print("No image generated. Model response:")\n print("\\n".join(text_response))\n else:\n print("No image data found in response.")\n print("Full response for debugging:")\n print(response)\n'):"\nimport base64\nimport os\nimport time\n\n# Helper function to encode images to base64\ndef encode_image(image_path):\n with open(image_path, \"rb\") as image_file:\n return base64.b64encode(image_file.read()).decode('utf-8')\n\n# Helper function to create a file (simplified for this example)\ndef create_file(image_path):\n # In a real implementation, this would upload the file to OpenAI\n # For this example, we'll just return a placeholder ID\n return f\"file_{os.path.basename(image_path).replace('.', '_')}\"\n\n# The prompt entered by the user\nprompt = \"".concat(f,'"\n\n# Encode images to base64\nbase64_image1 = encode_image("body-lotion.png")\nbase64_image2 = encode_image("soap.png")\n\n# Create file IDs\nfile_id1 = create_file("body-lotion.png")\nfile_id2 = create_file("incense-kit.png")\n\nresponse = client.responses.create(\n model="').concat(v,'",\n input=[\n {\n "role": "user",\n "content": [\n {"type": "input_text", "text": prompt},\n {\n "type": "input_image",\n "image_url": f"data:image/jpeg;base64,{base64_image1}",\n },\n {\n "type": "input_image",\n "image_url": f"data:image/jpeg;base64,{base64_image2}",\n },\n {\n "type": "input_image",\n "file_id": file_id1,\n },\n {\n "type": "input_image",\n "file_id": file_id2,\n }\n ],\n }\n ],\n tools=[{"type": "image_generation"}],\n)\n\n# Process the response\nimage_generation_calls = [\n output\n for output in response.output\n if output.type == "image_generation_call"\n]\n\nimage_data = [output.result for output in image_generation_calls]\n\nif image_data:\n image_base64 = image_data[0]\n image_filename = f"edited_image_{int(time.time())}.png"\n with open(image_filename, "wb") as f:\n f.write(base64.b64decode(image_base64))\n print(f"Image saved to {image_filename}")\nelse:\n # If no image is generated, there might be a text response with an explanation\n text_response = [output.text for output in response.output if hasattr(output, \'text\')]\n if text_response:\n print("No image generated. Model response:")\n print("\\n".join(text_response))\n else:\n print("No image data found in response.")\n print("Full response for debugging:")\n print(response)\n');break;default:t="\n# Code generation for this endpoint is not implemented yet."}return"".concat(j,"\n").concat(t)}},49817:function(e,t,n){var a,s,r,i;n.d(t,{KP:function(){return s},vf:function(){return l}}),(r=a||(a={})).IMAGE_GENERATION="image_generation",r.CHAT="chat",r.RESPONSES="responses",r.IMAGE_EDITS="image_edits",r.ANTHROPIC_MESSAGES="anthropic_messages",(i=s||(s={})).IMAGE="image",i.CHAT="chat",i.RESPONSES="responses",i.IMAGE_EDITS="image_edits",i.ANTHROPIC_MESSAGES="anthropic_messages";let o={image_generation:"image",chat:"chat",responses:"responses",image_edits:"image_edits",anthropic_messages:"anthropic_messages"},l=e=>{if(console.log("getEndpointType:",e),Object.values(a).includes(e)){let t=o[e];return console.log("endpointType:",t),t}return"chat"}},29488:function(e,t,n){n.d(t,{Hc:function(){return i},Ui:function(){return r},e4:function(){return o},xd:function(){return l}});let a="litellm_mcp_auth_tokens",s=()=>{try{let e=localStorage.getItem(a);return e?JSON.parse(e):{}}catch(e){return console.error("Error reading MCP auth tokens from localStorage:",e),{}}},r=(e,t)=>{try{let n=s()[e];if(n&&n.serverAlias===t||n&&!t&&!n.serverAlias)return n.authValue;return null}catch(e){return console.error("Error getting MCP auth token:",e),null}},i=(e,t,n,r)=>{try{let i=s();i[e]={serverId:e,serverAlias:r,authValue:t,authType:n,timestamp:Date.now()},localStorage.setItem(a,JSON.stringify(i))}catch(e){console.error("Error storing MCP auth token:",e)}},o=e=>{try{let t=s();delete t[e],localStorage.setItem(a,JSON.stringify(t))}catch(e){console.error("Error removing MCP auth token:",e)}},l=()=>{try{localStorage.removeItem(a)}catch(e){console.error("Error clearing MCP auth tokens:",e)}}},8048:function(e,t,n){n.d(t,{C:function(){return m}});var a=n(57437),s=n(71594),r=n(24525),i=n(2265),o=n(19130),l=n(44633),c=n(86462),d=n(49084);function m(e){let{data:t=[],columns:n,isLoading:m=!1,table:p,defaultSorting:u=[]}=e,[g,x]=i.useState(u),[h]=i.useState("onChange"),[f,_]=i.useState({}),[b,v]=i.useState({}),j=(0,s.b7)({data:t,columns:n,state:{sorting:g,columnSizing:f,columnVisibility:b},columnResizeMode:h,onSortingChange:x,onColumnSizingChange:_,onColumnVisibilityChange:v,getCoreRowModel:(0,r.sC)(),getSortedRowModel:(0,r.tj)(),enableSorting:!0,enableColumnResizing:!0,defaultColumn:{minSize:40,maxSize:500}});return i.useEffect(()=>{p&&(p.current=j)},[j,p]),(0,a.jsx)("div",{className:"rounded-lg custom-border relative",children:(0,a.jsx)("div",{className:"overflow-x-auto",children:(0,a.jsx)("div",{className:"relative min-w-full",children:(0,a.jsxs)(o.iA,{className:"[&_td]:py-2 [&_th]:py-2 w-full",children:[(0,a.jsx)(o.ss,{children:j.getHeaderGroups().map(e=>(0,a.jsx)(o.SC,{children:e.headers.map(e=>{var t;return(0,a.jsxs)(o.xs,{className:"py-1 h-8 relative ".concat("actions"===e.id?"sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)] z-20 w-[120px] ml-8":""," ").concat((null===(t=e.column.columnDef.meta)||void 0===t?void 0:t.className)||""),style:{width:"actions"===e.id?120:e.getSize(),position:"actions"===e.id?"sticky":"relative",right:"actions"===e.id?0:"auto"},onClick:e.column.getCanSort()?e.column.getToggleSortingHandler():void 0,children:[(0,a.jsxs)("div",{className:"flex items-center justify-between gap-2",children:[(0,a.jsx)("div",{className:"flex items-center",children:e.isPlaceholder?null:(0,s.ie)(e.column.columnDef.header,e.getContext())}),"actions"!==e.id&&e.column.getCanSort()&&(0,a.jsx)("div",{className:"w-4",children:e.column.getIsSorted()?({asc:(0,a.jsx)(l.Z,{className:"h-4 w-4 text-blue-500"}),desc:(0,a.jsx)(c.Z,{className:"h-4 w-4 text-blue-500"})})[e.column.getIsSorted()]:(0,a.jsx)(d.Z,{className:"h-4 w-4 text-gray-400"})})]}),e.column.getCanResize()&&(0,a.jsx)("div",{onMouseDown:e.getResizeHandler(),onTouchStart:e.getResizeHandler(),className:"absolute right-0 top-0 h-full w-2 cursor-col-resize select-none touch-none ".concat(e.column.getIsResizing()?"bg-blue-500":"hover:bg-blue-200")})]},e.id)})},e.id))}),(0,a.jsx)(o.RM,{children:m?(0,a.jsx)(o.SC,{children:(0,a.jsx)(o.pj,{colSpan:n.length,className:"h-8 text-center",children:(0,a.jsx)("div",{className:"text-center text-gray-500",children:(0,a.jsx)("p",{children:"\uD83D\uDE85 Loading models..."})})})}):j.getRowModel().rows.length>0?j.getRowModel().rows.map(e=>(0,a.jsx)(o.SC,{children:e.getVisibleCells().map(e=>{var t;return(0,a.jsx)(o.pj,{className:"py-0.5 ".concat("actions"===e.column.id?"sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)] z-20 w-[120px] ml-8":""," ").concat((null===(t=e.column.columnDef.meta)||void 0===t?void 0:t.className)||""),style:{width:"actions"===e.column.id?120:e.column.getSize(),position:"actions"===e.column.id?"sticky":"relative",right:"actions"===e.column.id?0:"auto"},children:(0,s.ie)(e.column.columnDef.cell,e.getContext())},e.id)})},e.id)):(0,a.jsx)(o.SC,{children:(0,a.jsx)(o.pj,{colSpan:n.length,className:"h-8 text-center",children:(0,a.jsx)("div",{className:"text-center text-gray-500",children:(0,a.jsx)("p",{children:"No models found"})})})})})]})})})})}},65373:function(e,t,n){n.d(t,{Z:function(){return v}});var a=n(57437),s=n(27648),r=n(2265),i=n(89970),o=n(80795),l=n(19250),c=n(15883),d=n(46346),m=n(57400),p=n(91870),u=n(40428),g=n(83884),x=n(45524),h=n(3914);let f=async e=>{if(!e)return null;try{return await (0,l.getProxyUISettings)(e)}catch(e){return console.error("Error fetching proxy settings:",e),null}};var _=n(69734),b=n(29488),v=e=>{let{userID:t,userEmail:n,userRole:v,premiumUser:j,proxySettings:y,setProxySettings:N,accessToken:w,isPublicPage:A=!1,sidebarCollapsed:I=!1,onToggleSidebar:S}=e,k=(0,l.getProxyBaseUrl)(),[C,E]=(0,r.useState)(""),{logoUrl:M}=(0,_.F)();(0,r.useEffect)(()=>{(async()=>{if(w){let e=await f(w);console.log("response from fetchProxySettings",e),e&&N(e)}})()},[w]),(0,r.useEffect)(()=>{E((null==y?void 0:y.PROXY_LOGOUT_URL)||"")},[y]);let O=[{key:"user-info",label:(0,a.jsxs)("div",{className:"px-3 py-3 border-b border-gray-100",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between mb-3",children:[(0,a.jsxs)("div",{className:"flex items-center",children:[(0,a.jsx)(c.Z,{className:"mr-2 text-gray-700"}),(0,a.jsx)("span",{className:"text-sm font-semibold text-gray-900",children:t})]}),j?(0,a.jsx)(i.Z,{title:"Premium User",placement:"left",children:(0,a.jsxs)("div",{className:"flex items-center bg-gradient-to-r from-amber-500 to-yellow-500 text-white px-2 py-0.5 rounded-full cursor-help",children:[(0,a.jsx)(d.Z,{className:"mr-1 text-xs"}),(0,a.jsx)("span",{className:"text-xs font-medium",children:"Premium"})]})}):(0,a.jsx)(i.Z,{title:"Upgrade to Premium for advanced features",placement:"left",children:(0,a.jsxs)("div",{className:"flex items-center bg-gray-100 text-gray-500 px-2 py-0.5 rounded-full cursor-help",children:[(0,a.jsx)(d.Z,{className:"mr-1 text-xs"}),(0,a.jsx)("span",{className:"text-xs font-medium",children:"Standard"})]})})]}),(0,a.jsxs)("div",{className:"space-y-2",children:[(0,a.jsxs)("div",{className:"flex items-center text-sm",children:[(0,a.jsx)(m.Z,{className:"mr-2 text-gray-400 text-xs"}),(0,a.jsx)("span",{className:"text-gray-500 text-xs",children:"Role"}),(0,a.jsx)("span",{className:"ml-auto text-gray-700 font-medium",children:v})]}),(0,a.jsxs)("div",{className:"flex items-center text-sm",children:[(0,a.jsx)(p.Z,{className:"mr-2 text-gray-400 text-xs"}),(0,a.jsx)("span",{className:"text-gray-500 text-xs",children:"Email"}),(0,a.jsx)("span",{className:"ml-auto text-gray-700 font-medium truncate max-w-[150px]",title:n||"Unknown",children:n||"Unknown"})]})]})]})},{key:"logout",label:(0,a.jsxs)("div",{className:"flex items-center py-2 px-3 hover:bg-gray-50 rounded-md mx-1 my-1",onClick:()=>{(0,h.b)(),(0,b.xd)(),window.location.href=C},children:[(0,a.jsx)(u.Z,{className:"mr-3 text-gray-600"}),(0,a.jsx)("span",{className:"text-gray-800",children:"Logout"})]})}];return(0,a.jsx)("nav",{className:"bg-white border-b border-gray-200 sticky top-0 z-10",children:(0,a.jsx)("div",{className:"w-full",children:(0,a.jsxs)("div",{className:"flex items-center h-14 px-4",children:[" ",(0,a.jsxs)("div",{className:"flex items-center flex-shrink-0",children:[S&&(0,a.jsx)("button",{onClick:S,className:"flex items-center justify-center w-10 h-10 mr-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded transition-colors",title:I?"Expand sidebar":"Collapse sidebar",children:(0,a.jsx)("span",{className:"text-lg",children:I?(0,a.jsx)(g.Z,{}):(0,a.jsx)(x.Z,{})})}),(0,a.jsx)(s.default,{href:"/",className:"flex items-center",children:(0,a.jsx)("img",{src:M||"".concat(k,"/get_image"),alt:"LiteLLM Brand",className:"h-10 w-auto"})})]}),(0,a.jsxs)("div",{className:"flex items-center space-x-5 ml-auto",children:[(0,a.jsx)("a",{href:"https://docs.litellm.ai/docs/",target:"_blank",rel:"noopener noreferrer",className:"text-sm text-gray-600 hover:text-gray-900 transition-colors",children:"Docs"}),!A&&(0,a.jsx)(o.Z,{menu:{items:O,className:"min-w-[200px]",style:{padding:"8px",marginTop:"8px",borderRadius:"12px",boxShadow:"0 4px 24px rgba(0, 0, 0, 0.08)"}},overlayStyle:{minWidth:"200px"},children:(0,a.jsxs)("button",{className:"inline-flex items-center text-sm text-gray-600 hover:text-gray-900 transition-colors",children:["User",(0,a.jsx)("svg",{className:"ml-1 w-5 h-5 text-gray-500",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,a.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:1.5,d:"M19 9l-7 7-7-7"})})]})})]})]})})})}},42673:function(e,t,n){var a,s;n.d(t,{Cl:function(){return a},bK:function(){return d},cd:function(){return o},dr:function(){return l},fK:function(){return r},ph:function(){return c}}),n(2265),(s=a||(a={})).AIML="AI/ML API",s.Bedrock="Amazon Bedrock",s.Anthropic="Anthropic",s.AssemblyAI="AssemblyAI",s.SageMaker="AWS SageMaker",s.Azure="Azure",s.Azure_AI_Studio="Azure AI Foundry (Studio)",s.Cerebras="Cerebras",s.Cohere="Cohere",s.Databricks="Databricks",s.DeepInfra="DeepInfra",s.Deepgram="Deepgram",s.Deepseek="Deepseek",s.ElevenLabs="ElevenLabs",s.FireworksAI="Fireworks AI",s.Google_AI_Studio="Google AI Studio",s.GradientAI="GradientAI",s.Groq="Groq",s.Hosted_Vllm="vllm",s.JinaAI="Jina AI",s.MistralAI="Mistral AI",s.Ollama="Ollama",s.OpenAI="OpenAI",s.OpenAI_Compatible="OpenAI-Compatible Endpoints (Together AI, etc.)",s.OpenAI_Text="OpenAI Text Completion",s.OpenAI_Text_Compatible="OpenAI-Compatible Text Completion Models (Together AI, etc.)",s.Openrouter="Openrouter",s.Perplexity="Perplexity",s.Sambanova="Sambanova",s.TogetherAI="TogetherAI",s.Triton="Triton",s.Vertex_AI="Vertex AI (Anthropic, Gemini, etc.)",s.VolcEngine="VolcEngine",s.Voyage="Voyage AI",s.xAI="xAI";let r={AIML:"aiml",OpenAI:"openai",OpenAI_Text:"text-completion-openai",Azure:"azure",Azure_AI_Studio:"azure_ai",Anthropic:"anthropic",Google_AI_Studio:"gemini",Bedrock:"bedrock",Groq:"groq",MistralAI:"mistral",Cohere:"cohere",OpenAI_Compatible:"openai",OpenAI_Text_Compatible:"text-completion-openai",Vertex_AI:"vertex_ai",Databricks:"databricks",xAI:"xai",Deepseek:"deepseek",Ollama:"ollama",AssemblyAI:"assemblyai",Cerebras:"cerebras",Sambanova:"sambanova",Perplexity:"perplexity",TogetherAI:"together_ai",Openrouter:"openrouter",FireworksAI:"fireworks_ai",GradientAI:"gradient_ai",Triton:"triton",Deepgram:"deepgram",ElevenLabs:"elevenlabs",SageMaker:"sagemaker_chat",Voyage:"voyage",JinaAI:"jina_ai",VolcEngine:"volcengine",DeepInfra:"deepinfra",Hosted_Vllm:"hosted_vllm"},i="/ui/assets/logos/",o={"AI/ML API":"".concat(i,"aiml_api.svg"),Anthropic:"".concat(i,"anthropic.svg"),AssemblyAI:"".concat(i,"assemblyai_small.png"),Azure:"".concat(i,"microsoft_azure.svg"),"Azure AI Foundry (Studio)":"".concat(i,"microsoft_azure.svg"),"Amazon Bedrock":"".concat(i,"bedrock.svg"),"AWS SageMaker":"".concat(i,"bedrock.svg"),Cerebras:"".concat(i,"cerebras.svg"),Cohere:"".concat(i,"cohere.svg"),Databricks:"".concat(i,"databricks.svg"),Deepseek:"".concat(i,"deepseek.svg"),"Fireworks AI":"".concat(i,"fireworks.svg"),Groq:"".concat(i,"groq.svg"),"Google AI Studio":"".concat(i,"google.svg"),vllm:"".concat(i,"vllm.png"),"Mistral AI":"".concat(i,"mistral.svg"),Ollama:"".concat(i,"ollama.svg"),OpenAI:"".concat(i,"openai_small.svg"),"OpenAI Text Completion":"".concat(i,"openai_small.svg"),"OpenAI-Compatible Text Completion Models (Together AI, etc.)":"".concat(i,"openai_small.svg"),"OpenAI-Compatible Endpoints (Together AI, etc.)":"".concat(i,"openai_small.svg"),Openrouter:"".concat(i,"openrouter.svg"),Perplexity:"".concat(i,"perplexity-ai.svg"),Sambanova:"".concat(i,"sambanova.svg"),TogetherAI:"".concat(i,"togetherai.svg"),"Vertex AI (Anthropic, Gemini, etc.)":"".concat(i,"google.svg"),xAI:"".concat(i,"xai.svg"),GradientAI:"".concat(i,"gradientai.svg"),Triton:"".concat(i,"nvidia_triton.png"),Deepgram:"".concat(i,"deepgram.png"),ElevenLabs:"".concat(i,"elevenlabs.png"),"Voyage AI":"".concat(i,"voyage.webp"),"Jina AI":"".concat(i,"jina.png"),VolcEngine:"".concat(i,"volcengine.png"),DeepInfra:"".concat(i,"deepinfra.png")},l=e=>{if(!e)return{logo:"",displayName:"-"};if("gemini"===e.toLowerCase()){let e="Google AI Studio";return{logo:o[e],displayName:e}}let t=Object.keys(r).find(t=>r[t].toLowerCase()===e.toLowerCase());if(!t)return{logo:"",displayName:e};let n=a[t];return{logo:o[n],displayName:n}},c=e=>{if("AI/ML API"===e)return"aiml/flux-pro/v1.1";if("Vertex AI (Anthropic, Gemini, etc.)"===e)return"gemini-pro";if("Anthropic"==e||"Amazon Bedrock"==e)return"claude-3-opus";if("AWS SageMaker"==e)return"sagemaker/jumpstart-dft-meta-textgeneration-llama-2-7b";if("Google AI Studio"==e)return"gemini-pro";if("Azure AI Foundry (Studio)"==e)return"azure_ai/command-r-plus";else if("Azure"==e)return"azure/my-deployment";else if("Voyage AI"==e)return"voyage/";else if("Jina AI"==e)return"jina_ai/";else if("VolcEngine"==e)return"volcengine/";else if("DeepInfra"==e)return"deepinfra/";else return"gpt-3.5-turbo"},d=(e,t)=>{console.log("Provider key: ".concat(e));let n=r[e];console.log("Provider mapped to: ".concat(n));let a=[];return e&&"object"==typeof t&&(Object.entries(t).forEach(e=>{let[t,s]=e;null!==s&&"object"==typeof s&&"litellm_provider"in s&&(s.litellm_provider===n||s.litellm_provider.includes(n))&&a.push(t)}),"Cohere"==e&&(console.log("Adding cohere chat models"),Object.entries(t).forEach(e=>{let[t,n]=e;null!==n&&"object"==typeof n&&"litellm_provider"in n&&"cohere_chat"===n.litellm_provider&&a.push(t)})),"AWS SageMaker"==e&&(console.log("Adding sagemaker chat models"),Object.entries(t).forEach(e=>{let[t,n]=e;null!==n&&"object"==typeof n&&"litellm_provider"in n&&"sagemaker_chat"===n.litellm_provider&&a.push(t)}))),a}},72162:function(e,t,n){var a=n(57437),s=n(2265),r=n(19250),i=n(8048),o=n(36724),l=n(89970),c=n(3810),d=n(52787),m=n(82680),p=n(3477),u=n(17732),g=n(33245),x=n(78867),h=n(88658),f=n(49817),_=n(42673),b=n(65373),v=n(69734),j=n(9114);t.Z=e=>{var t,n;let{accessToken:y}=e,[N,w]=(0,s.useState)(null),[A,I]=(0,s.useState)("LiteLLM Gateway"),[S,k]=(0,s.useState)(null),[C,E]=(0,s.useState)(""),[M,O]=(0,s.useState)({}),[T,D]=(0,s.useState)(!0),[z,P]=(0,s.useState)(""),[L,Z]=(0,s.useState)([]),[G,R]=(0,s.useState)([]),[H,F]=(0,s.useState)([]),[K,V]=(0,s.useState)("I'm alive! ✓"),[U,W]=(0,s.useState)(!1),[q,J]=(0,s.useState)(null),[B,Y]=(0,s.useState)({}),$=(0,s.useRef)(null);(0,s.useEffect)(()=>{let e=async()=>{try{D(!0);let e=await (0,r.modelHubPublicModelsCall)();console.log("ModelHubData:",e),w(e)}catch(e){console.error("There was an error fetching the public model data",e),V("Service unavailable")}finally{D(!1)}};(async()=>{let e=await (0,r.getPublicModelHubInfo)();console.log("Public Model Hub Info:",e),I(e.docs_title),k(e.custom_docs_description),E(e.litellm_version),O(e.useful_links||{})})(),e()},[]),(0,s.useEffect)(()=>{},[z,L,G,H]);let X=(0,s.useMemo)(()=>{if(!N)return[];let e=N;if(z.trim()){let t=z.toLowerCase(),n=t.split(/\s+/),a=N.filter(e=>{let a=e.model_group.toLowerCase();return!!a.includes(t)||n.every(e=>a.includes(e))});a.length>0&&(e=a.sort((e,n)=>{let a=e.model_group.toLowerCase(),s=n.model_group.toLowerCase(),r=a===t?1e3:0,i=s===t?1e3:0,o=a.startsWith(t)?100:0,l=s.startsWith(t)?100:0,c=t.split(/\s+/).every(e=>a.includes(e))?50:0,d=t.split(/\s+/).every(e=>s.includes(e))?50:0,m=a.length;return i+l+d+(1e3-s.length)-(r+o+c+(1e3-m))}))}return e.filter(e=>{let t=0===L.length||L.some(t=>e.providers.includes(t)),n=0===G.length||G.includes(e.mode||""),a=0===H.length||Object.entries(e).filter(e=>{let[t,n]=e;return t.startsWith("supports_")&&!0===n}).some(e=>{let[t]=e,n=t.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ");return H.includes(n)});return t&&n&&a})},[N,z,L,G,H]),Q=e=>{J(e),W(!0)},ee=e=>{navigator.clipboard.writeText(e),j.Z.success("Copied to clipboard!")},et=e=>e.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" "),en=e=>Object.entries(e).filter(e=>{let[t,n]=e;return t.startsWith("supports_")&&!0===n}).map(e=>{let[t]=e;return t}),ea=e=>"$".concat((1e6*e).toFixed(4)),es=e=>e?e>=1e3?"".concat((e/1e3).toFixed(0),"K"):e.toString():"N/A",er=(e,t)=>{let n=[];return e&&n.push("RPM: ".concat(e.toLocaleString())),t&&n.push("TPM: ".concat(t.toLocaleString())),n.length>0?n.join(", "):"N/A"};return(0,a.jsx)(v.f,{accessToken:y,children:(0,a.jsxs)("div",{className:"min-h-screen bg-white",children:[(0,a.jsx)(b.Z,{userID:null,userEmail:null,userRole:null,premiumUser:!1,setProxySettings:Y,proxySettings:B,accessToken:y||null,isPublicPage:!0}),(0,a.jsxs)("div",{className:"w-full px-8 py-12",children:[(0,a.jsxs)(o.Zb,{className:"mb-10 p-8 bg-white border border-gray-200 rounded-lg shadow-sm",children:[(0,a.jsx)(o.Dx,{className:"text-2xl font-semibold mb-6 text-gray-900",children:"About"}),(0,a.jsx)("p",{className:"text-gray-700 mb-6 text-base leading-relaxed",children:S||"Proxy Server to call 100+ LLMs in the OpenAI format."}),(0,a.jsx)("div",{className:"flex items-center space-x-3 text-sm text-gray-600",children:(0,a.jsxs)("span",{className:"flex items-center",children:[(0,a.jsx)("span",{className:"w-4 h-4 mr-2",children:"\uD83D\uDD27"}),"Built with litellm: v",C]})})]}),M&&Object.keys(M).length>0&&(0,a.jsxs)(o.Zb,{className:"mb-10 p-8 bg-white border border-gray-200 rounded-lg shadow-sm",children:[(0,a.jsx)(o.Dx,{className:"text-2xl font-semibold mb-6 text-gray-900",children:"Useful Links"}),(0,a.jsx)("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6",children:Object.entries(M||{}).map(e=>{let[t,n]=e;return(0,a.jsxs)("button",{onClick:()=>window.open(n,"_blank"),className:"flex items-center space-x-3 text-blue-600 hover:text-blue-800 transition-colors p-3 rounded-lg hover:bg-blue-50 border border-gray-200",children:[(0,a.jsx)(p.Z,{className:"w-4 h-4"}),(0,a.jsx)(o.xv,{className:"text-sm font-medium",children:t})]},t)})})]}),(0,a.jsxs)(o.Zb,{className:"mb-10 p-8 bg-white border border-gray-200 rounded-lg shadow-sm",children:[(0,a.jsx)(o.Dx,{className:"text-2xl font-semibold mb-6 text-gray-900",children:"Health and Endpoint Status"}),(0,a.jsx)("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-6",children:(0,a.jsxs)(o.xv,{className:"text-green-600 font-medium text-sm",children:["Service status: ",K]})})]}),(0,a.jsxs)(o.Zb,{className:"p-8 bg-white border border-gray-200 rounded-lg shadow-sm",children:[(0,a.jsx)("div",{className:"flex justify-between items-center mb-8",children:(0,a.jsx)(o.Dx,{className:"text-2xl font-semibold text-gray-900",children:"Available Models"})}),(0,a.jsxs)("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8 p-6 bg-gray-50 rounded-lg border border-gray-200",children:[(0,a.jsxs)("div",{children:[(0,a.jsxs)("div",{className:"flex items-center space-x-2 mb-3",children:[(0,a.jsx)(o.xv,{className:"text-sm font-medium text-gray-700",children:"Search Models:"}),(0,a.jsx)(l.Z,{title:"Smart search with relevance ranking - finds models containing your search terms, ranked by relevance. Try searching 'xai grok-4', 'claude-4', 'gpt-4', or 'sonnet'",placement:"top",children:(0,a.jsx)(g.Z,{className:"w-4 h-4 text-gray-400 cursor-help"})})]}),(0,a.jsxs)("div",{className:"relative",children:[(0,a.jsx)(u.Z,{className:"w-4 h-4 text-gray-400 absolute left-3 top-1/2 transform -translate-y-1/2"}),(0,a.jsx)("input",{type:"text",placeholder:"Search model names... (smart search enabled)",value:z,onChange:e=>P(e.target.value),className:"border border-gray-300 rounded-lg pl-10 pr-4 py-2 w-full text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white"})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"text-sm font-medium mb-3 text-gray-700",children:"Provider:"}),(0,a.jsx)(d.default,{mode:"multiple",value:L,onChange:e=>Z(e),placeholder:"Select providers",className:"w-full",size:"large",allowClear:!0,optionRender:e=>{let{logo:t}=(0,_.dr)(e.value);return(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[t&&(0,a.jsx)("img",{src:t,alt:e.label,className:"w-5 h-5 flex-shrink-0 object-contain",onError:e=>{e.target.style.display="none"}}),(0,a.jsx)("span",{className:"capitalize",children:e.label})]})},children:N&&(e=>{let t=new Set;return e.forEach(e=>{e.providers.forEach(e=>t.add(e))}),Array.from(t)})(N).map(e=>(0,a.jsx)(d.default.Option,{value:e,children:e},e))})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"text-sm font-medium mb-3 text-gray-700",children:"Mode:"}),(0,a.jsx)(d.default,{mode:"multiple",value:G,onChange:e=>R(e),placeholder:"Select modes",className:"w-full",size:"large",allowClear:!0,children:N&&(e=>{let t=new Set;return e.forEach(e=>{e.mode&&t.add(e.mode)}),Array.from(t)})(N).map(e=>(0,a.jsx)(d.default.Option,{value:e,children:e},e))})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"text-sm font-medium mb-3 text-gray-700",children:"Features:"}),(0,a.jsx)(d.default,{mode:"multiple",value:H,onChange:e=>F(e),placeholder:"Select features",className:"w-full",size:"large",allowClear:!0,children:N&&(e=>{let t=new Set;return e.forEach(e=>{Object.entries(e).filter(e=>{let[t,n]=e;return t.startsWith("supports_")&&!0===n}).forEach(e=>{let[n]=e,a=n.replace(/^supports_/,"").split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ");t.add(a)})}),Array.from(t).sort()})(N).map(e=>(0,a.jsx)(d.default.Option,{value:e,children:e},e))})]})]}),(0,a.jsx)(i.C,{columns:[{header:"Model Name",accessorKey:"model_group",enableSorting:!0,cell:e=>{let{row:t}=e;return(0,a.jsx)("div",{className:"overflow-hidden",children:(0,a.jsx)(l.Z,{title:t.original.model_group,children:(0,a.jsx)(o.zx,{size:"xs",variant:"light",className:"font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left",onClick:()=>Q(t.original),children:t.original.model_group})})})},size:150},{header:"Providers",accessorKey:"providers",enableSorting:!0,cell:e=>{let{row:t}=e,n=t.original.providers;return(0,a.jsx)("div",{className:"flex flex-wrap gap-1",children:n.map(e=>{let{logo:t}=(0,_.dr)(e);return(0,a.jsxs)("div",{className:"flex items-center space-x-1 px-2 py-1 bg-gray-100 rounded text-xs",children:[t&&(0,a.jsx)("img",{src:t,alt:e,className:"w-3 h-3 flex-shrink-0 object-contain",onError:e=>{e.target.style.display="none"}}),(0,a.jsx)("span",{className:"capitalize",children:e})]},e)})})},size:120},{header:"Mode",accessorKey:"mode",enableSorting:!0,cell:e=>{let{row:t}=e,n=t.original.mode;return(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)("span",{children:(e=>{switch(null==e?void 0:e.toLowerCase()){case"chat":return"\uD83D\uDCAC";case"rerank":return"\uD83D\uDD04";case"embedding":return"\uD83D\uDCC4";default:return"\uD83E\uDD16"}})(n||"")}),(0,a.jsx)(o.xv,{children:n||"Chat"})]})},size:100},{header:"Max Input",accessorKey:"max_input_tokens",enableSorting:!0,cell:e=>{let{row:t}=e;return(0,a.jsx)(o.xv,{className:"text-center",children:es(t.original.max_input_tokens)})},size:100,meta:{className:"text-center"}},{header:"Max Output",accessorKey:"max_output_tokens",enableSorting:!0,cell:e=>{let{row:t}=e;return(0,a.jsx)(o.xv,{className:"text-center",children:es(t.original.max_output_tokens)})},size:100,meta:{className:"text-center"}},{header:"Input $/1M",accessorKey:"input_cost_per_token",enableSorting:!0,cell:e=>{let{row:t}=e,n=t.original.input_cost_per_token;return(0,a.jsx)(o.xv,{className:"text-center",children:n?ea(n):"Free"})},size:100,meta:{className:"text-center"}},{header:"Output $/1M",accessorKey:"output_cost_per_token",enableSorting:!0,cell:e=>{let{row:t}=e,n=t.original.output_cost_per_token;return(0,a.jsx)(o.xv,{className:"text-center",children:n?ea(n):"Free"})},size:100,meta:{className:"text-center"}},{header:"Features",accessorKey:"supports_vision",enableSorting:!1,cell:e=>{let{row:t}=e,n=Object.entries(t.original).filter(e=>{let[t,n]=e;return t.startsWith("supports_")&&!0===n}).map(e=>{let[t]=e;return et(t)});return 0===n.length?(0,a.jsx)(o.xv,{className:"text-gray-400",children:"-"}):1===n.length?(0,a.jsx)("div",{className:"h-6 flex items-center",children:(0,a.jsx)(c.Z,{color:"blue",className:"text-xs",children:n[0]})}):(0,a.jsxs)("div",{className:"h-6 flex items-center space-x-1",children:[(0,a.jsx)(c.Z,{color:"blue",className:"text-xs",children:n[0]}),(0,a.jsx)(l.Z,{title:(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsx)("div",{className:"font-medium",children:"All Features:"}),n.map((e,t)=>(0,a.jsxs)("div",{className:"text-xs",children:["• ",e]},t))]}),trigger:"click",placement:"topLeft",children:(0,a.jsxs)("span",{className:"text-xs text-blue-600 cursor-pointer hover:text-blue-800 hover:underline",onClick:e=>e.stopPropagation(),children:["+",n.length-1]})})]})},size:120},{header:"Limits",accessorKey:"rpm",enableSorting:!0,cell:e=>{let{row:t}=e,n=t.original;return(0,a.jsx)(o.xv,{className:"text-xs text-gray-600",children:er(n.rpm,n.tpm)})},size:150}],data:X,isLoading:T,table:$,defaultSorting:[{id:"model_group",desc:!1}]}),(0,a.jsx)("div",{className:"mt-8 text-center",children:(0,a.jsxs)(o.xv,{className:"text-sm text-gray-600",children:["Showing ",X.length," of ",(null==N?void 0:N.length)||0," models"]})})]})]}),(0,a.jsx)(m.Z,{title:(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)("span",{children:(null==q?void 0:q.model_group)||"Model Details"}),q&&(0,a.jsx)(l.Z,{title:"Copy model name",children:(0,a.jsx)(x.Z,{onClick:()=>ee(q.model_group),className:"cursor-pointer text-gray-500 hover:text-blue-500 w-4 h-4"})})]}),width:1e3,open:U,footer:null,onOk:()=>{W(!1),J(null)},onCancel:()=>{W(!1),J(null)},children:q&&(0,a.jsxs)("div",{className:"space-y-6",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"text-lg font-semibold mb-4",children:"Model Overview"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4 mb-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"font-medium",children:"Model Name:"}),(0,a.jsx)(o.xv,{children:q.model_group})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"font-medium",children:"Mode:"}),(0,a.jsx)(o.xv,{children:q.mode||"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"font-medium",children:"Providers:"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1 mt-1",children:q.providers.map(e=>{let{logo:t}=(0,_.dr)(e);return(0,a.jsx)(c.Z,{color:"blue",children:(0,a.jsxs)("div",{className:"flex items-center space-x-1",children:[t&&(0,a.jsx)("img",{src:t,alt:e,className:"w-3 h-3 flex-shrink-0 object-contain",onError:e=>{e.target.style.display="none"}}),(0,a.jsx)("span",{className:"capitalize",children:e})]})},e)})})]})]}),q.model_group.includes("*")&&(0,a.jsx)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4",children:(0,a.jsxs)("div",{className:"flex items-start space-x-2",children:[(0,a.jsx)(g.Z,{className:"w-4 h-4 text-blue-600 mt-0.5 flex-shrink-0"}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"font-medium text-blue-900 mb-2",children:"Wildcard Routing"}),(0,a.jsxs)(o.xv,{className:"text-sm text-blue-800 mb-2",children:["This model uses wildcard routing. You can pass any value where you see the ",(0,a.jsx)("code",{className:"bg-blue-100 px-1 py-0.5 rounded text-xs",children:"*"})," symbol."]}),(0,a.jsxs)(o.xv,{className:"text-sm text-blue-800",children:["For example, with ",(0,a.jsx)("code",{className:"bg-blue-100 px-1 py-0.5 rounded text-xs",children:q.model_group}),", you can use any string (",(0,a.jsx)("code",{className:"bg-blue-100 px-1 py-0.5 rounded text-xs",children:q.model_group.replace("*","my-custom-value")}),") that matches this pattern."]})]})]})})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"text-lg font-semibold mb-4",children:"Token & Cost Information"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"font-medium",children:"Max Input Tokens:"}),(0,a.jsx)(o.xv,{children:(null===(t=q.max_input_tokens)||void 0===t?void 0:t.toLocaleString())||"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"font-medium",children:"Max Output Tokens:"}),(0,a.jsx)(o.xv,{children:(null===(n=q.max_output_tokens)||void 0===n?void 0:n.toLocaleString())||"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"font-medium",children:"Input Cost per 1M Tokens:"}),(0,a.jsx)(o.xv,{children:q.input_cost_per_token?ea(q.input_cost_per_token):"Not specified"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"font-medium",children:"Output Cost per 1M Tokens:"}),(0,a.jsx)(o.xv,{children:q.output_cost_per_token?ea(q.output_cost_per_token):"Not specified"})]})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"text-lg font-semibold mb-4",children:"Capabilities"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:(()=>{let e=en(q),t=["green","blue","purple","orange","red","yellow"];return 0===e.length?(0,a.jsx)(o.xv,{className:"text-gray-500",children:"No special capabilities listed"}):e.map((e,n)=>(0,a.jsx)(c.Z,{color:t[n%t.length],children:et(e)},e))})()})]}),(q.tpm||q.rpm)&&(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"text-lg font-semibold mb-4",children:"Rate Limits"}),(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[q.tpm&&(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"font-medium",children:"Tokens per Minute:"}),(0,a.jsx)(o.xv,{children:q.tpm.toLocaleString()})]}),q.rpm&&(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"font-medium",children:"Requests per Minute:"}),(0,a.jsx)(o.xv,{children:q.rpm.toLocaleString()})]})]})]}),q.supported_openai_params&&(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"text-lg font-semibold mb-4",children:"Supported OpenAI Parameters"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:q.supported_openai_params.map(e=>(0,a.jsx)(c.Z,{color:"green",children:e},e))})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(o.xv,{className:"text-lg font-semibold mb-4",children:"Usage Example"}),(0,a.jsx)("div",{className:"bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto",children:(0,a.jsx)("pre",{className:"text-sm",children:(0,h.L)({apiKeySource:"custom",accessToken:null,apiKey:"your_api_key",inputMessage:"Hello, how are you?",chatHistory:[{role:"user",content:"Hello, how are you?",isImage:!1}],selectedTags:[],selectedVectorStores:[],selectedGuardrails:[],endpointType:(0,f.vf)(q.mode||"chat"),selectedModel:q.model_group,selectedSdk:"openai"})})}),(0,a.jsx)("div",{className:"mt-2 text-right",children:(0,a.jsx)("button",{onClick:()=>{ee((0,h.L)({apiKeySource:"custom",accessToken:null,apiKey:"your_api_key",inputMessage:"Hello, how are you?",chatHistory:[{role:"user",content:"Hello, how are you?",isImage:!1}],selectedTags:[],selectedVectorStores:[],selectedGuardrails:[],endpointType:(0,f.vf)(q.mode||"chat"),selectedModel:q.model_group,selectedSdk:"openai"}))},className:"text-sm text-blue-600 hover:text-blue-800 cursor-pointer",children:"Copy to clipboard"})})]})]})})]})})}},69734:function(e,t,n){n.d(t,{F:function(){return o},f:function(){return l}});var a=n(57437),s=n(2265),r=n(19250);let i=(0,s.createContext)(void 0),o=()=>{let e=(0,s.useContext)(i);if(!e)throw Error("useTheme must be used within a ThemeProvider");return e},l=e=>{let{children:t,accessToken:n}=e,[o,l]=(0,s.useState)(null);return(0,s.useEffect)(()=>{(async()=>{if(n)try{let t=(0,r.getProxyBaseUrl)(),a=await fetch(t?"".concat(t,"/get/ui_theme_settings"):"/get/ui_theme_settings",{method:"GET",headers:{Authorization:"Bearer ".concat(n),"Content-Type":"application/json"}});if(a.ok){var e;let t=await a.json();(null===(e=t.values)||void 0===e?void 0:e.logo_url)&&l(t.values.logo_url)}}catch(e){console.warn("Failed to load logo settings from backend:",e)}})()},[n]),(0,a.jsx)(i.Provider,{value:{logoUrl:o,setLogoUrl:l},children:t})}}}]); \ No newline at end of file diff --git a/ui/litellm-dashboard/out/_next/static/chunks/220-1c8d82f7ce7658c4.js b/litellm/proxy/_experimental/out/_next/static/chunks/220-5061c4cea850d728.js similarity index 71% rename from ui/litellm-dashboard/out/_next/static/chunks/220-1c8d82f7ce7658c4.js rename to litellm/proxy/_experimental/out/_next/static/chunks/220-5061c4cea850d728.js index 206acc40d24..870b249e205 100644 --- a/ui/litellm-dashboard/out/_next/static/chunks/220-1c8d82f7ce7658c4.js +++ b/litellm/proxy/_experimental/out/_next/static/chunks/220-5061c4cea850d728.js @@ -1,12 +1,12 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[220],{12660:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M917.7 148.8l-42.4-42.4c-1.6-1.6-3.6-2.3-5.7-2.3s-4.1.8-5.7 2.3l-76.1 76.1a199.27 199.27 0 00-112.1-34.3c-51.2 0-102.4 19.5-141.5 58.6L432.3 308.7a8.03 8.03 0 000 11.3L704 591.7c1.6 1.6 3.6 2.3 5.7 2.3 2 0 4.1-.8 5.7-2.3l101.9-101.9c68.9-69 77-175.7 24.3-253.5l76.1-76.1c3.1-3.2 3.1-8.3 0-11.4zM769.1 441.7l-59.4 59.4-186.8-186.8 59.4-59.4c24.9-24.9 58.1-38.7 93.4-38.7 35.3 0 68.4 13.7 93.4 38.7 24.9 24.9 38.7 58.1 38.7 93.4 0 35.3-13.8 68.4-38.7 93.4zm-190.2 105a8.03 8.03 0 00-11.3 0L501 613.3 410.7 523l66.7-66.7c3.1-3.1 3.1-8.2 0-11.3L441 408.6a8.03 8.03 0 00-11.3 0L363 475.3l-43-43a7.85 7.85 0 00-5.7-2.3c-2 0-4.1.8-5.7 2.3L206.8 534.2c-68.9 69-77 175.7-24.3 253.5l-76.1 76.1a8.03 8.03 0 000 11.3l42.4 42.4c1.6 1.6 3.6 2.3 5.7 2.3s4.1-.8 5.7-2.3l76.1-76.1c33.7 22.9 72.9 34.3 112.1 34.3 51.2 0 102.4-19.5 141.5-58.6l101.9-101.9c3.1-3.1 3.1-8.2 0-11.3l-43-43 66.7-66.7c3.1-3.1 3.1-8.2 0-11.3l-36.6-36.2zM441.7 769.1a131.32 131.32 0 01-93.4 38.7c-35.3 0-68.4-13.7-93.4-38.7a131.32 131.32 0 01-38.7-93.4c0-35.3 13.7-68.4 38.7-93.4l59.4-59.4 186.8 186.8-59.4 59.4z"}}]},name:"api",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},88009:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M464 144H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H212V212h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H612V212h200v200zM464 544H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H212V612h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H612V612h200v200z"}}]},name:"appstore",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},79276:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M868 545.5L536.1 163a31.96 31.96 0 00-48.3 0L156 545.5a7.97 7.97 0 006 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"}}]},name:"arrow-up",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},37527:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M894 462c30.9 0 43.8-39.7 18.7-58L530.8 126.2a31.81 31.81 0 00-37.6 0L111.3 404c-25.1 18.2-12.2 58 18.8 58H192v374h-72c-4.4 0-8 3.6-8 8v52c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-52c0-4.4-3.6-8-8-8h-72V462h62zM512 196.7l271.1 197.2H240.9L512 196.7zM264 462h117v374H264V462zm189 0h117v374H453V462zm307 374H642V462h118v374z"}}]},name:"bank",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},9775:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M888 792H200V168c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v688c0 4.4 3.6 8 8 8h752c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-600-80h56c4.4 0 8-3.6 8-8V560c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v144c0 4.4 3.6 8 8 8zm152 0h56c4.4 0 8-3.6 8-8V384c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v320c0 4.4 3.6 8 8 8zm152 0h56c4.4 0 8-3.6 8-8V462c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v242c0 4.4 3.6 8 8 8zm152 0h56c4.4 0 8-3.6 8-8V304c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v400c0 4.4 3.6 8 8 8z"}}]},name:"bar-chart",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},11429:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M766.4 744.3c43.7 0 79.4-36.2 79.4-80.5 0-53.5-79.4-140.8-79.4-140.8S687 610.3 687 663.8c0 44.3 35.7 80.5 79.4 80.5zm-377.1-44.1c7.1 7.1 18.6 7.1 25.6 0l256.1-256c7.1-7.1 7.1-18.6 0-25.6l-256-256c-.6-.6-1.3-1.2-2-1.7l-78.2-78.2a9.11 9.11 0 00-12.8 0l-48 48a9.11 9.11 0 000 12.8l67.2 67.2-207.8 207.9c-7.1 7.1-7.1 18.6 0 25.6l255.9 256zm12.9-448.6l178.9 178.9H223.4l178.8-178.9zM904 816H120c-4.4 0-8 3.6-8 8v80c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-80c0-4.4-3.6-8-8-8z"}}]},name:"bg-colors",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},68208:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M856 376H648V168c0-8.8-7.2-16-16-16H168c-8.8 0-16 7.2-16 16v464c0 8.8 7.2 16 16 16h208v208c0 8.8 7.2 16 16 16h464c8.8 0 16-7.2 16-16V392c0-8.8-7.2-16-16-16zm-480 16v188H220V220h360v156H392c-8.8 0-16 7.2-16 16zm204 52v136H444V444h136zm224 360H444V648h188c8.8 0 16-7.2 16-16V444h156v360z"}}]},name:"block",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},83322:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M632 888H392c-4.4 0-8 3.6-8 8v32c0 17.7 14.3 32 32 32h192c17.7 0 32-14.3 32-32v-32c0-4.4-3.6-8-8-8zM512 64c-181.1 0-328 146.9-328 328 0 121.4 66 227.4 164 284.1V792c0 17.7 14.3 32 32 32h264c17.7 0 32-14.3 32-32V676.1c98-56.7 164-162.7 164-284.1 0-181.1-146.9-328-328-328zm127.9 549.8L604 634.6V752H420V634.6l-35.9-20.8C305.4 568.3 256 484.5 256 392c0-141.4 114.6-256 256-256s256 114.6 256 256c0 92.5-49.4 176.3-128.1 221.8z"}}]},name:"bulb",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},49634:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"}}]},name:"calendar",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},83669:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M699 353h-46.9c-10.2 0-19.9 4.9-25.9 13.3L469 584.3l-71.2-98.8c-6-8.3-15.6-13.3-25.9-13.3H325c-6.5 0-10.3 7.4-6.5 12.7l124.6 172.8a31.8 31.8 0 0051.7 0l210.6-292c3.9-5.3.1-12.7-6.4-12.7z"}},{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}}]},name:"check-circle",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},26430:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"defs",attrs:{},children:[{tag:"style",attrs:{}}]},{tag:"path",attrs:{d:"M899.1 869.6l-53-305.6H864c14.4 0 26-11.6 26-26V346c0-14.4-11.6-26-26-26H618V138c0-14.4-11.6-26-26-26H432c-14.4 0-26 11.6-26 26v182H160c-14.4 0-26 11.6-26 26v192c0 14.4 11.6 26 26 26h17.9l-53 305.6a25.95 25.95 0 0025.6 30.4h723c1.5 0 3-.1 4.4-.4a25.88 25.88 0 0021.2-30zM204 390h272V182h72v208h272v104H204V390zm468 440V674c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v156H416V674c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v156H202.8l45.1-260H776l45.1 260H672z"}}]},name:"clear",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},5540:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}},{tag:"path",attrs:{d:"M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8H488c-4.4 0-8 3.6-8 8v275.4c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.8 11.2-1.7l28.6-39c2.6-3.7 1.8-8.7-1.8-11.2z"}}]},name:"clock-circle",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},11894:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M516 673c0 4.4 3.4 8 7.5 8h185c4.1 0 7.5-3.6 7.5-8v-48c0-4.4-3.4-8-7.5-8h-185c-4.1 0-7.5 3.6-7.5 8v48zm-194.9 6.1l192-161c3.8-3.2 3.8-9.1 0-12.3l-192-160.9A7.95 7.95 0 00308 351v62.7c0 2.4 1 4.6 2.9 6.1L420.7 512l-109.8 92.2a8.1 8.1 0 00-2.9 6.1V673c0 6.8 7.9 10.5 13.1 6.1zM880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z"}}]},name:"code",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},44625:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-600 72h560v208H232V136zm560 480H232V408h560v208zm0 272H232V680h560v208zM304 240a40 40 0 1080 0 40 40 0 10-80 0zm0 272a40 40 0 1080 0 40 40 0 10-80 0zm0 272a40 40 0 1080 0 40 40 0 10-80 0z"}}]},name:"database",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},26349:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"}}]},name:"delete",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},62670:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372zm47.7-395.2l-25.4-5.9V348.6c38 5.2 61.5 29 65.5 58.2.5 4 3.9 6.9 7.9 6.9h44.9c4.7 0 8.4-4.1 8-8.8-6.1-62.3-57.4-102.3-125.9-109.2V263c0-4.4-3.6-8-8-8h-28.1c-4.4 0-8 3.6-8 8v33c-70.8 6.9-126.2 46-126.2 119 0 67.6 49.8 100.2 102.1 112.7l24.7 6.3v142.7c-44.2-5.9-69-29.5-74.1-61.3-.6-3.8-4-6.6-7.9-6.6H363c-4.7 0-8.4 4-8 8.7 4.5 55 46.2 105.6 135.2 112.1V761c0 4.4 3.6 8 8 8h28.4c4.4 0 8-3.6 8-8.1l-.2-31.7c78.3-6.9 134.3-48.8 134.3-124-.1-69.4-44.2-100.4-109-116.4zm-68.6-16.2c-5.6-1.6-10.3-3.1-15-5-33.8-12.2-49.5-31.9-49.5-57.3 0-36.3 27.5-57 64.5-61.7v124zM534.3 677V543.3c3.1.9 5.9 1.6 8.8 2.2 47.3 14.4 63.2 34.4 63.2 65.1 0 39.1-29.4 62.6-72 66.4z"}}]},name:"dollar",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},73879:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"}}]},name:"download",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},29271:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}},{tag:"path",attrs:{d:"M464 688a48 48 0 1096 0 48 48 0 10-96 0zm24-112h48c4.4 0 8-3.6 8-8V296c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8z"}}]},name:"exclamation-circle",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},41169:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 472a40 40 0 1080 0 40 40 0 10-80 0zm367 352.9L696.3 352V178H768v-68H256v68h71.7v174L145 824.9c-2.8 7.4-4.3 15.2-4.3 23.1 0 35.3 28.7 64 64 64h614.6c7.9 0 15.7-1.5 23.1-4.3 33-12.7 49.4-49.8 36.6-82.8zM395.7 364.7V180h232.6v184.7L719.2 600c-20.7-5.3-42.1-8-63.9-8-61.2 0-119.2 21.5-165.3 60a188.78 188.78 0 01-121.3 43.9c-32.7 0-64.1-8.3-91.8-23.7l118.8-307.5zM210.5 844l41.7-107.8c35.7 18.1 75.4 27.8 116.6 27.8 61.2 0 119.2-21.5 165.3-60 33.9-28.2 76.3-43.9 121.3-43.9 35 0 68.4 9.5 97.6 27.1L813.5 844h-603z"}}]},name:"experiment",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},11741:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{"fill-rule":"evenodd",viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M880 912H144c-17.7 0-32-14.3-32-32V144c0-17.7 14.3-32 32-32h360c4.4 0 8 3.6 8 8v56c0 4.4-3.6 8-8 8H184v656h656V520c0-4.4 3.6-8 8-8h56c4.4 0 8 3.6 8 8v360c0 17.7-14.3 32-32 32zM770.87 199.13l-52.2-52.2a8.01 8.01 0 014.7-13.6l179.4-21c5.1-.6 9.5 3.7 8.9 8.9l-21 179.4c-.8 6.6-8.9 9.4-13.6 4.7l-52.4-52.4-256.2 256.2a8.03 8.03 0 01-11.3 0l-42.4-42.4a8.03 8.03 0 010-11.3l256.1-256.3z"}}]},name:"export",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},34310:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM472 744a40 40 0 1080 0 40 40 0 10-80 0zm16-104h48c4.4 0 8-3.6 8-8V448c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v184c0 4.4 3.6 8 8 8z"}}]},name:"file-exclamation",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},50010:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M531.3 574.4l.3-1.4c5.8-23.9 13.1-53.7 7.4-80.7-3.8-21.3-19.5-29.6-32.9-30.2-15.8-.7-29.9 8.3-33.4 21.4-6.6 24-.7 56.8 10.1 98.6-13.6 32.4-35.3 79.5-51.2 107.5-29.6 15.3-69.3 38.9-75.2 68.7-1.2 5.5.2 12.5 3.5 18.8 3.7 7 9.6 12.4 16.5 15 3 1.1 6.6 2 10.8 2 17.6 0 46.1-14.2 84.1-79.4 5.8-1.9 11.8-3.9 17.6-5.9 27.2-9.2 55.4-18.8 80.9-23.1 28.2 15.1 60.3 24.8 82.1 24.8 21.6 0 30.1-12.8 33.3-20.5 5.6-13.5 2.9-30.5-6.2-39.6-13.2-13-45.3-16.4-95.3-10.2-24.6-15-40.7-35.4-52.4-65.8zM421.6 726.3c-13.9 20.2-24.4 30.3-30.1 34.7 6.7-12.3 19.8-25.3 30.1-34.7zm87.6-235.5c5.2 8.9 4.5 35.8.5 49.4-4.9-19.9-5.6-48.1-2.7-51.4.8.1 1.5.7 2.2 2zm-1.6 120.5c10.7 18.5 24.2 34.4 39.1 46.2-21.6 4.9-41.3 13-58.9 20.2-4.2 1.7-8.3 3.4-12.3 5 13.3-24.1 24.4-51.4 32.1-71.4zm155.6 65.5c.1.2.2.5-.4.9h-.2l-.2.3c-.8.5-9 5.3-44.3-8.6 40.6-1.9 45 7.3 45.1 7.4zm191.4-388.2L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494z"}}]},name:"file-pdf",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},38434:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z"}}]},name:"file-text",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},10798:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M880.1 154H143.9c-24.5 0-39.8 26.7-27.5 48L349 597.4V838c0 17.7 14.2 32 31.8 32h262.4c17.6 0 31.8-14.3 31.8-32V597.4L907.7 202c12.2-21.3-3.1-48-27.6-48zM603.4 798H420.6V642h182.9v156zm9.6-236.6l-9.5 16.6h-183l-9.5-16.6L212.7 226h598.6L613 561.4z"}}]},name:"filter",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},71282:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{"fill-rule":"evenodd",viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M880 912H144c-17.7 0-32-14.3-32-32V144c0-17.7 14.3-32 32-32h360c4.4 0 8 3.6 8 8v56c0 4.4-3.6 8-8 8H184v656h656V520c0-4.4 3.6-8 8-8h56c4.4 0 8 3.6 8 8v360c0 17.7-14.3 32-32 32zM653.3 424.6l52.2 52.2a8.01 8.01 0 01-4.7 13.6l-179.4 21c-5.1.6-9.5-3.7-8.9-8.9l21-179.4c.8-6.6 8.9-9.4 13.6-4.7l52.4 52.4 256.2-256.2c3.1-3.1 8.2-3.1 11.3 0l42.4 42.4c3.1 3.1 3.1 8.2 0 11.3L653.3 424.6z"}}]},name:"import",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},92403:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M608 112c-167.9 0-304 136.1-304 304 0 70.3 23.9 135 63.9 186.5l-41.1 41.1-62.3-62.3a8.15 8.15 0 00-11.4 0l-39.8 39.8a8.15 8.15 0 000 11.4l62.3 62.3-44.9 44.9-62.3-62.3a8.15 8.15 0 00-11.4 0l-39.8 39.8a8.15 8.15 0 000 11.4l62.3 62.3-65.3 65.3a8.03 8.03 0 000 11.3l42.3 42.3c3.1 3.1 8.2 3.1 11.3 0l253.6-253.6A304.06 304.06 0 00608 720c167.9 0 304-136.1 304-304S775.9 112 608 112zm161.2 465.2C726.2 620.3 668.9 644 608 644c-60.9 0-118.2-23.7-161.2-66.8-43.1-43-66.8-100.3-66.8-161.2 0-60.9 23.7-118.2 66.8-161.2 43-43.1 100.3-66.8 161.2-66.8 60.9 0 118.2 23.7 161.2 66.8 43.1 43 66.8 100.3 66.8 161.2 0 60.9-23.7 118.2-66.8 161.2z"}}]},name:"key",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},48231:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M888 792H200V168c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v688c0 4.4 3.6 8 8 8h752c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM305.8 637.7c3.1 3.1 8.1 3.1 11.3 0l138.3-137.6L583 628.5c3.1 3.1 8.2 3.1 11.3 0l275.4-275.3c3.1-3.1 3.1-8.2 0-11.3l-39.6-39.6a8.03 8.03 0 00-11.3 0l-230 229.9L461.4 404a8.03 8.03 0 00-11.3 0L266.3 586.7a8.03 8.03 0 000 11.3l39.5 39.7z"}}]},name:"line-chart",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},62272:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M574 665.4a8.03 8.03 0 00-11.3 0L446.5 781.6c-53.8 53.8-144.6 59.5-204 0-59.5-59.5-53.8-150.2 0-204l116.2-116.2c3.1-3.1 3.1-8.2 0-11.3l-39.8-39.8a8.03 8.03 0 00-11.3 0L191.4 526.5c-84.6 84.6-84.6 221.5 0 306s221.5 84.6 306 0l116.2-116.2c3.1-3.1 3.1-8.2 0-11.3L574 665.4zm258.6-474c-84.6-84.6-221.5-84.6-306 0L410.3 307.6a8.03 8.03 0 000 11.3l39.7 39.7c3.1 3.1 8.2 3.1 11.3 0l116.2-116.2c53.8-53.8 144.6-59.5 204 0 59.5 59.5 53.8 150.2 0 204L665.3 562.6a8.03 8.03 0 000 11.3l39.8 39.8c3.1 3.1 8.2 3.1 11.3 0l116.2-116.2c84.5-84.6 84.5-221.5 0-306.1zM610.1 372.3a8.03 8.03 0 00-11.3 0L372.3 598.7a8.03 8.03 0 000 11.3l39.6 39.6c3.1 3.1 8.2 3.1 11.3 0l226.4-226.4c3.1-3.1 3.1-8.2 0-11.3l-39.5-39.6z"}}]},name:"link",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},45246:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M696 480H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h368c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z"}},{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}}]},name:"minus-circle",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},16601:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M872 394c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8H708V152c0-4.4-3.6-8-8-8h-64c-4.4 0-8 3.6-8 8v166H400V152c0-4.4-3.6-8-8-8h-64c-4.4 0-8 3.6-8 8v166H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h168v236H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h168v166c0 4.4 3.6 8 8 8h64c4.4 0 8-3.6 8-8V706h228v166c0 4.4 3.6 8 8 8h64c4.4 0 8-3.6 8-8V706h164c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8H708V394h164zM628 630H400V394h228v236z"}}]},name:"number",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},53508:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M779.3 196.6c-94.2-94.2-247.6-94.2-341.7 0l-261 260.8c-1.7 1.7-2.6 4-2.6 6.4s.9 4.7 2.6 6.4l36.9 36.9a9 9 0 0012.7 0l261-260.8c32.4-32.4 75.5-50.2 121.3-50.2s88.9 17.8 121.2 50.2c32.4 32.4 50.2 75.5 50.2 121.2 0 45.8-17.8 88.8-50.2 121.2l-266 265.9-43.1 43.1c-40.3 40.3-105.8 40.3-146.1 0-19.5-19.5-30.2-45.4-30.2-73s10.7-53.5 30.2-73l263.9-263.8c6.7-6.6 15.5-10.3 24.9-10.3h.1c9.4 0 18.1 3.7 24.7 10.3 6.7 6.7 10.3 15.5 10.3 24.9 0 9.3-3.7 18.1-10.3 24.7L372.4 653c-1.7 1.7-2.6 4-2.6 6.4s.9 4.7 2.6 6.4l36.9 36.9a9 9 0 0012.7 0l215.6-215.6c19.9-19.9 30.8-46.3 30.8-74.4s-11-54.6-30.8-74.4c-41.1-41.1-107.9-41-149 0L463 364 224.8 602.1A172.22 172.22 0 00174 724.8c0 46.3 18.1 89.8 50.8 122.5 33.9 33.8 78.3 50.7 122.7 50.7 44.4 0 88.8-16.9 122.6-50.7l309.2-309C824.8 492.7 850 432 850 367.5c.1-64.6-25.1-125.3-70.7-170.9z"}}]},name:"paper-clip",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},99890:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32zm-40 632H136v-39.9l138.5-164.3 150.1 178L658.1 489 888 761.6V792zm0-129.8L664.2 396.8c-3.2-3.8-9-3.8-12.2 0L424.6 666.4l-144-170.7c-3.2-3.8-9-3.8-12.2 0L136 652.7V232h752v430.2zM304 456a88 88 0 100-176 88 88 0 000 176zm0-116c15.5 0 28 12.5 28 28s-12.5 28-28 28-28-12.5-28-28 12.5-28 28-28z"}}]},name:"picture",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},28595:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}},{tag:"path",attrs:{d:"M719.4 499.1l-296.1-215A15.9 15.9 0 00398 297v430c0 13.1 14.8 20.5 25.3 12.9l296.1-215a15.9 15.9 0 000-25.8zm-257.6 134V390.9L628.5 512 461.8 633.1z"}}]},name:"play-circle",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},34419:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M696 480H544V328c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v152H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h152v152c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V544h152c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z"}},{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}}]},name:"plus-circle",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},96473:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"}},{tag:"path",attrs:{d:"M192 474h672q8 0 8 8v60q0 8-8 8H160q-8 0-8-8v-60q0-8 8-8z"}}]},name:"plus",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},89245:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M909.1 209.3l-56.4 44.1C775.8 155.1 656.2 92 521.9 92 290 92 102.3 279.5 102 511.5 101.7 743.7 289.8 932 521.9 932c181.3 0 335.8-115 394.6-276.1 1.5-4.2-.7-8.9-4.9-10.3l-56.7-19.5a8 8 0 00-10.1 4.8c-1.8 5-3.8 10-5.9 14.9-17.3 41-42.1 77.8-73.7 109.4A344.77 344.77 0 01655.9 829c-42.3 17.9-87.4 27-133.8 27-46.5 0-91.5-9.1-133.8-27A341.5 341.5 0 01279 755.2a342.16 342.16 0 01-73.7-109.4c-17.9-42.4-27-87.4-27-133.9s9.1-91.5 27-133.9c17.3-41 42.1-77.8 73.7-109.4 31.6-31.6 68.4-56.4 109.3-73.8 42.3-17.9 87.4-27 133.8-27 46.5 0 91.5 9.1 133.8 27a341.5 341.5 0 01109.3 73.8c9.9 9.9 19.2 20.4 27.8 31.4l-60.2 47a8 8 0 003 14.1l175.6 43c5 1.2 9.9-2.6 9.9-7.7l.8-180.9c-.1-6.6-7.8-10.3-13-6.2z"}}]},name:"reload",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},69993:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M300 328a60 60 0 10120 0 60 60 0 10-120 0zM852 64H172c-17.7 0-32 14.3-32 32v660c0 17.7 14.3 32 32 32h680c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-32 660H204V128h616v596zM604 328a60 60 0 10120 0 60 60 0 10-120 0zm250.2 556H169.8c-16.5 0-29.8 14.3-29.8 32v36c0 4.4 3.3 8 7.4 8h729.1c4.1 0 7.4-3.6 7.4-8v-36c.1-17.7-13.2-32-29.7-32zM664 508H360c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h304c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"}}]},name:"robot",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},78355:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M893.3 293.3L730.7 130.7c-7.5-7.5-16.7-13-26.7-16V112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V338.5c0-17-6.7-33.2-18.7-45.2zM384 184h256v104H384V184zm456 656H184V184h136v136c0 17.7 14.3 32 32 32h320c17.7 0 32-14.3 32-32V205.8l136 136V840zM512 442c-79.5 0-144 64.5-144 144s64.5 144 144 144 144-64.5 144-144-64.5-144-144-144zm0 224c-44.2 0-80-35.8-80-80s35.8-80 80-80 80 35.8 80 80-35.8 80-80 80z"}}]},name:"save",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},23907:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"defs",attrs:{},children:[{tag:"style",attrs:{}}]},{tag:"path",attrs:{d:"M931.4 498.9L94.9 79.5c-3.4-1.7-7.3-2.1-11-1.2a15.99 15.99 0 00-11.7 19.3l86.2 352.2c1.3 5.3 5.2 9.6 10.4 11.3l147.7 50.7-147.6 50.7c-5.2 1.8-9.1 6-10.3 11.3L72.2 926.5c-.9 3.7-.5 7.6 1.2 10.9 3.9 7.9 13.5 11.1 21.5 7.2l836.5-417c3.1-1.5 5.6-4.1 7.2-7.1 3.9-8 .7-17.6-7.2-21.6zM170.8 826.3l50.3-205.6 295.2-101.3c2.3-.8 4.2-2.6 5-5 1.4-4.2-.8-8.7-5-10.2L221.1 403 171 198.2l628 314.9-628.2 313.2z"}}]},name:"send",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},55322:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56a32.03 32.03 0 009.3-35.2l-.9-2.6a443.74 443.74 0 00-79.7-137.9l-1.8-2.1a32.12 32.12 0 00-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85a32.05 32.05 0 00-25.8-25.7l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 00-25.8 25.7l-15.8 85.4a351.86 351.86 0 00-99 57.4l-81.9-29.1a32 32 0 00-35.1 9.5l-1.8 2.1a446.02 446.02 0 00-79.7 137.9l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 00-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1a32.12 32.12 0 0035.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4a32.05 32.05 0 0025.8 25.7l2.7.5a449.4 449.4 0 00159 0l2.7-.5a32.05 32.05 0 0025.8-25.7l15.7-85a350 350 0 0099.7-57.6l81.3 28.9a32 32 0 0035.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c4.5-12.3.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 01-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97a377.5 377.5 0 01-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9zM512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 01512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 01400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 01624 502c0 29.9-11.7 58-32.8 79.2z"}}]},name:"setting",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},8881:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372 0-89 31.3-170.8 83.5-234.8l523.3 523.3C682.8 852.7 601 884 512 884zm288.5-137.2L277.2 223.5C341.2 171.3 423 140 512 140c205.4 0 372 166.6 372 372 0 89-31.3 170.8-83.5 234.8z"}}]},name:"stop",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},71891:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M483.2 790.3L861.4 412c1.7-1.7 2.5-4 2.3-6.3l-25.5-301.4c-.7-7.8-6.8-13.9-14.6-14.6L522.2 64.3c-2.3-.2-4.7.6-6.3 2.3L137.7 444.8a8.03 8.03 0 000 11.3l334.2 334.2c3.1 3.2 8.2 3.2 11.3 0zm62.6-651.7l224.6 19 19 224.6L477.5 694 233.9 450.5l311.9-311.9zm60.16 186.23a48 48 0 1067.88-67.89 48 48 0 10-67.88 67.89zM889.7 539.8l-39.6-39.5a8.03 8.03 0 00-11.3 0l-362 361.3-237.6-237a8.03 8.03 0 00-11.3 0l-39.6 39.5a8.03 8.03 0 000 11.3l243.2 242.8 39.6 39.5c3.1 3.1 8.2 3.1 11.3 0l407.3-406.6c3.1-3.1 3.1-8.2 0-11.3z"}}]},name:"tags",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},41361:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M824.2 699.9a301.55 301.55 0 00-86.4-60.4C783.1 602.8 812 546.8 812 484c0-110.8-92.4-201.7-203.2-200-109.1 1.7-197 90.6-197 200 0 62.8 29 118.8 74.2 155.5a300.95 300.95 0 00-86.4 60.4C345 754.6 314 826.8 312 903.8a8 8 0 008 8.2h56c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5A226.62 226.62 0 01612 684c60.9 0 118.2 23.7 161.3 66.8C814.5 792 838 846.3 840 904.3c.1 4.3 3.7 7.7 8 7.7h56a8 8 0 008-8.2c-2-77-33-149.2-87.8-203.9zM612 612c-34.2 0-66.4-13.3-90.5-37.5a126.86 126.86 0 01-37.5-91.8c.3-32.8 13.4-64.5 36.3-88 24-24.6 56.1-38.3 90.4-38.7 33.9-.3 66.8 12.9 91 36.6 24.8 24.3 38.4 56.8 38.4 91.4 0 34.2-13.3 66.3-37.5 90.5A127.3 127.3 0 01612 612zM361.5 510.4c-.9-8.7-1.4-17.5-1.4-26.4 0-15.9 1.5-31.4 4.3-46.5.7-3.6-1.2-7.3-4.5-8.8-13.6-6.1-26.1-14.5-36.9-25.1a127.54 127.54 0 01-38.7-95.4c.9-32.1 13.8-62.6 36.3-85.6 24.7-25.3 57.9-39.1 93.2-38.7 31.9.3 62.7 12.6 86 34.4 7.9 7.4 14.7 15.6 20.4 24.4 2 3.1 5.9 4.4 9.3 3.2 17.6-6.1 36.2-10.4 55.3-12.4 5.6-.6 8.8-6.6 6.3-11.6-32.5-64.3-98.9-108.7-175.7-109.9-110.9-1.7-203.3 89.2-203.3 199.9 0 62.8 28.9 118.8 74.2 155.5-31.8 14.7-61.1 35-86.5 60.4-54.8 54.7-85.8 126.9-87.8 204a8 8 0 008 8.2h56.1c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5 29.4-29.4 65.4-49.8 104.7-59.7 3.9-1 6.5-4.7 6-8.7z"}}]},name:"team",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},58630:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M876.6 239.5c-.5-.9-1.2-1.8-2-2.5-5-5-13.1-5-18.1 0L684.2 409.3l-67.9-67.9L788.7 169c.8-.8 1.4-1.6 2-2.5 3.6-6.1 1.6-13.9-4.5-17.5-98.2-58-226.8-44.7-311.3 39.7-67 67-89.2 162-66.5 247.4l-293 293c-3 3-2.8 7.9.3 11l169.7 169.7c3.1 3.1 8.1 3.3 11 .3l292.9-292.9c85.5 22.8 180.5.7 247.6-66.4 84.4-84.5 97.7-213.1 39.7-311.3zM786 499.8c-58.1 58.1-145.3 69.3-214.6 33.6l-8.8 8.8-.1-.1-274 274.1-79.2-79.2 230.1-230.1s0 .1.1.1l52.8-52.8c-35.7-69.3-24.5-156.5 33.6-214.6a184.2 184.2 0 01144-53.5L537 318.9a32.05 32.05 0 000 45.3l124.5 124.5a32.05 32.05 0 0045.3 0l132.8-132.8c3.7 51.8-14.4 104.8-53.6 143.9z"}}]},name:"tool",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},3632:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M400 317.7h73.9V656c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V317.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 163a8 8 0 00-12.6 0l-112 141.7c-4.1 5.3-.4 13 6.3 13zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"}}]},name:"upload",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},35291:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M464 720a48 48 0 1096 0 48 48 0 10-96 0zm16-304v184c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V416c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8zm475.7 440l-416-720c-6.2-10.7-16.9-16-27.7-16s-21.6 5.3-27.7 16l-416 720C56 877.4 71.4 904 96 904h832c24.6 0 40-26.6 27.7-48zm-783.5-27.9L512 239.9l339.8 588.2H172.2z"}}]},name:"warning",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},58747:function(e,t,n){"use strict";n.d(t,{Z:function(){return i}});var r=n(5853),o=n(2265);let i=e=>{var t=(0,r._T)(e,[]);return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),o.createElement("path",{d:"M11.9999 13.1714L16.9497 8.22168L18.3639 9.63589L11.9999 15.9999L5.63599 9.63589L7.0502 8.22168L11.9999 13.1714Z"}))}},4537:function(e,t,n){"use strict";n.d(t,{Z:function(){return i}});var r=n(5853),o=n(2265);let i=e=>{var t=(0,r._T)(e,[]);return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),o.createElement("path",{d:"M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 10.5858L9.17157 7.75736L7.75736 9.17157L10.5858 12L7.75736 14.8284L9.17157 16.2426L12 13.4142L14.8284 16.2426L16.2426 14.8284L13.4142 12L16.2426 9.17157L14.8284 7.75736L12 10.5858Z"}))}},75105:function(e,t,n){"use strict";n.d(t,{Z:function(){return et}});var r=n(5853),o=n(2265),i=n(47625),a=n(93765),l=n(87602),s=n(59221),c=n(86757),u=n.n(c),d=n(95645),f=n.n(d),p=n(77571),h=n.n(p),m=n(82559),g=n.n(m),v=n(21652),y=n.n(v),b=n(57165),x=n(81889),w=n(9841),k=n(58772),S=n(34067),E=n(16630),O=n(85355),C=n(82944),j=["layout","type","stroke","connectNulls","isRange","ref"];function _(e){return(_="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function P(){return(P=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}(i,j));return o.createElement(w.m,{clipPath:n?"url(#clipPath-".concat(r,")"):null},o.createElement(b.H,P({},(0,C.L6)(d,!0),{points:e,connectNulls:c,type:l,baseLine:t,layout:a,stroke:"none",className:"recharts-area-area"})),"none"!==s&&o.createElement(b.H,P({},(0,C.L6)(this.props,!1),{className:"recharts-area-curve",layout:a,type:l,connectNulls:c,fill:"none",points:e})),"none"!==s&&u&&o.createElement(b.H,P({},(0,C.L6)(this.props,!1),{className:"recharts-area-curve",layout:a,type:l,connectNulls:c,fill:"none",points:t})))}},{key:"renderAreaWithAnimation",value:function(e,t){var n=this,r=this.props,i=r.points,a=r.baseLine,l=r.isAnimationActive,c=r.animationBegin,u=r.animationDuration,d=r.animationEasing,f=r.animationId,p=this.state,m=p.prevPoints,v=p.prevBaseLine;return o.createElement(s.ZP,{begin:c,duration:u,isActive:l,easing:d,from:{t:0},to:{t:1},key:"area-".concat(f),onAnimationEnd:this.handleAnimationEnd,onAnimationStart:this.handleAnimationStart},function(r){var l=r.t;if(m){var s,c=m.length/i.length,u=i.map(function(e,t){var n=Math.floor(t*c);if(m[n]){var r=m[n],o=(0,E.k4)(r.x,e.x),i=(0,E.k4)(r.y,e.y);return M(M({},e),{},{x:o(l),y:i(l)})}return e});return s=(0,E.hj)(a)&&"number"==typeof a?(0,E.k4)(v,a)(l):h()(a)||g()(a)?(0,E.k4)(v,0)(l):a.map(function(e,t){var n=Math.floor(t*c);if(v[n]){var r=v[n],o=(0,E.k4)(r.x,e.x),i=(0,E.k4)(r.y,e.y);return M(M({},e),{},{x:o(l),y:i(l)})}return e}),n.renderAreaStatically(u,s,e,t)}return o.createElement(w.m,null,o.createElement("defs",null,o.createElement("clipPath",{id:"animationClipPath-".concat(t)},n.renderClipRect(l))),o.createElement(w.m,{clipPath:"url(#animationClipPath-".concat(t,")")},n.renderAreaStatically(i,a,e,t)))})}},{key:"renderArea",value:function(e,t){var n=this.props,r=n.points,o=n.baseLine,i=n.isAnimationActive,a=this.state,l=a.prevPoints,s=a.prevBaseLine,c=a.totalLength;return i&&r&&r.length&&(!l&&c>0||!y()(l,r)||!y()(s,o))?this.renderAreaWithAnimation(e,t):this.renderAreaStatically(r,o,e,t)}},{key:"render",value:function(){var e,t=this.props,n=t.hide,r=t.dot,i=t.points,a=t.className,s=t.top,c=t.left,u=t.xAxis,d=t.yAxis,f=t.width,p=t.height,m=t.isAnimationActive,g=t.id;if(n||!i||!i.length)return null;var v=this.state.isAnimationFinished,y=1===i.length,b=(0,l.Z)("recharts-area",a),x=u&&u.allowDataOverflow,S=d&&d.allowDataOverflow,E=x||S,O=h()(g)?this.id:g,j=null!==(e=(0,C.L6)(r,!1))&&void 0!==e?e:{r:3,strokeWidth:2},_=j.r,P=j.strokeWidth,T=((0,C.$k)(r)?r:{}).clipDot,M=void 0===T||T,N=2*(void 0===_?3:_)+(void 0===P?2:P);return o.createElement(w.m,{className:b},x||S?o.createElement("defs",null,o.createElement("clipPath",{id:"clipPath-".concat(O)},o.createElement("rect",{x:x?c:c-f/2,y:S?s:s-p/2,width:x?f:2*f,height:S?p:2*p})),!M&&o.createElement("clipPath",{id:"clipPath-dots-".concat(O)},o.createElement("rect",{x:c-N/2,y:s-N/2,width:f+N,height:p+N}))):null,y?null:this.renderArea(E,O),(r||y)&&this.renderDots(E,M,O),(!m||v)&&k.e.renderCallByParent(this.props,i))}}],r=[{key:"getDerivedStateFromProps",value:function(e,t){return e.animationId!==t.prevAnimationId?{prevAnimationId:e.animationId,curPoints:e.points,curBaseLine:e.baseLine,prevPoints:t.curPoints,prevBaseLine:t.curBaseLine}:e.points!==t.curPoints||e.baseLine!==t.curBaseLine?{curPoints:e.points,curBaseLine:e.baseLine}:null}}],n&&N(a.prototype,n),r&&N(a,r),Object.defineProperty(a,"prototype",{writable:!1}),a}(o.PureComponent);D(z,"displayName","Area"),D(z,"defaultProps",{stroke:"#3182bd",fill:"#3182bd",fillOpacity:.6,xAxisId:0,yAxisId:0,legendType:"line",connectNulls:!1,points:[],dot:!1,activeDot:!0,hide:!1,isAnimationActive:!S.x.isSsr,animationBegin:0,animationDuration:1500,animationEasing:"ease"}),D(z,"getBaseValue",function(e,t,n,r){var o=e.layout,i=e.baseValue,a=t.props.baseValue,l=null!=a?a:i;if((0,E.hj)(l)&&"number"==typeof l)return l;var s="horizontal"===o?r:n,c=s.scale.domain();if("number"===s.type){var u=Math.max(c[0],c[1]),d=Math.min(c[0],c[1]);return"dataMin"===l?d:"dataMax"===l?u:u<0?u:Math.max(Math.min(c[0],c[1]),0)}return"dataMin"===l?c[0]:"dataMax"===l?c[1]:c[0]}),D(z,"getComposedData",function(e){var t,n=e.props,r=e.item,o=e.xAxis,i=e.yAxis,a=e.xAxisTicks,l=e.yAxisTicks,s=e.bandSize,c=e.dataKey,u=e.stackedData,d=e.dataStartIndex,f=e.displayedData,p=e.offset,h=n.layout,m=u&&u.length,g=z.getBaseValue(n,r,o,i),v="horizontal"===h,y=!1,b=f.map(function(e,t){m?n=u[d+t]:Array.isArray(n=(0,O.F$)(e,c))?y=!0:n=[g,n];var n,r=null==n[1]||m&&null==(0,O.F$)(e,c);return v?{x:(0,O.Hv)({axis:o,ticks:a,bandSize:s,entry:e,index:t}),y:r?null:i.scale(n[1]),value:n,payload:e}:{x:r?null:o.scale(n[1]),y:(0,O.Hv)({axis:i,ticks:l,bandSize:s,entry:e,index:t}),value:n,payload:e}});return t=m||y?b.map(function(e){var t=Array.isArray(e.value)?e.value[0]:null;return v?{x:e.x,y:null!=t&&null!=e.y?i.scale(t):null}:{x:null!=t?o.scale(t):null,y:e.y}}):v?i.scale(g):o.scale(g),M({points:b,baseLine:t,layout:h,isRange:y},p)}),D(z,"renderDotItem",function(e,t){return o.isValidElement(e)?o.cloneElement(e,t):u()(e)?e(t):o.createElement(x.o,P({},t,{className:"recharts-area-dot"}))});var Z=n(97059),B=n(62994),F=n(25311),H=(0,a.z)({chartName:"AreaChart",GraphicalChild:z,axisComponents:[{axisType:"xAxis",AxisComp:Z.K},{axisType:"yAxis",AxisComp:B.B}],formatAxisMap:F.t9}),q=n(56940),U=n(8147),W=n(22190),K=n(54061),V=n(65278),$=n(98593),X=n(69448),G=n(32644),Y=n(7084),Q=n(26898),J=n(97324),ee=n(1153);let et=o.forwardRef((e,t)=>{let{data:n=[],categories:a=[],index:l,stack:s=!1,colors:c=Q.s,valueFormatter:u=ee.Cj,startEndOnly:d=!1,showXAxis:f=!0,showYAxis:p=!0,yAxisWidth:h=56,intervalType:m="equidistantPreserveStart",showAnimation:g=!1,animationDuration:v=900,showTooltip:y=!0,showLegend:b=!0,showGridLines:w=!0,showGradient:k=!0,autoMinValue:S=!1,curveType:E="linear",minValue:O,maxValue:C,connectNulls:j=!1,allowDecimals:_=!0,noDataText:P,className:T,onValueChange:M,enableLegendSlider:N=!1,customTooltip:A,rotateLabelX:I,tickGap:R=5}=e,D=(0,r._T)(e,["data","categories","index","stack","colors","valueFormatter","startEndOnly","showXAxis","showYAxis","yAxisWidth","intervalType","showAnimation","animationDuration","showTooltip","showLegend","showGridLines","showGradient","autoMinValue","curveType","minValue","maxValue","connectNulls","allowDecimals","noDataText","className","onValueChange","enableLegendSlider","customTooltip","rotateLabelX","tickGap"]),L=(f||p)&&(!d||p)?20:0,[F,et]=(0,o.useState)(60),[en,er]=(0,o.useState)(void 0),[eo,ei]=(0,o.useState)(void 0),ea=(0,G.me)(a,c),el=(0,G.i4)(S,O,C),es=!!M;function ec(e){es&&(e===eo&&!en||(0,G.FB)(n,e)&&en&&en.dataKey===e?(ei(void 0),null==M||M(null)):(ei(e),null==M||M({eventType:"category",categoryClicked:e})),er(void 0))}return o.createElement("div",Object.assign({ref:t,className:(0,J.q)("w-full h-80",T)},D),o.createElement(i.h,{className:"h-full w-full"},(null==n?void 0:n.length)?o.createElement(H,{data:n,onClick:es&&(eo||en)?()=>{er(void 0),ei(void 0),null==M||M(null)}:void 0},w?o.createElement(q.q,{className:(0,J.q)("stroke-1","stroke-tremor-border","dark:stroke-dark-tremor-border"),horizontal:!0,vertical:!1}):null,o.createElement(Z.K,{padding:{left:L,right:L},hide:!f,dataKey:l,tick:{transform:"translate(0, 6)"},ticks:d?[n[0][l],n[n.length-1][l]]:void 0,fill:"",stroke:"",className:(0,J.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),interval:d?"preserveStartEnd":m,tickLine:!1,axisLine:!1,minTickGap:R,angle:null==I?void 0:I.angle,dy:null==I?void 0:I.verticalShift,height:null==I?void 0:I.xAxisHeight}),o.createElement(B.B,{width:h,hide:!p,axisLine:!1,tickLine:!1,type:"number",domain:el,tick:{transform:"translate(-3, 0)"},fill:"",stroke:"",className:(0,J.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickFormatter:u,allowDecimals:_}),o.createElement(U.u,{wrapperStyle:{outline:"none"},isAnimationActive:!1,cursor:{stroke:"#d1d5db",strokeWidth:1},content:y?e=>{let{active:t,payload:n,label:r}=e;return A?o.createElement(A,{payload:null==n?void 0:n.map(e=>{var t;return Object.assign(Object.assign({},e),{color:null!==(t=ea.get(e.dataKey))&&void 0!==t?t:Y.fr.Gray})}),active:t,label:r}):o.createElement($.ZP,{active:t,payload:n,label:r,valueFormatter:u,categoryColors:ea})}:o.createElement(o.Fragment,null),position:{y:0}}),b?o.createElement(W.D,{verticalAlign:"top",height:F,content:e=>{let{payload:t}=e;return(0,V.Z)({payload:t},ea,et,eo,es?e=>ec(e):void 0,N)}}):null,a.map(e=>{var t,n;return o.createElement("defs",{key:e},k?o.createElement("linearGradient",{className:(0,ee.bM)(null!==(t=ea.get(e))&&void 0!==t?t:Y.fr.Gray,Q.K.text).textColor,id:ea.get(e),x1:"0",y1:"0",x2:"0",y2:"1"},o.createElement("stop",{offset:"5%",stopColor:"currentColor",stopOpacity:en||eo&&eo!==e?.15:.4}),o.createElement("stop",{offset:"95%",stopColor:"currentColor",stopOpacity:0})):o.createElement("linearGradient",{className:(0,ee.bM)(null!==(n=ea.get(e))&&void 0!==n?n:Y.fr.Gray,Q.K.text).textColor,id:ea.get(e),x1:"0",y1:"0",x2:"0",y2:"1"},o.createElement("stop",{stopColor:"currentColor",stopOpacity:en||eo&&eo!==e?.1:.3})))}),a.map(e=>{var t;return o.createElement(z,{className:(0,ee.bM)(null!==(t=ea.get(e))&&void 0!==t?t:Y.fr.Gray,Q.K.text).strokeColor,strokeOpacity:en||eo&&eo!==e?.3:1,activeDot:e=>{var t;let{cx:r,cy:i,stroke:a,strokeLinecap:l,strokeLinejoin:s,strokeWidth:c,dataKey:u}=e;return o.createElement(x.o,{className:(0,J.q)("stroke-tremor-background dark:stroke-dark-tremor-background",M?"cursor-pointer":"",(0,ee.bM)(null!==(t=ea.get(u))&&void 0!==t?t:Y.fr.Gray,Q.K.text).fillColor),cx:r,cy:i,r:5,fill:"",stroke:a,strokeLinecap:l,strokeLinejoin:s,strokeWidth:c,onClick:(t,r)=>{r.stopPropagation(),es&&(e.index===(null==en?void 0:en.index)&&e.dataKey===(null==en?void 0:en.dataKey)||(0,G.FB)(n,e.dataKey)&&eo&&eo===e.dataKey?(ei(void 0),er(void 0),null==M||M(null)):(ei(e.dataKey),er({index:e.index,dataKey:e.dataKey}),null==M||M(Object.assign({eventType:"dot",categoryClicked:e.dataKey},e.payload))))}})},dot:t=>{var r;let{stroke:i,strokeLinecap:a,strokeLinejoin:l,strokeWidth:s,cx:c,cy:u,dataKey:d,index:f}=t;return(0,G.FB)(n,e)&&!(en||eo&&eo!==e)||(null==en?void 0:en.index)===f&&(null==en?void 0:en.dataKey)===e?o.createElement(x.o,{key:f,cx:c,cy:u,r:5,stroke:i,fill:"",strokeLinecap:a,strokeLinejoin:l,strokeWidth:s,className:(0,J.q)("stroke-tremor-background dark:stroke-dark-tremor-background",M?"cursor-pointer":"",(0,ee.bM)(null!==(r=ea.get(d))&&void 0!==r?r:Y.fr.Gray,Q.K.text).fillColor)}):o.createElement(o.Fragment,{key:f})},key:e,name:e,type:E,dataKey:e,stroke:"",fill:"url(#".concat(ea.get(e),")"),strokeWidth:2,strokeLinejoin:"round",strokeLinecap:"round",isAnimationActive:g,animationDuration:v,stackId:s?"a":void 0,connectNulls:j})}),M?a.map(e=>o.createElement(K.x,{className:(0,J.q)("cursor-pointer"),strokeOpacity:0,key:e,name:e,type:E,dataKey:e,stroke:"transparent",fill:"transparent",legendType:"none",tooltipType:"none",strokeWidth:12,connectNulls:j,onClick:(e,t)=>{t.stopPropagation();let{name:n}=e;ec(n)}})):null):o.createElement(X.Z,{noDataText:P})))});et.displayName="AreaChart"},40278:function(e,t,n){"use strict";n.d(t,{Z:function(){return S}});var r=n(5853),o=n(7084),i=n(26898),a=n(97324),l=n(1153),s=n(2265),c=n(47625),u=n(93765),d=n(31699),f=n(97059),p=n(62994),h=n(25311),m=(0,u.z)({chartName:"BarChart",GraphicalChild:d.$,defaultTooltipEventType:"axis",validateTooltipEventTypes:["axis","item"],axisComponents:[{axisType:"xAxis",AxisComp:f.K},{axisType:"yAxis",AxisComp:p.B}],formatAxisMap:h.t9}),g=n(56940),v=n(8147),y=n(22190),b=n(65278),x=n(98593),w=n(69448),k=n(32644);let S=s.forwardRef((e,t)=>{let{data:n=[],categories:u=[],index:h,colors:S=i.s,valueFormatter:E=l.Cj,layout:O="horizontal",stack:C=!1,relative:j=!1,startEndOnly:_=!1,animationDuration:P=900,showAnimation:T=!1,showXAxis:M=!0,showYAxis:N=!0,yAxisWidth:A=56,intervalType:I="equidistantPreserveStart",showTooltip:R=!0,showLegend:D=!0,showGridLines:L=!0,autoMinValue:z=!1,minValue:Z,maxValue:B,allowDecimals:F=!0,noDataText:H,onValueChange:q,enableLegendSlider:U=!1,customTooltip:W,rotateLabelX:K,tickGap:V=5,className:$}=e,X=(0,r._T)(e,["data","categories","index","colors","valueFormatter","layout","stack","relative","startEndOnly","animationDuration","showAnimation","showXAxis","showYAxis","yAxisWidth","intervalType","showTooltip","showLegend","showGridLines","autoMinValue","minValue","maxValue","allowDecimals","noDataText","onValueChange","enableLegendSlider","customTooltip","rotateLabelX","tickGap","className"]),G=M||N?20:0,[Y,Q]=(0,s.useState)(60),J=(0,k.me)(u,S),[ee,et]=s.useState(void 0),[en,er]=(0,s.useState)(void 0),eo=!!q;function ei(e,t,n){var r,o,i,a;n.stopPropagation(),q&&((0,k.vZ)(ee,Object.assign(Object.assign({},e.payload),{value:e.value}))?(er(void 0),et(void 0),null==q||q(null)):(er(null===(o=null===(r=e.tooltipPayload)||void 0===r?void 0:r[0])||void 0===o?void 0:o.dataKey),et(Object.assign(Object.assign({},e.payload),{value:e.value})),null==q||q(Object.assign({eventType:"bar",categoryClicked:null===(a=null===(i=e.tooltipPayload)||void 0===i?void 0:i[0])||void 0===a?void 0:a.dataKey},e.payload))))}let ea=(0,k.i4)(z,Z,B);return s.createElement("div",Object.assign({ref:t,className:(0,a.q)("w-full h-80",$)},X),s.createElement(c.h,{className:"h-full w-full"},(null==n?void 0:n.length)?s.createElement(m,{data:n,stackOffset:C?"sign":j?"expand":"none",layout:"vertical"===O?"vertical":"horizontal",onClick:eo&&(en||ee)?()=>{et(void 0),er(void 0),null==q||q(null)}:void 0},L?s.createElement(g.q,{className:(0,a.q)("stroke-1","stroke-tremor-border","dark:stroke-dark-tremor-border"),horizontal:"vertical"!==O,vertical:"vertical"===O}):null,"vertical"!==O?s.createElement(f.K,{padding:{left:G,right:G},hide:!M,dataKey:h,interval:_?"preserveStartEnd":I,tick:{transform:"translate(0, 6)"},ticks:_?[n[0][h],n[n.length-1][h]]:void 0,fill:"",stroke:"",className:(0,a.q)("mt-4 text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickLine:!1,axisLine:!1,angle:null==K?void 0:K.angle,dy:null==K?void 0:K.verticalShift,height:null==K?void 0:K.xAxisHeight,minTickGap:V}):s.createElement(f.K,{hide:!M,type:"number",tick:{transform:"translate(-3, 0)"},domain:ea,fill:"",stroke:"",className:(0,a.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickLine:!1,axisLine:!1,tickFormatter:E,minTickGap:V,allowDecimals:F,angle:null==K?void 0:K.angle,dy:null==K?void 0:K.verticalShift,height:null==K?void 0:K.xAxisHeight}),"vertical"!==O?s.createElement(p.B,{width:A,hide:!N,axisLine:!1,tickLine:!1,type:"number",domain:ea,tick:{transform:"translate(-3, 0)"},fill:"",stroke:"",className:(0,a.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickFormatter:j?e=>"".concat((100*e).toString()," %"):E,allowDecimals:F}):s.createElement(p.B,{width:A,hide:!N,dataKey:h,axisLine:!1,tickLine:!1,ticks:_?[n[0][h],n[n.length-1][h]]:void 0,type:"category",interval:"preserveStartEnd",tick:{transform:"translate(0, 6)"},fill:"",stroke:"",className:(0,a.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content")}),s.createElement(v.u,{wrapperStyle:{outline:"none"},isAnimationActive:!1,cursor:{fill:"#d1d5db",opacity:"0.15"},content:R?e=>{let{active:t,payload:n,label:r}=e;return W?s.createElement(W,{payload:null==n?void 0:n.map(e=>{var t;return Object.assign(Object.assign({},e),{color:null!==(t=J.get(e.dataKey))&&void 0!==t?t:o.fr.Gray})}),active:t,label:r}):s.createElement(x.ZP,{active:t,payload:n,label:r,valueFormatter:E,categoryColors:J})}:s.createElement(s.Fragment,null),position:{y:0}}),D?s.createElement(y.D,{verticalAlign:"top",height:Y,content:e=>{let{payload:t}=e;return(0,b.Z)({payload:t},J,Q,en,eo?e=>{eo&&(e!==en||ee?(er(e),null==q||q({eventType:"category",categoryClicked:e})):(er(void 0),null==q||q(null)),et(void 0))}:void 0,U)}}):null,u.map(e=>{var t;return s.createElement(d.$,{className:(0,a.q)((0,l.bM)(null!==(t=J.get(e))&&void 0!==t?t:o.fr.Gray,i.K.background).fillColor,q?"cursor-pointer":""),key:e,name:e,type:"linear",stackId:C||j?"a":void 0,dataKey:e,fill:"",isAnimationActive:T,animationDuration:P,shape:e=>((e,t,n,r)=>{let{fillOpacity:o,name:i,payload:a,value:l}=e,{x:c,width:u,y:d,height:f}=e;return"horizontal"===r&&f<0?(d+=f,f=Math.abs(f)):"vertical"===r&&u<0&&(c+=u,u=Math.abs(u)),s.createElement("rect",{x:c,y:d,width:u,height:f,opacity:t||n&&n!==i?(0,k.vZ)(t,Object.assign(Object.assign({},a),{value:l}))?o:.3:o})})(e,ee,en,O),onClick:ei})})):s.createElement(w.Z,{noDataText:H})))});S.displayName="BarChart"},14042:function(e,t,n){"use strict";n.d(t,{Z:function(){return eZ}});var r=n(5853),o=n(7084),i=n(26898),a=n(97324),l=n(1153),s=n(2265),c=n(60474),u=n(47625),d=n(93765),f=n(86757),p=n.n(f),h=n(9841),m=n(81889),g=n(87602),v=n(82944),y=["points","className","baseLinePoints","connectNulls"];function b(){return(b=Object.assign?Object.assign.bind():function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&void 0!==arguments[0]?arguments[0]:[],t=[[]];return e.forEach(function(e){k(e)?t[t.length-1].push(e):t[t.length-1].length>0&&t.push([])}),k(e[0])&&t[t.length-1].push(e[0]),t[t.length-1].length<=0&&(t=t.slice(0,-1)),t},E=function(e,t){var n=S(e);t&&(n=[n.reduce(function(e,t){return[].concat(x(e),x(t))},[])]);var r=n.map(function(e){return e.reduce(function(e,t,n){return"".concat(e).concat(0===n?"M":"L").concat(t.x,",").concat(t.y)},"")}).join("");return 1===n.length?"".concat(r,"Z"):r},O=function(e,t,n){var r=E(e,n);return"".concat("Z"===r.slice(-1)?r.slice(0,-1):r,"L").concat(E(t.reverse(),n).slice(1))},C=function(e){var t=e.points,n=e.className,r=e.baseLinePoints,o=e.connectNulls,i=function(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n,r,o={},i=Object.keys(e);for(r=0;r=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}(e,y);if(!t||!t.length)return null;var a=(0,g.Z)("recharts-polygon",n);if(r&&r.length){var l=i.stroke&&"none"!==i.stroke,c=O(t,r,o);return s.createElement("g",{className:a},s.createElement("path",b({},(0,v.L6)(i,!0),{fill:"Z"===c.slice(-1)?i.fill:"none",stroke:"none",d:c})),l?s.createElement("path",b({},(0,v.L6)(i,!0),{fill:"none",d:E(t,o)})):null,l?s.createElement("path",b({},(0,v.L6)(i,!0),{fill:"none",d:E(r,o)})):null)}var u=E(t,o);return s.createElement("path",b({},(0,v.L6)(i,!0),{fill:"Z"===u.slice(-1)?i.fill:"none",className:a,d:u}))},j=n(58811),_=n(41637),P=n(39206);function T(e){return(T="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function M(){return(M=Object.assign?Object.assign.bind():function(e){for(var t=1;t1e-5?"outer"===t?"start":"end":n<-.00001?"outer"===t?"end":"start":"middle"}},{key:"renderAxisLine",value:function(){var e=this.props,t=e.cx,n=e.cy,r=e.radius,o=e.axisLine,i=e.axisLineType,a=A(A({},(0,v.L6)(this.props,!1)),{},{fill:"none"},(0,v.L6)(o,!1));if("circle"===i)return s.createElement(m.o,M({className:"recharts-polar-angle-axis-line"},a,{cx:t,cy:n,r:r}));var l=this.props.ticks.map(function(e){return(0,P.op)(t,n,r,e.coordinate)});return s.createElement(C,M({className:"recharts-polar-angle-axis-line"},a,{points:l}))}},{key:"renderTicks",value:function(){var e=this,t=this.props,n=t.ticks,r=t.tick,o=t.tickLine,a=t.tickFormatter,l=t.stroke,c=(0,v.L6)(this.props,!1),u=(0,v.L6)(r,!1),d=A(A({},c),{},{fill:"none"},(0,v.L6)(o,!1)),f=n.map(function(t,n){var f=e.getTickLineCoord(t),p=A(A(A({textAnchor:e.getTickTextAnchor(t)},c),{},{stroke:"none",fill:l},u),{},{index:n,payload:t,x:f.x2,y:f.y2});return s.createElement(h.m,M({className:"recharts-polar-angle-axis-tick",key:"tick-".concat(t.coordinate)},(0,_.bw)(e.props,t,n)),o&&s.createElement("line",M({className:"recharts-polar-angle-axis-tick-line"},d,f)),r&&i.renderTickItem(r,p,a?a(t.value,n):t.value))});return s.createElement(h.m,{className:"recharts-polar-angle-axis-ticks"},f)}},{key:"render",value:function(){var e=this.props,t=e.ticks,n=e.radius,r=e.axisLine;return!(n<=0)&&t&&t.length?s.createElement(h.m,{className:"recharts-polar-angle-axis"},r&&this.renderAxisLine(),this.renderTicks()):null}}],r=[{key:"renderTickItem",value:function(e,t,n){return s.isValidElement(e)?s.cloneElement(e,t):p()(e)?e(t):s.createElement(j.x,M({},t,{className:"recharts-polar-angle-axis-tick-value"}),n)}}],n&&I(i.prototype,n),r&&I(i,r),Object.defineProperty(i,"prototype",{writable:!1}),i}(s.PureComponent);L(B,"displayName","PolarAngleAxis"),L(B,"axisType","angleAxis"),L(B,"defaultProps",{type:"category",angleAxisId:0,scale:"auto",cx:0,cy:0,orientation:"outer",axisLine:!0,tickLine:!0,tickSize:8,tick:!0,hide:!1,allowDuplicatedCategory:!0});var F=n(35802),H=n.n(F),q=n(37891),U=n.n(q),W=n(26680),K=["cx","cy","angle","ticks","axisLine"],V=["ticks","tick","angle","tickFormatter","stroke"];function $(e){return($="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function X(){return(X=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function J(e,t){for(var n=0;n0?el()(e,"paddingAngle",0):0;if(n){var l=(0,eg.k4)(n.endAngle-n.startAngle,e.endAngle-e.startAngle),s=eS(eS({},e),{},{startAngle:i+a,endAngle:i+l(r)+a});o.push(s),i=s.endAngle}else{var c=e.endAngle,d=e.startAngle,f=(0,eg.k4)(0,c-d)(r),p=eS(eS({},e),{},{startAngle:i+a,endAngle:i+f+a});o.push(p),i=p.endAngle}}),s.createElement(h.m,null,e.renderSectorsStatically(o))})}},{key:"attachKeyboardHandlers",value:function(e){var t=this;e.onkeydown=function(e){if(!e.altKey)switch(e.key){case"ArrowLeft":var n=++t.state.sectorToFocus%t.sectorRefs.length;t.sectorRefs[n].focus(),t.setState({sectorToFocus:n});break;case"ArrowRight":var r=--t.state.sectorToFocus<0?t.sectorRefs.length-1:t.state.sectorToFocus%t.sectorRefs.length;t.sectorRefs[r].focus(),t.setState({sectorToFocus:r});break;case"Escape":t.sectorRefs[t.state.sectorToFocus].blur(),t.setState({sectorToFocus:0})}}}},{key:"renderSectors",value:function(){var e=this.props,t=e.sectors,n=e.isAnimationActive,r=this.state.prevSectors;return n&&t&&t.length&&(!r||!ec()(r,t))?this.renderSectorsWithAnimation():this.renderSectorsStatically(t)}},{key:"componentDidMount",value:function(){this.pieRef&&this.attachKeyboardHandlers(this.pieRef)}},{key:"render",value:function(){var e=this,t=this.props,n=t.hide,r=t.sectors,o=t.className,i=t.label,a=t.cx,l=t.cy,c=t.innerRadius,u=t.outerRadius,d=t.isAnimationActive,f=this.state.isAnimationFinished;if(n||!r||!r.length||!(0,eg.hj)(a)||!(0,eg.hj)(l)||!(0,eg.hj)(c)||!(0,eg.hj)(u))return null;var p=(0,g.Z)("recharts-pie",o);return s.createElement(h.m,{tabIndex:this.props.rootTabIndex,className:p,ref:function(t){e.pieRef=t}},this.renderSectors(),i&&this.renderLabels(r),W._.renderCallByParent(this.props,null,!1),(!d||f)&&ep.e.renderCallByParent(this.props,r,!1))}}],r=[{key:"getDerivedStateFromProps",value:function(e,t){return t.prevIsAnimationActive!==e.isAnimationActive?{prevIsAnimationActive:e.isAnimationActive,prevAnimationId:e.animationId,curSectors:e.sectors,prevSectors:[],isAnimationFinished:!0}:e.isAnimationActive&&e.animationId!==t.prevAnimationId?{prevAnimationId:e.animationId,curSectors:e.sectors,prevSectors:t.curSectors,isAnimationFinished:!0}:e.sectors!==t.curSectors?{curSectors:e.sectors,isAnimationFinished:!0}:null}},{key:"getTextAnchor",value:function(e,t){return e>t?"start":e=360?x:x-1)*u,k=i.reduce(function(e,t){var n=(0,ev.F$)(t,b,0);return e+((0,eg.hj)(n)?n:0)},0);return k>0&&(t=i.map(function(e,t){var r,o=(0,ev.F$)(e,b,0),i=(0,ev.F$)(e,f,t),a=((0,eg.hj)(o)?o:0)/k,c=(r=t?n.endAngle+(0,eg.uY)(v)*u*(0!==o?1:0):s)+(0,eg.uY)(v)*((0!==o?m:0)+a*w),d=(r+c)/2,p=(g.innerRadius+g.outerRadius)/2,y=[{name:i,value:o,payload:e,dataKey:b,type:h}],x=(0,P.op)(g.cx,g.cy,p,d);return n=eS(eS(eS({percent:a,cornerRadius:l,name:i,tooltipPayload:y,midAngle:d,middleRadius:p,tooltipPosition:x},e),g),{},{value:(0,ev.F$)(e,b),startAngle:r,endAngle:c,payload:e,paddingAngle:(0,eg.uY)(v)*u})})),eS(eS({},g),{},{sectors:t,data:i})});var eM=(0,d.z)({chartName:"PieChart",GraphicalChild:eT,validateTooltipEventTypes:["item"],defaultTooltipEventType:"item",legendContent:"children",axisComponents:[{axisType:"angleAxis",AxisComp:B},{axisType:"radiusAxis",AxisComp:eo}],formatAxisMap:P.t9,defaultProps:{layout:"centric",startAngle:0,endAngle:360,cx:"50%",cy:"50%",innerRadius:0,outerRadius:"80%"}}),eN=n(8147),eA=n(69448),eI=n(98593);let eR=e=>{let{active:t,payload:n,valueFormatter:r}=e;if(t&&(null==n?void 0:n[0])){let e=null==n?void 0:n[0];return s.createElement(eI.$B,null,s.createElement("div",{className:(0,a.q)("px-4 py-2")},s.createElement(eI.zX,{value:r(e.value),name:e.name,color:e.payload.color})))}return null},eD=(e,t)=>e.map((e,n)=>{let r=ne||t((0,l.vP)(n.map(e=>e[r]))),ez=e=>{let{cx:t,cy:n,innerRadius:r,outerRadius:o,startAngle:i,endAngle:a,className:l}=e;return s.createElement("g",null,s.createElement(c.L,{cx:t,cy:n,innerRadius:r,outerRadius:o,startAngle:i,endAngle:a,className:l,fill:"",opacity:.3,style:{outline:"none"}}))},eZ=s.forwardRef((e,t)=>{let{data:n=[],category:c="value",index:d="name",colors:f=i.s,variant:p="donut",valueFormatter:h=l.Cj,label:m,showLabel:g=!0,animationDuration:v=900,showAnimation:y=!1,showTooltip:b=!0,noDataText:x,onValueChange:w,customTooltip:k,className:S}=e,E=(0,r._T)(e,["data","category","index","colors","variant","valueFormatter","label","showLabel","animationDuration","showAnimation","showTooltip","noDataText","onValueChange","customTooltip","className"]),O="donut"==p,C=eL(m,h,n,c),[j,_]=s.useState(void 0),P=!!w;return(0,s.useEffect)(()=>{let e=document.querySelectorAll(".recharts-pie-sector");e&&e.forEach(e=>{e.setAttribute("style","outline: none")})},[j]),s.createElement("div",Object.assign({ref:t,className:(0,a.q)("w-full h-40",S)},E),s.createElement(u.h,{className:"h-full w-full"},(null==n?void 0:n.length)?s.createElement(eM,{onClick:P&&j?()=>{_(void 0),null==w||w(null)}:void 0,margin:{top:0,left:0,right:0,bottom:0}},g&&O?s.createElement("text",{className:(0,a.q)("fill-tremor-content-emphasis","dark:fill-dark-tremor-content-emphasis"),x:"50%",y:"50%",textAnchor:"middle",dominantBaseline:"middle"},C):null,s.createElement(eT,{className:(0,a.q)("stroke-tremor-background dark:stroke-dark-tremor-background",w?"cursor-pointer":"cursor-default"),data:eD(n,f),cx:"50%",cy:"50%",startAngle:90,endAngle:-270,innerRadius:O?"75%":"0%",outerRadius:"100%",stroke:"",strokeLinejoin:"round",dataKey:c,nameKey:d,isAnimationActive:y,animationDuration:v,onClick:function(e,t,n){n.stopPropagation(),P&&(j===t?(_(void 0),null==w||w(null)):(_(t),null==w||w(Object.assign({eventType:"slice"},e.payload.payload))))},activeIndex:j,inactiveShape:ez,style:{outline:"none"}}),s.createElement(eN.u,{wrapperStyle:{outline:"none"},isAnimationActive:!1,content:b?e=>{var t;let{active:n,payload:r}=e;return k?s.createElement(k,{payload:null==r?void 0:r.map(e=>{var t,n,i;return Object.assign(Object.assign({},e),{color:null!==(i=null===(n=null===(t=null==r?void 0:r[0])||void 0===t?void 0:t.payload)||void 0===n?void 0:n.color)&&void 0!==i?i:o.fr.Gray})}),active:n,label:null===(t=null==r?void 0:r[0])||void 0===t?void 0:t.name}):s.createElement(eR,{active:n,payload:r,valueFormatter:h})}:s.createElement(s.Fragment,null)})):s.createElement(eA.Z,{noDataText:x})))});eZ.displayName="DonutChart"},59664:function(e,t,n){"use strict";n.d(t,{Z:function(){return E}});var r=n(5853),o=n(2265),i=n(47625),a=n(93765),l=n(54061),s=n(97059),c=n(62994),u=n(25311),d=(0,a.z)({chartName:"LineChart",GraphicalChild:l.x,axisComponents:[{axisType:"xAxis",AxisComp:s.K},{axisType:"yAxis",AxisComp:c.B}],formatAxisMap:u.t9}),f=n(56940),p=n(8147),h=n(22190),m=n(81889),g=n(65278),v=n(98593),y=n(69448),b=n(32644),x=n(7084),w=n(26898),k=n(97324),S=n(1153);let E=o.forwardRef((e,t)=>{let{data:n=[],categories:a=[],index:u,colors:E=w.s,valueFormatter:O=S.Cj,startEndOnly:C=!1,showXAxis:j=!0,showYAxis:_=!0,yAxisWidth:P=56,intervalType:T="equidistantPreserveStart",animationDuration:M=900,showAnimation:N=!1,showTooltip:A=!0,showLegend:I=!0,showGridLines:R=!0,autoMinValue:D=!1,curveType:L="linear",minValue:z,maxValue:Z,connectNulls:B=!1,allowDecimals:F=!0,noDataText:H,className:q,onValueChange:U,enableLegendSlider:W=!1,customTooltip:K,rotateLabelX:V,tickGap:$=5}=e,X=(0,r._T)(e,["data","categories","index","colors","valueFormatter","startEndOnly","showXAxis","showYAxis","yAxisWidth","intervalType","animationDuration","showAnimation","showTooltip","showLegend","showGridLines","autoMinValue","curveType","minValue","maxValue","connectNulls","allowDecimals","noDataText","className","onValueChange","enableLegendSlider","customTooltip","rotateLabelX","tickGap"]),G=j||_?20:0,[Y,Q]=(0,o.useState)(60),[J,ee]=(0,o.useState)(void 0),[et,en]=(0,o.useState)(void 0),er=(0,b.me)(a,E),eo=(0,b.i4)(D,z,Z),ei=!!U;function ea(e){ei&&(e===et&&!J||(0,b.FB)(n,e)&&J&&J.dataKey===e?(en(void 0),null==U||U(null)):(en(e),null==U||U({eventType:"category",categoryClicked:e})),ee(void 0))}return o.createElement("div",Object.assign({ref:t,className:(0,k.q)("w-full h-80",q)},X),o.createElement(i.h,{className:"h-full w-full"},(null==n?void 0:n.length)?o.createElement(d,{data:n,onClick:ei&&(et||J)?()=>{ee(void 0),en(void 0),null==U||U(null)}:void 0},R?o.createElement(f.q,{className:(0,k.q)("stroke-1","stroke-tremor-border","dark:stroke-dark-tremor-border"),horizontal:!0,vertical:!1}):null,o.createElement(s.K,{padding:{left:G,right:G},hide:!j,dataKey:u,interval:C?"preserveStartEnd":T,tick:{transform:"translate(0, 6)"},ticks:C?[n[0][u],n[n.length-1][u]]:void 0,fill:"",stroke:"",className:(0,k.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickLine:!1,axisLine:!1,minTickGap:$,angle:null==V?void 0:V.angle,dy:null==V?void 0:V.verticalShift,height:null==V?void 0:V.xAxisHeight}),o.createElement(c.B,{width:P,hide:!_,axisLine:!1,tickLine:!1,type:"number",domain:eo,tick:{transform:"translate(-3, 0)"},fill:"",stroke:"",className:(0,k.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickFormatter:O,allowDecimals:F}),o.createElement(p.u,{wrapperStyle:{outline:"none"},isAnimationActive:!1,cursor:{stroke:"#d1d5db",strokeWidth:1},content:A?e=>{let{active:t,payload:n,label:r}=e;return K?o.createElement(K,{payload:null==n?void 0:n.map(e=>{var t;return Object.assign(Object.assign({},e),{color:null!==(t=er.get(e.dataKey))&&void 0!==t?t:x.fr.Gray})}),active:t,label:r}):o.createElement(v.ZP,{active:t,payload:n,label:r,valueFormatter:O,categoryColors:er})}:o.createElement(o.Fragment,null),position:{y:0}}),I?o.createElement(h.D,{verticalAlign:"top",height:Y,content:e=>{let{payload:t}=e;return(0,g.Z)({payload:t},er,Q,et,ei?e=>ea(e):void 0,W)}}):null,a.map(e=>{var t;return o.createElement(l.x,{className:(0,k.q)((0,S.bM)(null!==(t=er.get(e))&&void 0!==t?t:x.fr.Gray,w.K.text).strokeColor),strokeOpacity:J||et&&et!==e?.3:1,activeDot:e=>{var t;let{cx:r,cy:i,stroke:a,strokeLinecap:l,strokeLinejoin:s,strokeWidth:c,dataKey:u}=e;return o.createElement(m.o,{className:(0,k.q)("stroke-tremor-background dark:stroke-dark-tremor-background",U?"cursor-pointer":"",(0,S.bM)(null!==(t=er.get(u))&&void 0!==t?t:x.fr.Gray,w.K.text).fillColor),cx:r,cy:i,r:5,fill:"",stroke:a,strokeLinecap:l,strokeLinejoin:s,strokeWidth:c,onClick:(t,r)=>{r.stopPropagation(),ei&&(e.index===(null==J?void 0:J.index)&&e.dataKey===(null==J?void 0:J.dataKey)||(0,b.FB)(n,e.dataKey)&&et&&et===e.dataKey?(en(void 0),ee(void 0),null==U||U(null)):(en(e.dataKey),ee({index:e.index,dataKey:e.dataKey}),null==U||U(Object.assign({eventType:"dot",categoryClicked:e.dataKey},e.payload))))}})},dot:t=>{var r;let{stroke:i,strokeLinecap:a,strokeLinejoin:l,strokeWidth:s,cx:c,cy:u,dataKey:d,index:f}=t;return(0,b.FB)(n,e)&&!(J||et&&et!==e)||(null==J?void 0:J.index)===f&&(null==J?void 0:J.dataKey)===e?o.createElement(m.o,{key:f,cx:c,cy:u,r:5,stroke:i,fill:"",strokeLinecap:a,strokeLinejoin:l,strokeWidth:s,className:(0,k.q)("stroke-tremor-background dark:stroke-dark-tremor-background",U?"cursor-pointer":"",(0,S.bM)(null!==(r=er.get(d))&&void 0!==r?r:x.fr.Gray,w.K.text).fillColor)}):o.createElement(o.Fragment,{key:f})},key:e,name:e,type:L,dataKey:e,stroke:"",strokeWidth:2,strokeLinejoin:"round",strokeLinecap:"round",isAnimationActive:N,animationDuration:M,connectNulls:B})}),U?a.map(e=>o.createElement(l.x,{className:(0,k.q)("cursor-pointer"),strokeOpacity:0,key:e,name:e,type:L,dataKey:e,stroke:"transparent",fill:"transparent",legendType:"none",tooltipType:"none",strokeWidth:12,connectNulls:B,onClick:(e,t)=>{t.stopPropagation();let{name:n}=e;ea(n)}})):null):o.createElement(y.Z,{noDataText:H})))});E.displayName="LineChart"},65278:function(e,t,n){"use strict";n.d(t,{Z:function(){return m}});var r=n(2265);let o=(e,t)=>{let[n,o]=(0,r.useState)(t);(0,r.useEffect)(()=>{let t=()=>{o(window.innerWidth),e()};return t(),window.addEventListener("resize",t),()=>window.removeEventListener("resize",t)},[e,n])};var i=n(5853),a=n(26898),l=n(97324),s=n(1153);let c=e=>{var t=(0,i._T)(e,[]);return r.createElement("svg",Object.assign({},t,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"}),r.createElement("path",{d:"M8 12L14 6V18L8 12Z"}))},u=e=>{var t=(0,i._T)(e,[]);return r.createElement("svg",Object.assign({},t,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"}),r.createElement("path",{d:"M16 12L10 18V6L16 12Z"}))},d=(0,s.fn)("Legend"),f=e=>{let{name:t,color:n,onClick:o,activeLegend:i}=e,c=!!o;return r.createElement("li",{className:(0,l.q)(d("legendItem"),"group inline-flex items-center px-2 py-0.5 rounded-tremor-small transition whitespace-nowrap",c?"cursor-pointer":"cursor-default","text-tremor-content",c?"hover:bg-tremor-background-subtle":"","dark:text-dark-tremor-content",c?"dark:hover:bg-dark-tremor-background-subtle":""),onClick:e=>{e.stopPropagation(),null==o||o(t,n)}},r.createElement("svg",{className:(0,l.q)("flex-none h-2 w-2 mr-1.5",(0,s.bM)(n,a.K.text).textColor,i&&i!==t?"opacity-40":"opacity-100"),fill:"currentColor",viewBox:"0 0 8 8"},r.createElement("circle",{cx:4,cy:4,r:4})),r.createElement("p",{className:(0,l.q)("whitespace-nowrap truncate text-tremor-default","text-tremor-content",c?"group-hover:text-tremor-content-emphasis":"","dark:text-dark-tremor-content",i&&i!==t?"opacity-40":"opacity-100",c?"dark:group-hover:text-dark-tremor-content-emphasis":"")},t))},p=e=>{let{icon:t,onClick:n,disabled:o}=e,[i,a]=r.useState(!1),s=r.useRef(null);return r.useEffect(()=>(i?s.current=setInterval(()=>{null==n||n()},300):clearInterval(s.current),()=>clearInterval(s.current)),[i,n]),(0,r.useEffect)(()=>{o&&(clearInterval(s.current),a(!1))},[o]),r.createElement("button",{type:"button",className:(0,l.q)(d("legendSliderButton"),"w-5 group inline-flex items-center truncate rounded-tremor-small transition",o?"cursor-not-allowed":"cursor-pointer",o?"text-tremor-content-subtle":"text-tremor-content hover:text-tremor-content-emphasis hover:bg-tremor-background-subtle",o?"dark:text-dark-tremor-subtle":"dark:text-dark-tremor dark:hover:text-tremor-content-emphasis dark:hover:bg-dark-tremor-background-subtle"),disabled:o,onClick:e=>{e.stopPropagation(),null==n||n()},onMouseDown:e=>{e.stopPropagation(),a(!0)},onMouseUp:e=>{e.stopPropagation(),a(!1)}},r.createElement(t,{className:"w-full"}))},h=r.forwardRef((e,t)=>{var n,o;let{categories:s,colors:h=a.s,className:m,onClickLegendItem:g,activeLegend:v,enableLegendSlider:y=!1}=e,b=(0,i._T)(e,["categories","colors","className","onClickLegendItem","activeLegend","enableLegendSlider"]),x=r.useRef(null),[w,k]=r.useState(null),[S,E]=r.useState(null),O=r.useRef(null),C=(0,r.useCallback)(()=>{let e=null==x?void 0:x.current;e&&k({left:e.scrollLeft>0,right:e.scrollWidth-e.clientWidth>e.scrollLeft})},[k]),j=(0,r.useCallback)(e=>{var t;let n=null==x?void 0:x.current,r=null!==(t=null==n?void 0:n.clientWidth)&&void 0!==t?t:0;n&&y&&(n.scrollTo({left:"left"===e?n.scrollLeft-r:n.scrollLeft+r,behavior:"smooth"}),setTimeout(()=>{C()},400))},[y,C]);r.useEffect(()=>{let e=e=>{"ArrowLeft"===e?j("left"):"ArrowRight"===e&&j("right")};return S?(e(S),O.current=setInterval(()=>{e(S)},300)):clearInterval(O.current),()=>clearInterval(O.current)},[S,j]);let _=e=>{e.stopPropagation(),"ArrowLeft"!==e.key&&"ArrowRight"!==e.key||(e.preventDefault(),E(e.key))},P=e=>{e.stopPropagation(),E(null)};return r.useEffect(()=>{let e=null==x?void 0:x.current;return y&&(C(),null==e||e.addEventListener("keydown",_),null==e||e.addEventListener("keyup",P)),()=>{null==e||e.removeEventListener("keydown",_),null==e||e.removeEventListener("keyup",P)}},[C,y]),r.createElement("ol",Object.assign({ref:t,className:(0,l.q)(d("root"),"relative overflow-hidden",m)},b),r.createElement("div",{ref:x,tabIndex:0,className:(0,l.q)("h-full flex",y?(null==w?void 0:w.right)||(null==w?void 0:w.left)?"pl-4 pr-12 items-center overflow-auto snap-mandatory [&::-webkit-scrollbar]:hidden [scrollbar-width:none]":"":"flex-wrap")},s.map((e,t)=>r.createElement(f,{key:"item-".concat(t),name:e,color:h[t],onClick:g,activeLegend:v}))),y&&((null==w?void 0:w.right)||(null==w?void 0:w.left))?r.createElement(r.Fragment,null,r.createElement("div",{className:(0,l.q)("from-tremor-background","dark:from-dark-tremor-background","absolute top-0 bottom-0 left-0 w-4 bg-gradient-to-r to-transparent pointer-events-none")}),r.createElement("div",{className:(0,l.q)("to-tremor-background","dark:to-dark-tremor-background","absolute top-0 bottom-0 right-10 w-4 bg-gradient-to-r from-transparent pointer-events-none")}),r.createElement("div",{className:(0,l.q)("bg-tremor-background","dark:bg-dark-tremor-background","absolute flex top-0 pr-1 bottom-0 right-0 items-center justify-center h-full")},r.createElement(p,{icon:c,onClick:()=>{E(null),j("left")},disabled:!(null==w?void 0:w.left)}),r.createElement(p,{icon:u,onClick:()=>{E(null),j("right")},disabled:!(null==w?void 0:w.right)}))):null)});h.displayName="Legend";let m=(e,t,n,i,a,l)=>{let{payload:s}=e,c=(0,r.useRef)(null);o(()=>{var e,t;n((t=null===(e=c.current)||void 0===e?void 0:e.clientHeight)?Number(t)+20:60)});let u=s.filter(e=>"none"!==e.type);return r.createElement("div",{ref:c,className:"flex items-center justify-end"},r.createElement(h,{categories:u.map(e=>e.value),colors:u.map(e=>t.get(e.value)),onClickLegendItem:a,activeLegend:i,enableLegendSlider:l}))}},98593:function(e,t,n){"use strict";n.d(t,{$B:function(){return s},ZP:function(){return u},zX:function(){return c}});var r=n(2265),o=n(7084),i=n(26898),a=n(97324),l=n(1153);let s=e=>{let{children:t}=e;return r.createElement("div",{className:(0,a.q)("rounded-tremor-default text-tremor-default border","bg-tremor-background shadow-tremor-dropdown border-tremor-border","dark:bg-dark-tremor-background dark:shadow-dark-tremor-dropdown dark:border-dark-tremor-border")},t)},c=e=>{let{value:t,name:n,color:o}=e;return r.createElement("div",{className:"flex items-center justify-between space-x-8"},r.createElement("div",{className:"flex items-center space-x-2"},r.createElement("span",{className:(0,a.q)("shrink-0 rounded-tremor-full border-2 h-3 w-3","border-tremor-background shadow-tremor-card","dark:border-dark-tremor-background dark:shadow-dark-tremor-card",(0,l.bM)(o,i.K.background).bgColor)}),r.createElement("p",{className:(0,a.q)("text-right whitespace-nowrap","text-tremor-content","dark:text-dark-tremor-content")},n)),r.createElement("p",{className:(0,a.q)("font-medium tabular-nums text-right whitespace-nowrap","text-tremor-content-emphasis","dark:text-dark-tremor-content-emphasis")},t))},u=e=>{let{active:t,payload:n,label:i,categoryColors:l,valueFormatter:u}=e;if(t&&n){let e=n.filter(e=>"none"!==e.type);return r.createElement(s,null,r.createElement("div",{className:(0,a.q)("border-tremor-border border-b px-4 py-2","dark:border-dark-tremor-border")},r.createElement("p",{className:(0,a.q)("font-medium","text-tremor-content-emphasis","dark:text-dark-tremor-content-emphasis")},i)),r.createElement("div",{className:(0,a.q)("px-4 py-2 space-y-1")},e.map((e,t)=>{var n;let{value:i,name:a}=e;return r.createElement(c,{key:"id-".concat(t),value:u(i),name:a,color:null!==(n=l.get(a))&&void 0!==n?n:o.fr.Blue})})))}return null}},69448:function(e,t,n){"use strict";n.d(t,{Z:function(){return f}});var r=n(97324),o=n(2265),i=n(5853);let a=(0,n(1153).fn)("Flex"),l={start:"justify-start",end:"justify-end",center:"justify-center",between:"justify-between",around:"justify-around",evenly:"justify-evenly"},s={start:"items-start",end:"items-end",center:"items-center",baseline:"items-baseline",stretch:"items-stretch"},c={row:"flex-row",col:"flex-col","row-reverse":"flex-row-reverse","col-reverse":"flex-col-reverse"},u=o.forwardRef((e,t)=>{let{flexDirection:n="row",justifyContent:u="between",alignItems:d="center",children:f,className:p}=e,h=(0,i._T)(e,["flexDirection","justifyContent","alignItems","children","className"]);return o.createElement("div",Object.assign({ref:t,className:(0,r.q)(a("root"),"flex w-full",c[n],l[u],s[d],p)},h),f)});u.displayName="Flex";var d=n(84264);let f=e=>{let{noDataText:t="No data"}=e;return o.createElement(u,{alignItems:"center",justifyContent:"center",className:(0,r.q)("w-full h-full border border-dashed rounded-tremor-default","border-tremor-border","dark:border-dark-tremor-border")},o.createElement(d.Z,{className:(0,r.q)("text-tremor-content","dark:text-dark-tremor-content")},t))}},32644:function(e,t,n){"use strict";n.d(t,{FB:function(){return i},i4:function(){return o},me:function(){return r},vZ:function(){return function e(t,n){if(t===n)return!0;if("object"!=typeof t||"object"!=typeof n||null===t||null===n)return!1;let r=Object.keys(t),o=Object.keys(n);if(r.length!==o.length)return!1;for(let i of r)if(!o.includes(i)||!e(t[i],n[i]))return!1;return!0}}});let r=(e,t)=>{let n=new Map;return e.forEach((e,r)=>{n.set(e,t[r])}),n},o=(e,t,n)=>[e?"auto":null!=t?t:0,null!=n?n:"auto"];function i(e,t){let n=[];for(let r of e)if(Object.prototype.hasOwnProperty.call(r,t)&&(n.push(r[t]),n.length>1))return!1;return!0}},47323:function(e,t,n){"use strict";n.d(t,{Z:function(){return m}});var r=n(5853),o=n(2265),i=n(1526),a=n(7084),l=n(97324),s=n(1153),c=n(26898);let u={xs:{paddingX:"px-1.5",paddingY:"py-1.5"},sm:{paddingX:"px-1.5",paddingY:"py-1.5"},md:{paddingX:"px-2",paddingY:"py-2"},lg:{paddingX:"px-2",paddingY:"py-2"},xl:{paddingX:"px-2.5",paddingY:"py-2.5"}},d={xs:{height:"h-3",width:"w-3"},sm:{height:"h-5",width:"w-5"},md:{height:"h-5",width:"w-5"},lg:{height:"h-7",width:"w-7"},xl:{height:"h-9",width:"w-9"}},f={simple:{rounded:"",border:"",ring:"",shadow:""},light:{rounded:"rounded-tremor-default",border:"",ring:"",shadow:""},shadow:{rounded:"rounded-tremor-default",border:"border",ring:"",shadow:"shadow-tremor-card dark:shadow-dark-tremor-card"},solid:{rounded:"rounded-tremor-default",border:"border-2",ring:"ring-1",shadow:""},outlined:{rounded:"rounded-tremor-default",border:"border",ring:"ring-2",shadow:""}},p=(e,t)=>{switch(e){case"simple":return{textColor:t?(0,s.bM)(t,c.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:"",borderColor:"",ringColor:""};case"light":return{textColor:t?(0,s.bM)(t,c.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,l.q)((0,s.bM)(t,c.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand-muted dark:bg-dark-tremor-brand-muted",borderColor:"",ringColor:""};case"shadow":return{textColor:t?(0,s.bM)(t,c.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,l.q)((0,s.bM)(t,c.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:"border-tremor-border dark:border-dark-tremor-border",ringColor:""};case"solid":return{textColor:t?(0,s.bM)(t,c.K.text).textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",bgColor:t?(0,l.q)((0,s.bM)(t,c.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand dark:bg-dark-tremor-brand",borderColor:"border-tremor-brand-inverted dark:border-dark-tremor-brand-inverted",ringColor:"ring-tremor-ring dark:ring-dark-tremor-ring"};case"outlined":return{textColor:t?(0,s.bM)(t,c.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,l.q)((0,s.bM)(t,c.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:t?(0,s.bM)(t,c.K.ring).borderColor:"border-tremor-brand-subtle dark:border-dark-tremor-brand-subtle",ringColor:t?(0,l.q)((0,s.bM)(t,c.K.ring).ringColor,"ring-opacity-40"):"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"}}},h=(0,s.fn)("Icon"),m=o.forwardRef((e,t)=>{let{icon:n,variant:c="simple",tooltip:m,size:g=a.u8.SM,color:v,className:y}=e,b=(0,r._T)(e,["icon","variant","tooltip","size","color","className"]),x=p(c,v),{tooltipProps:w,getReferenceProps:k}=(0,i.l)();return o.createElement("span",Object.assign({ref:(0,s.lq)([t,w.refs.setReference]),className:(0,l.q)(h("root"),"inline-flex flex-shrink-0 items-center",x.bgColor,x.textColor,x.borderColor,x.ringColor,f[c].rounded,f[c].border,f[c].shadow,f[c].ring,u[g].paddingX,u[g].paddingY,y)},k,b),o.createElement(i.Z,Object.assign({text:m},w)),o.createElement(n,{className:(0,l.q)(h("icon"),"shrink-0",d[g].height,d[g].width)}))});m.displayName="Icon"},21487:function(e,t,n){"use strict";let r,o,i;n.d(t,{Z:function(){return nF}});var a,l,s,c,u=n(5853),d=n(2265),f=n(54887),p=n(13323),h=n(64518),m=n(96822),g=n(40048),v=n(72238),y=n(93689);let b=(0,d.createContext)(!1);var x=n(61424),w=n(27847);let k=d.Fragment,S=d.Fragment,E=(0,d.createContext)(null),O=(0,d.createContext)(null);Object.assign((0,w.yV)(function(e,t){var n;let r,o,i=(0,d.useRef)(null),a=(0,y.T)((0,y.h)(e=>{i.current=e}),t),l=(0,g.i)(i),s=function(e){let t=(0,d.useContext)(b),n=(0,d.useContext)(E),r=(0,g.i)(e),[o,i]=(0,d.useState)(()=>{if(!t&&null!==n||x.O.isServer)return null;let e=null==r?void 0:r.getElementById("headlessui-portal-root");if(e)return e;if(null===r)return null;let o=r.createElement("div");return o.setAttribute("id","headlessui-portal-root"),r.body.appendChild(o)});return(0,d.useEffect)(()=>{null!==o&&(null!=r&&r.body.contains(o)||null==r||r.body.appendChild(o))},[o,r]),(0,d.useEffect)(()=>{t||null!==n&&i(n.current)},[n,i,t]),o}(i),[c]=(0,d.useState)(()=>{var e;return x.O.isServer?null:null!=(e=null==l?void 0:l.createElement("div"))?e:null}),u=(0,d.useContext)(O),S=(0,v.H)();return(0,h.e)(()=>{!s||!c||s.contains(c)||(c.setAttribute("data-headlessui-portal",""),s.appendChild(c))},[s,c]),(0,h.e)(()=>{if(c&&u)return u.register(c)},[u,c]),n=()=>{var e;s&&c&&(c instanceof Node&&s.contains(c)&&s.removeChild(c),s.childNodes.length<=0&&(null==(e=s.parentElement)||e.removeChild(s)))},r=(0,p.z)(n),o=(0,d.useRef)(!1),(0,d.useEffect)(()=>(o.current=!1,()=>{o.current=!0,(0,m.Y)(()=>{o.current&&r()})}),[r]),S&&s&&c?(0,f.createPortal)((0,w.sY)({ourProps:{ref:a},theirProps:e,defaultTag:k,name:"Portal"}),c):null}),{Group:(0,w.yV)(function(e,t){let{target:n,...r}=e,o={ref:(0,y.T)(t)};return d.createElement(E.Provider,{value:n},(0,w.sY)({ourProps:o,theirProps:r,defaultTag:S,name:"Popover.Group"}))})});var C=n(31948),j=n(17684),_=n(32539),P=n(80004),T=n(38198),M=n(3141),N=((r=N||{})[r.Forwards=0]="Forwards",r[r.Backwards=1]="Backwards",r);function A(){let e=(0,d.useRef)(0);return(0,M.s)("keydown",t=>{"Tab"===t.key&&(e.current=t.shiftKey?1:0)},!0),e}var I=n(37863),R=n(47634),D=n(37105),L=n(24536),z=n(40293),Z=n(37388),B=((o=B||{})[o.Open=0]="Open",o[o.Closed=1]="Closed",o),F=((i=F||{})[i.TogglePopover=0]="TogglePopover",i[i.ClosePopover=1]="ClosePopover",i[i.SetButton=2]="SetButton",i[i.SetButtonId=3]="SetButtonId",i[i.SetPanel=4]="SetPanel",i[i.SetPanelId=5]="SetPanelId",i);let H={0:e=>{let t={...e,popoverState:(0,L.E)(e.popoverState,{0:1,1:0})};return 0===t.popoverState&&(t.__demoMode=!1),t},1:e=>1===e.popoverState?e:{...e,popoverState:1},2:(e,t)=>e.button===t.button?e:{...e,button:t.button},3:(e,t)=>e.buttonId===t.buttonId?e:{...e,buttonId:t.buttonId},4:(e,t)=>e.panel===t.panel?e:{...e,panel:t.panel},5:(e,t)=>e.panelId===t.panelId?e:{...e,panelId:t.panelId}},q=(0,d.createContext)(null);function U(e){let t=(0,d.useContext)(q);if(null===t){let t=Error("<".concat(e," /> is missing a parent component."));throw Error.captureStackTrace&&Error.captureStackTrace(t,U),t}return t}q.displayName="PopoverContext";let W=(0,d.createContext)(null);function K(e){let t=(0,d.useContext)(W);if(null===t){let t=Error("<".concat(e," /> is missing a parent component."));throw Error.captureStackTrace&&Error.captureStackTrace(t,K),t}return t}W.displayName="PopoverAPIContext";let V=(0,d.createContext)(null);function $(){return(0,d.useContext)(V)}V.displayName="PopoverGroupContext";let X=(0,d.createContext)(null);function G(e,t){return(0,L.E)(t.type,H,e,t)}X.displayName="PopoverPanelContext";let Y=w.AN.RenderStrategy|w.AN.Static,Q=w.AN.RenderStrategy|w.AN.Static,J=Object.assign((0,w.yV)(function(e,t){var n,r,o,i;let a,l,s,c,u,f;let{__demoMode:h=!1,...m}=e,v=(0,d.useRef)(null),b=(0,y.T)(t,(0,y.h)(e=>{v.current=e})),x=(0,d.useRef)([]),k=(0,d.useReducer)(G,{__demoMode:h,popoverState:h?0:1,buttons:x,button:null,buttonId:null,panel:null,panelId:null,beforePanelSentinel:(0,d.createRef)(),afterPanelSentinel:(0,d.createRef)()}),[{popoverState:S,button:E,buttonId:j,panel:P,panelId:M,beforePanelSentinel:N,afterPanelSentinel:A},R]=k,z=(0,g.i)(null!=(n=v.current)?n:E),Z=(0,d.useMemo)(()=>{if(!E||!P)return!1;for(let e of document.querySelectorAll("body > *"))if(Number(null==e?void 0:e.contains(E))^Number(null==e?void 0:e.contains(P)))return!0;let e=(0,D.GO)(),t=e.indexOf(E),n=(t+e.length-1)%e.length,r=(t+1)%e.length,o=e[n],i=e[r];return!P.contains(o)&&!P.contains(i)},[E,P]),B=(0,C.E)(j),F=(0,C.E)(M),H=(0,d.useMemo)(()=>({buttonId:B,panelId:F,close:()=>R({type:1})}),[B,F,R]),U=$(),K=null==U?void 0:U.registerPopover,V=(0,p.z)(()=>{var e;return null!=(e=null==U?void 0:U.isFocusWithinPopoverGroup())?e:(null==z?void 0:z.activeElement)&&((null==E?void 0:E.contains(z.activeElement))||(null==P?void 0:P.contains(z.activeElement)))});(0,d.useEffect)(()=>null==K?void 0:K(H),[K,H]);let[Y,Q]=(a=(0,d.useContext)(O),l=(0,d.useRef)([]),s=(0,p.z)(e=>(l.current.push(e),a&&a.register(e),()=>c(e))),c=(0,p.z)(e=>{let t=l.current.indexOf(e);-1!==t&&l.current.splice(t,1),a&&a.unregister(e)}),u=(0,d.useMemo)(()=>({register:s,unregister:c,portals:l}),[s,c,l]),[l,(0,d.useMemo)(()=>function(e){let{children:t}=e;return d.createElement(O.Provider,{value:u},t)},[u])]),J=function(){var e;let{defaultContainers:t=[],portals:n,mainTreeNodeRef:r}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},o=(0,d.useRef)(null!=(e=null==r?void 0:r.current)?e:null),i=(0,g.i)(o),a=(0,p.z)(()=>{var e,r,a;let l=[];for(let e of t)null!==e&&(e instanceof HTMLElement?l.push(e):"current"in e&&e.current instanceof HTMLElement&&l.push(e.current));if(null!=n&&n.current)for(let e of n.current)l.push(e);for(let t of null!=(e=null==i?void 0:i.querySelectorAll("html > *, body > *"))?e:[])t!==document.body&&t!==document.head&&t instanceof HTMLElement&&"headlessui-portal-root"!==t.id&&(t.contains(o.current)||t.contains(null==(a=null==(r=o.current)?void 0:r.getRootNode())?void 0:a.host)||l.some(e=>t.contains(e))||l.push(t));return l});return{resolveContainers:a,contains:(0,p.z)(e=>a().some(t=>t.contains(e))),mainTreeNodeRef:o,MainTreeNode:(0,d.useMemo)(()=>function(){return null!=r?null:d.createElement(T._,{features:T.A.Hidden,ref:o})},[o,r])}}({mainTreeNodeRef:null==U?void 0:U.mainTreeNodeRef,portals:Y,defaultContainers:[E,P]});r=null==z?void 0:z.defaultView,o="focus",i=e=>{var t,n,r,o;e.target!==window&&e.target instanceof HTMLElement&&0===S&&(V()||E&&P&&(J.contains(e.target)||null!=(n=null==(t=N.current)?void 0:t.contains)&&n.call(t,e.target)||null!=(o=null==(r=A.current)?void 0:r.contains)&&o.call(r,e.target)||R({type:1})))},f=(0,C.E)(i),(0,d.useEffect)(()=>{function e(e){f.current(e)}return(r=null!=r?r:window).addEventListener(o,e,!0),()=>r.removeEventListener(o,e,!0)},[r,o,!0]),(0,_.O)(J.resolveContainers,(e,t)=>{R({type:1}),(0,D.sP)(t,D.tJ.Loose)||(e.preventDefault(),null==E||E.focus())},0===S);let ee=(0,p.z)(e=>{R({type:1});let t=e?e instanceof HTMLElement?e:"current"in e&&e.current instanceof HTMLElement?e.current:E:E;null==t||t.focus()}),et=(0,d.useMemo)(()=>({close:ee,isPortalled:Z}),[ee,Z]),en=(0,d.useMemo)(()=>({open:0===S,close:ee}),[S,ee]);return d.createElement(X.Provider,{value:null},d.createElement(q.Provider,{value:k},d.createElement(W.Provider,{value:et},d.createElement(I.up,{value:(0,L.E)(S,{0:I.ZM.Open,1:I.ZM.Closed})},d.createElement(Q,null,(0,w.sY)({ourProps:{ref:b},theirProps:m,slot:en,defaultTag:"div",name:"Popover"}),d.createElement(J.MainTreeNode,null))))))}),{Button:(0,w.yV)(function(e,t){let n=(0,j.M)(),{id:r="headlessui-popover-button-".concat(n),...o}=e,[i,a]=U("Popover.Button"),{isPortalled:l}=K("Popover.Button"),s=(0,d.useRef)(null),c="headlessui-focus-sentinel-".concat((0,j.M)()),u=$(),f=null==u?void 0:u.closeOthers,h=null!==(0,d.useContext)(X);(0,d.useEffect)(()=>{if(!h)return a({type:3,buttonId:r}),()=>{a({type:3,buttonId:null})}},[h,r,a]);let[m]=(0,d.useState)(()=>Symbol()),v=(0,y.T)(s,t,h?null:e=>{if(e)i.buttons.current.push(m);else{let e=i.buttons.current.indexOf(m);-1!==e&&i.buttons.current.splice(e,1)}i.buttons.current.length>1&&console.warn("You are already using a but only 1 is supported."),e&&a({type:2,button:e})}),b=(0,y.T)(s,t),x=(0,g.i)(s),k=(0,p.z)(e=>{var t,n,r;if(h){if(1===i.popoverState)return;switch(e.key){case Z.R.Space:case Z.R.Enter:e.preventDefault(),null==(n=(t=e.target).click)||n.call(t),a({type:1}),null==(r=i.button)||r.focus()}}else switch(e.key){case Z.R.Space:case Z.R.Enter:e.preventDefault(),e.stopPropagation(),1===i.popoverState&&(null==f||f(i.buttonId)),a({type:0});break;case Z.R.Escape:if(0!==i.popoverState)return null==f?void 0:f(i.buttonId);if(!s.current||null!=x&&x.activeElement&&!s.current.contains(x.activeElement))return;e.preventDefault(),e.stopPropagation(),a({type:1})}}),S=(0,p.z)(e=>{h||e.key===Z.R.Space&&e.preventDefault()}),E=(0,p.z)(t=>{var n,r;(0,R.P)(t.currentTarget)||e.disabled||(h?(a({type:1}),null==(n=i.button)||n.focus()):(t.preventDefault(),t.stopPropagation(),1===i.popoverState&&(null==f||f(i.buttonId)),a({type:0}),null==(r=i.button)||r.focus()))}),O=(0,p.z)(e=>{e.preventDefault(),e.stopPropagation()}),C=0===i.popoverState,_=(0,d.useMemo)(()=>({open:C}),[C]),M=(0,P.f)(e,s),I=h?{ref:b,type:M,onKeyDown:k,onClick:E}:{ref:v,id:i.buttonId,type:M,"aria-expanded":0===i.popoverState,"aria-controls":i.panel?i.panelId:void 0,onKeyDown:k,onKeyUp:S,onClick:E,onMouseDown:O},z=A(),B=(0,p.z)(()=>{let e=i.panel;e&&(0,L.E)(z.current,{[N.Forwards]:()=>(0,D.jA)(e,D.TO.First),[N.Backwards]:()=>(0,D.jA)(e,D.TO.Last)})===D.fE.Error&&(0,D.jA)((0,D.GO)().filter(e=>"true"!==e.dataset.headlessuiFocusGuard),(0,L.E)(z.current,{[N.Forwards]:D.TO.Next,[N.Backwards]:D.TO.Previous}),{relativeTo:i.button})});return d.createElement(d.Fragment,null,(0,w.sY)({ourProps:I,theirProps:o,slot:_,defaultTag:"button",name:"Popover.Button"}),C&&!h&&l&&d.createElement(T._,{id:c,features:T.A.Focusable,"data-headlessui-focus-guard":!0,as:"button",type:"button",onFocus:B}))}),Overlay:(0,w.yV)(function(e,t){let n=(0,j.M)(),{id:r="headlessui-popover-overlay-".concat(n),...o}=e,[{popoverState:i},a]=U("Popover.Overlay"),l=(0,y.T)(t),s=(0,I.oJ)(),c=null!==s?(s&I.ZM.Open)===I.ZM.Open:0===i,u=(0,p.z)(e=>{if((0,R.P)(e.currentTarget))return e.preventDefault();a({type:1})}),f=(0,d.useMemo)(()=>({open:0===i}),[i]);return(0,w.sY)({ourProps:{ref:l,id:r,"aria-hidden":!0,onClick:u},theirProps:o,slot:f,defaultTag:"div",features:Y,visible:c,name:"Popover.Overlay"})}),Panel:(0,w.yV)(function(e,t){let n=(0,j.M)(),{id:r="headlessui-popover-panel-".concat(n),focus:o=!1,...i}=e,[a,l]=U("Popover.Panel"),{close:s,isPortalled:c}=K("Popover.Panel"),u="headlessui-focus-sentinel-before-".concat((0,j.M)()),f="headlessui-focus-sentinel-after-".concat((0,j.M)()),m=(0,d.useRef)(null),v=(0,y.T)(m,t,e=>{l({type:4,panel:e})}),b=(0,g.i)(m),x=(0,w.Y2)();(0,h.e)(()=>(l({type:5,panelId:r}),()=>{l({type:5,panelId:null})}),[r,l]);let k=(0,I.oJ)(),S=null!==k?(k&I.ZM.Open)===I.ZM.Open:0===a.popoverState,E=(0,p.z)(e=>{var t;if(e.key===Z.R.Escape){if(0!==a.popoverState||!m.current||null!=b&&b.activeElement&&!m.current.contains(b.activeElement))return;e.preventDefault(),e.stopPropagation(),l({type:1}),null==(t=a.button)||t.focus()}});(0,d.useEffect)(()=>{var t;e.static||1===a.popoverState&&(null==(t=e.unmount)||t)&&l({type:4,panel:null})},[a.popoverState,e.unmount,e.static,l]),(0,d.useEffect)(()=>{if(a.__demoMode||!o||0!==a.popoverState||!m.current)return;let e=null==b?void 0:b.activeElement;m.current.contains(e)||(0,D.jA)(m.current,D.TO.First)},[a.__demoMode,o,m,a.popoverState]);let O=(0,d.useMemo)(()=>({open:0===a.popoverState,close:s}),[a,s]),C={ref:v,id:r,onKeyDown:E,onBlur:o&&0===a.popoverState?e=>{var t,n,r,o,i;let s=e.relatedTarget;s&&m.current&&(null!=(t=m.current)&&t.contains(s)||(l({type:1}),(null!=(r=null==(n=a.beforePanelSentinel.current)?void 0:n.contains)&&r.call(n,s)||null!=(i=null==(o=a.afterPanelSentinel.current)?void 0:o.contains)&&i.call(o,s))&&s.focus({preventScroll:!0})))}:void 0,tabIndex:-1},_=A(),P=(0,p.z)(()=>{let e=m.current;e&&(0,L.E)(_.current,{[N.Forwards]:()=>{var t;(0,D.jA)(e,D.TO.First)===D.fE.Error&&(null==(t=a.afterPanelSentinel.current)||t.focus())},[N.Backwards]:()=>{var e;null==(e=a.button)||e.focus({preventScroll:!0})}})}),M=(0,p.z)(()=>{let e=m.current;e&&(0,L.E)(_.current,{[N.Forwards]:()=>{var e;if(!a.button)return;let t=(0,D.GO)(),n=t.indexOf(a.button),r=t.slice(0,n+1),o=[...t.slice(n+1),...r];for(let t of o.slice())if("true"===t.dataset.headlessuiFocusGuard||null!=(e=a.panel)&&e.contains(t)){let e=o.indexOf(t);-1!==e&&o.splice(e,1)}(0,D.jA)(o,D.TO.First,{sorted:!1})},[N.Backwards]:()=>{var t;(0,D.jA)(e,D.TO.Previous)===D.fE.Error&&(null==(t=a.button)||t.focus())}})});return d.createElement(X.Provider,{value:r},S&&c&&d.createElement(T._,{id:u,ref:a.beforePanelSentinel,features:T.A.Focusable,"data-headlessui-focus-guard":!0,as:"button",type:"button",onFocus:P}),(0,w.sY)({mergeRefs:x,ourProps:C,theirProps:i,slot:O,defaultTag:"div",features:Q,visible:S,name:"Popover.Panel"}),S&&c&&d.createElement(T._,{id:f,ref:a.afterPanelSentinel,features:T.A.Focusable,"data-headlessui-focus-guard":!0,as:"button",type:"button",onFocus:M}))}),Group:(0,w.yV)(function(e,t){let n;let r=(0,d.useRef)(null),o=(0,y.T)(r,t),[i,a]=(0,d.useState)([]),l={mainTreeNodeRef:n=(0,d.useRef)(null),MainTreeNode:(0,d.useMemo)(()=>function(){return d.createElement(T._,{features:T.A.Hidden,ref:n})},[n])},s=(0,p.z)(e=>{a(t=>{let n=t.indexOf(e);if(-1!==n){let e=t.slice();return e.splice(n,1),e}return t})}),c=(0,p.z)(e=>(a(t=>[...t,e]),()=>s(e))),u=(0,p.z)(()=>{var e;let t=(0,z.r)(r);if(!t)return!1;let n=t.activeElement;return!!(null!=(e=r.current)&&e.contains(n))||i.some(e=>{var r,o;return(null==(r=t.getElementById(e.buttonId.current))?void 0:r.contains(n))||(null==(o=t.getElementById(e.panelId.current))?void 0:o.contains(n))})}),f=(0,p.z)(e=>{for(let t of i)t.buttonId.current!==e&&t.close()}),h=(0,d.useMemo)(()=>({registerPopover:c,unregisterPopover:s,isFocusWithinPopoverGroup:u,closeOthers:f,mainTreeNodeRef:l.mainTreeNodeRef}),[c,s,u,f,l.mainTreeNodeRef]),m=(0,d.useMemo)(()=>({}),[]);return d.createElement(V.Provider,{value:h},(0,w.sY)({ourProps:{ref:o},theirProps:e,slot:m,defaultTag:"div",name:"Popover.Group"}),d.createElement(l.MainTreeNode,null))})});var ee=n(33044),et=n(9528);let en=e=>{var t=(0,u._T)(e,[]);return d.createElement("svg",Object.assign({},t,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",fill:"currentColor"}),d.createElement("path",{fillRule:"evenodd",d:"M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z",clipRule:"evenodd"}))};var er=n(4537),eo=n(99735),ei=n(7656);function ea(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e);return t.setHours(0,0,0,0),t}function el(){return ea(Date.now())}function es(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e);return t.setDate(1),t.setHours(0,0,0,0),t}var ec=n(97324),eu=n(96398),ed=n(41154);function ef(e){var t,n;if((0,ei.Z)(1,arguments),e&&"function"==typeof e.forEach)t=e;else{if("object"!==(0,ed.Z)(e)||null===e)return new Date(NaN);t=Array.prototype.slice.call(e)}return t.forEach(function(e){var t=(0,eo.Z)(e);(void 0===n||nt||isNaN(t.getDate()))&&(n=t)}),n||new Date(NaN)}var eh=n(25721),em=n(47869);function eg(e,t){(0,ei.Z)(2,arguments);var n=(0,em.Z)(t);return(0,eh.Z)(e,-n)}var ev=n(55463);function ey(e,t){if((0,ei.Z)(2,arguments),!t||"object"!==(0,ed.Z)(t))return new Date(NaN);var n=t.years?(0,em.Z)(t.years):0,r=t.months?(0,em.Z)(t.months):0,o=t.weeks?(0,em.Z)(t.weeks):0,i=t.days?(0,em.Z)(t.days):0,a=t.hours?(0,em.Z)(t.hours):0,l=t.minutes?(0,em.Z)(t.minutes):0,s=t.seconds?(0,em.Z)(t.seconds):0;return new Date(eg(function(e,t){(0,ei.Z)(2,arguments);var n=(0,em.Z)(t);return(0,ev.Z)(e,-n)}(e,r+12*n),i+7*o).getTime()-1e3*(s+60*(l+60*a)))}function eb(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e),n=new Date(0);return n.setFullYear(t.getFullYear(),0,1),n.setHours(0,0,0,0),n}function ex(e){return(0,ei.Z)(1,arguments),e instanceof Date||"object"===(0,ed.Z)(e)&&"[object Date]"===Object.prototype.toString.call(e)}function ew(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e),n=t.getUTCDay();return t.setUTCDate(t.getUTCDate()-((n<1?7:0)+n-1)),t.setUTCHours(0,0,0,0),t}function ek(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e),n=t.getUTCFullYear(),r=new Date(0);r.setUTCFullYear(n+1,0,4),r.setUTCHours(0,0,0,0);var o=ew(r),i=new Date(0);i.setUTCFullYear(n,0,4),i.setUTCHours(0,0,0,0);var a=ew(i);return t.getTime()>=o.getTime()?n+1:t.getTime()>=a.getTime()?n:n-1}var eS={};function eE(e,t){(0,ei.Z)(1,arguments);var n,r,o,i,a,l,s,c,u=(0,em.Z)(null!==(n=null!==(r=null!==(o=null!==(i=null==t?void 0:t.weekStartsOn)&&void 0!==i?i:null==t?void 0:null===(a=t.locale)||void 0===a?void 0:null===(l=a.options)||void 0===l?void 0:l.weekStartsOn)&&void 0!==o?o:eS.weekStartsOn)&&void 0!==r?r:null===(s=eS.locale)||void 0===s?void 0:null===(c=s.options)||void 0===c?void 0:c.weekStartsOn)&&void 0!==n?n:0);if(!(u>=0&&u<=6))throw RangeError("weekStartsOn must be between 0 and 6 inclusively");var d=(0,eo.Z)(e),f=d.getUTCDay();return d.setUTCDate(d.getUTCDate()-((f=1&&f<=7))throw RangeError("firstWeekContainsDate must be between 1 and 7 inclusively");var p=new Date(0);p.setUTCFullYear(d+1,0,f),p.setUTCHours(0,0,0,0);var h=eE(p,t),m=new Date(0);m.setUTCFullYear(d,0,f),m.setUTCHours(0,0,0,0);var g=eE(m,t);return u.getTime()>=h.getTime()?d+1:u.getTime()>=g.getTime()?d:d-1}function eC(e,t){for(var n=Math.abs(e).toString();n.length0?n:1-n;return eC("yy"===t?r%100:r,t.length)},M:function(e,t){var n=e.getUTCMonth();return"M"===t?String(n+1):eC(n+1,2)},d:function(e,t){return eC(e.getUTCDate(),t.length)},h:function(e,t){return eC(e.getUTCHours()%12||12,t.length)},H:function(e,t){return eC(e.getUTCHours(),t.length)},m:function(e,t){return eC(e.getUTCMinutes(),t.length)},s:function(e,t){return eC(e.getUTCSeconds(),t.length)},S:function(e,t){var n=t.length;return eC(Math.floor(e.getUTCMilliseconds()*Math.pow(10,n-3)),t.length)}},e_={midnight:"midnight",noon:"noon",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"};function eP(e,t){var n=e>0?"-":"+",r=Math.abs(e),o=Math.floor(r/60),i=r%60;return 0===i?n+String(o):n+String(o)+(t||"")+eC(i,2)}function eT(e,t){return e%60==0?(e>0?"-":"+")+eC(Math.abs(e)/60,2):eM(e,t)}function eM(e,t){var n=Math.abs(e);return(e>0?"-":"+")+eC(Math.floor(n/60),2)+(t||"")+eC(n%60,2)}var eN={G:function(e,t,n){var r=e.getUTCFullYear()>0?1:0;switch(t){case"G":case"GG":case"GGG":return n.era(r,{width:"abbreviated"});case"GGGGG":return n.era(r,{width:"narrow"});default:return n.era(r,{width:"wide"})}},y:function(e,t,n){if("yo"===t){var r=e.getUTCFullYear();return n.ordinalNumber(r>0?r:1-r,{unit:"year"})}return ej.y(e,t)},Y:function(e,t,n,r){var o=eO(e,r),i=o>0?o:1-o;return"YY"===t?eC(i%100,2):"Yo"===t?n.ordinalNumber(i,{unit:"year"}):eC(i,t.length)},R:function(e,t){return eC(ek(e),t.length)},u:function(e,t){return eC(e.getUTCFullYear(),t.length)},Q:function(e,t,n){var r=Math.ceil((e.getUTCMonth()+1)/3);switch(t){case"Q":return String(r);case"QQ":return eC(r,2);case"Qo":return n.ordinalNumber(r,{unit:"quarter"});case"QQQ":return n.quarter(r,{width:"abbreviated",context:"formatting"});case"QQQQQ":return n.quarter(r,{width:"narrow",context:"formatting"});default:return n.quarter(r,{width:"wide",context:"formatting"})}},q:function(e,t,n){var r=Math.ceil((e.getUTCMonth()+1)/3);switch(t){case"q":return String(r);case"qq":return eC(r,2);case"qo":return n.ordinalNumber(r,{unit:"quarter"});case"qqq":return n.quarter(r,{width:"abbreviated",context:"standalone"});case"qqqqq":return n.quarter(r,{width:"narrow",context:"standalone"});default:return n.quarter(r,{width:"wide",context:"standalone"})}},M:function(e,t,n){var r=e.getUTCMonth();switch(t){case"M":case"MM":return ej.M(e,t);case"Mo":return n.ordinalNumber(r+1,{unit:"month"});case"MMM":return n.month(r,{width:"abbreviated",context:"formatting"});case"MMMMM":return n.month(r,{width:"narrow",context:"formatting"});default:return n.month(r,{width:"wide",context:"formatting"})}},L:function(e,t,n){var r=e.getUTCMonth();switch(t){case"L":return String(r+1);case"LL":return eC(r+1,2);case"Lo":return n.ordinalNumber(r+1,{unit:"month"});case"LLL":return n.month(r,{width:"abbreviated",context:"standalone"});case"LLLLL":return n.month(r,{width:"narrow",context:"standalone"});default:return n.month(r,{width:"wide",context:"standalone"})}},w:function(e,t,n,r){var o=function(e,t){(0,ei.Z)(1,arguments);var n=(0,eo.Z)(e);return Math.round((eE(n,t).getTime()-(function(e,t){(0,ei.Z)(1,arguments);var n,r,o,i,a,l,s,c,u=(0,em.Z)(null!==(n=null!==(r=null!==(o=null!==(i=null==t?void 0:t.firstWeekContainsDate)&&void 0!==i?i:null==t?void 0:null===(a=t.locale)||void 0===a?void 0:null===(l=a.options)||void 0===l?void 0:l.firstWeekContainsDate)&&void 0!==o?o:eS.firstWeekContainsDate)&&void 0!==r?r:null===(s=eS.locale)||void 0===s?void 0:null===(c=s.options)||void 0===c?void 0:c.firstWeekContainsDate)&&void 0!==n?n:1),d=eO(e,t),f=new Date(0);return f.setUTCFullYear(d,0,u),f.setUTCHours(0,0,0,0),eE(f,t)})(n,t).getTime())/6048e5)+1}(e,r);return"wo"===t?n.ordinalNumber(o,{unit:"week"}):eC(o,t.length)},I:function(e,t,n){var r=function(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e);return Math.round((ew(t).getTime()-(function(e){(0,ei.Z)(1,arguments);var t=ek(e),n=new Date(0);return n.setUTCFullYear(t,0,4),n.setUTCHours(0,0,0,0),ew(n)})(t).getTime())/6048e5)+1}(e);return"Io"===t?n.ordinalNumber(r,{unit:"week"}):eC(r,t.length)},d:function(e,t,n){return"do"===t?n.ordinalNumber(e.getUTCDate(),{unit:"date"}):ej.d(e,t)},D:function(e,t,n){var r=function(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e),n=t.getTime();return t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0),Math.floor((n-t.getTime())/864e5)+1}(e);return"Do"===t?n.ordinalNumber(r,{unit:"dayOfYear"}):eC(r,t.length)},E:function(e,t,n){var r=e.getUTCDay();switch(t){case"E":case"EE":case"EEE":return n.day(r,{width:"abbreviated",context:"formatting"});case"EEEEE":return n.day(r,{width:"narrow",context:"formatting"});case"EEEEEE":return n.day(r,{width:"short",context:"formatting"});default:return n.day(r,{width:"wide",context:"formatting"})}},e:function(e,t,n,r){var o=e.getUTCDay(),i=(o-r.weekStartsOn+8)%7||7;switch(t){case"e":return String(i);case"ee":return eC(i,2);case"eo":return n.ordinalNumber(i,{unit:"day"});case"eee":return n.day(o,{width:"abbreviated",context:"formatting"});case"eeeee":return n.day(o,{width:"narrow",context:"formatting"});case"eeeeee":return n.day(o,{width:"short",context:"formatting"});default:return n.day(o,{width:"wide",context:"formatting"})}},c:function(e,t,n,r){var o=e.getUTCDay(),i=(o-r.weekStartsOn+8)%7||7;switch(t){case"c":return String(i);case"cc":return eC(i,t.length);case"co":return n.ordinalNumber(i,{unit:"day"});case"ccc":return n.day(o,{width:"abbreviated",context:"standalone"});case"ccccc":return n.day(o,{width:"narrow",context:"standalone"});case"cccccc":return n.day(o,{width:"short",context:"standalone"});default:return n.day(o,{width:"wide",context:"standalone"})}},i:function(e,t,n){var r=e.getUTCDay(),o=0===r?7:r;switch(t){case"i":return String(o);case"ii":return eC(o,t.length);case"io":return n.ordinalNumber(o,{unit:"day"});case"iii":return n.day(r,{width:"abbreviated",context:"formatting"});case"iiiii":return n.day(r,{width:"narrow",context:"formatting"});case"iiiiii":return n.day(r,{width:"short",context:"formatting"});default:return n.day(r,{width:"wide",context:"formatting"})}},a:function(e,t,n){var r=e.getUTCHours()/12>=1?"pm":"am";switch(t){case"a":case"aa":return n.dayPeriod(r,{width:"abbreviated",context:"formatting"});case"aaa":return n.dayPeriod(r,{width:"abbreviated",context:"formatting"}).toLowerCase();case"aaaaa":return n.dayPeriod(r,{width:"narrow",context:"formatting"});default:return n.dayPeriod(r,{width:"wide",context:"formatting"})}},b:function(e,t,n){var r,o=e.getUTCHours();switch(r=12===o?e_.noon:0===o?e_.midnight:o/12>=1?"pm":"am",t){case"b":case"bb":return n.dayPeriod(r,{width:"abbreviated",context:"formatting"});case"bbb":return n.dayPeriod(r,{width:"abbreviated",context:"formatting"}).toLowerCase();case"bbbbb":return n.dayPeriod(r,{width:"narrow",context:"formatting"});default:return n.dayPeriod(r,{width:"wide",context:"formatting"})}},B:function(e,t,n){var r,o=e.getUTCHours();switch(r=o>=17?e_.evening:o>=12?e_.afternoon:o>=4?e_.morning:e_.night,t){case"B":case"BB":case"BBB":return n.dayPeriod(r,{width:"abbreviated",context:"formatting"});case"BBBBB":return n.dayPeriod(r,{width:"narrow",context:"formatting"});default:return n.dayPeriod(r,{width:"wide",context:"formatting"})}},h:function(e,t,n){if("ho"===t){var r=e.getUTCHours()%12;return 0===r&&(r=12),n.ordinalNumber(r,{unit:"hour"})}return ej.h(e,t)},H:function(e,t,n){return"Ho"===t?n.ordinalNumber(e.getUTCHours(),{unit:"hour"}):ej.H(e,t)},K:function(e,t,n){var r=e.getUTCHours()%12;return"Ko"===t?n.ordinalNumber(r,{unit:"hour"}):eC(r,t.length)},k:function(e,t,n){var r=e.getUTCHours();return(0===r&&(r=24),"ko"===t)?n.ordinalNumber(r,{unit:"hour"}):eC(r,t.length)},m:function(e,t,n){return"mo"===t?n.ordinalNumber(e.getUTCMinutes(),{unit:"minute"}):ej.m(e,t)},s:function(e,t,n){return"so"===t?n.ordinalNumber(e.getUTCSeconds(),{unit:"second"}):ej.s(e,t)},S:function(e,t){return ej.S(e,t)},X:function(e,t,n,r){var o=(r._originalDate||e).getTimezoneOffset();if(0===o)return"Z";switch(t){case"X":return eT(o);case"XXXX":case"XX":return eM(o);default:return eM(o,":")}},x:function(e,t,n,r){var o=(r._originalDate||e).getTimezoneOffset();switch(t){case"x":return eT(o);case"xxxx":case"xx":return eM(o);default:return eM(o,":")}},O:function(e,t,n,r){var o=(r._originalDate||e).getTimezoneOffset();switch(t){case"O":case"OO":case"OOO":return"GMT"+eP(o,":");default:return"GMT"+eM(o,":")}},z:function(e,t,n,r){var o=(r._originalDate||e).getTimezoneOffset();switch(t){case"z":case"zz":case"zzz":return"GMT"+eP(o,":");default:return"GMT"+eM(o,":")}},t:function(e,t,n,r){return eC(Math.floor((r._originalDate||e).getTime()/1e3),t.length)},T:function(e,t,n,r){return eC((r._originalDate||e).getTime(),t.length)}},eA=function(e,t){switch(e){case"P":return t.date({width:"short"});case"PP":return t.date({width:"medium"});case"PPP":return t.date({width:"long"});default:return t.date({width:"full"})}},eI=function(e,t){switch(e){case"p":return t.time({width:"short"});case"pp":return t.time({width:"medium"});case"ppp":return t.time({width:"long"});default:return t.time({width:"full"})}},eR={p:eI,P:function(e,t){var n,r=e.match(/(P+)(p+)?/)||[],o=r[1],i=r[2];if(!i)return eA(e,t);switch(o){case"P":n=t.dateTime({width:"short"});break;case"PP":n=t.dateTime({width:"medium"});break;case"PPP":n=t.dateTime({width:"long"});break;default:n=t.dateTime({width:"full"})}return n.replace("{{date}}",eA(o,t)).replace("{{time}}",eI(i,t))}};function eD(e){var t=new Date(Date.UTC(e.getFullYear(),e.getMonth(),e.getDate(),e.getHours(),e.getMinutes(),e.getSeconds(),e.getMilliseconds()));return t.setUTCFullYear(e.getFullYear()),e.getTime()-t.getTime()}var eL=["D","DD"],ez=["YY","YYYY"];function eZ(e,t,n){if("YYYY"===e)throw RangeError("Use `yyyy` instead of `YYYY` (in `".concat(t,"`) for formatting years to the input `").concat(n,"`; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md"));if("YY"===e)throw RangeError("Use `yy` instead of `YY` (in `".concat(t,"`) for formatting years to the input `").concat(n,"`; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md"));if("D"===e)throw RangeError("Use `d` instead of `D` (in `".concat(t,"`) for formatting days of the month to the input `").concat(n,"`; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md"));if("DD"===e)throw RangeError("Use `dd` instead of `DD` (in `".concat(t,"`) for formatting days of the month to the input `").concat(n,"`; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md"))}var eB={lessThanXSeconds:{one:"less than a second",other:"less than {{count}} seconds"},xSeconds:{one:"1 second",other:"{{count}} seconds"},halfAMinute:"half a minute",lessThanXMinutes:{one:"less than a minute",other:"less than {{count}} minutes"},xMinutes:{one:"1 minute",other:"{{count}} minutes"},aboutXHours:{one:"about 1 hour",other:"about {{count}} hours"},xHours:{one:"1 hour",other:"{{count}} hours"},xDays:{one:"1 day",other:"{{count}} days"},aboutXWeeks:{one:"about 1 week",other:"about {{count}} weeks"},xWeeks:{one:"1 week",other:"{{count}} weeks"},aboutXMonths:{one:"about 1 month",other:"about {{count}} months"},xMonths:{one:"1 month",other:"{{count}} months"},aboutXYears:{one:"about 1 year",other:"about {{count}} years"},xYears:{one:"1 year",other:"{{count}} years"},overXYears:{one:"over 1 year",other:"over {{count}} years"},almostXYears:{one:"almost 1 year",other:"almost {{count}} years"}};function eF(e){return function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=t.width?String(t.width):e.defaultWidth;return e.formats[n]||e.formats[e.defaultWidth]}}var eH={date:eF({formats:{full:"EEEE, MMMM do, y",long:"MMMM do, y",medium:"MMM d, y",short:"MM/dd/yyyy"},defaultWidth:"full"}),time:eF({formats:{full:"h:mm:ss a zzzz",long:"h:mm:ss a z",medium:"h:mm:ss a",short:"h:mm a"},defaultWidth:"full"}),dateTime:eF({formats:{full:"{{date}} 'at' {{time}}",long:"{{date}} 'at' {{time}}",medium:"{{date}}, {{time}}",short:"{{date}}, {{time}}"},defaultWidth:"full"})},eq={lastWeek:"'last' eeee 'at' p",yesterday:"'yesterday at' p",today:"'today at' p",tomorrow:"'tomorrow at' p",nextWeek:"eeee 'at' p",other:"P"};function eU(e){return function(t,n){var r;if("formatting"===(null!=n&&n.context?String(n.context):"standalone")&&e.formattingValues){var o=e.defaultFormattingWidth||e.defaultWidth,i=null!=n&&n.width?String(n.width):o;r=e.formattingValues[i]||e.formattingValues[o]}else{var a=e.defaultWidth,l=null!=n&&n.width?String(n.width):e.defaultWidth;r=e.values[l]||e.values[a]}return r[e.argumentCallback?e.argumentCallback(t):t]}}function eW(e){return function(t){var n,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=r.width,i=o&&e.matchPatterns[o]||e.matchPatterns[e.defaultMatchWidth],a=t.match(i);if(!a)return null;var l=a[0],s=o&&e.parsePatterns[o]||e.parsePatterns[e.defaultParseWidth],c=Array.isArray(s)?function(e,t){for(var n=0;n0?"in "+r:r+" ago":r},formatLong:eH,formatRelative:function(e,t,n,r){return eq[e]},localize:{ordinalNumber:function(e,t){var n=Number(e),r=n%100;if(r>20||r<10)switch(r%10){case 1:return n+"st";case 2:return n+"nd";case 3:return n+"rd"}return n+"th"},era:eU({values:{narrow:["B","A"],abbreviated:["BC","AD"],wide:["Before Christ","Anno Domini"]},defaultWidth:"wide"}),quarter:eU({values:{narrow:["1","2","3","4"],abbreviated:["Q1","Q2","Q3","Q4"],wide:["1st quarter","2nd quarter","3rd quarter","4th quarter"]},defaultWidth:"wide",argumentCallback:function(e){return e-1}}),month:eU({values:{narrow:["J","F","M","A","M","J","J","A","S","O","N","D"],abbreviated:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],wide:["January","February","March","April","May","June","July","August","September","October","November","December"]},defaultWidth:"wide"}),day:eU({values:{narrow:["S","M","T","W","T","F","S"],short:["Su","Mo","Tu","We","Th","Fr","Sa"],abbreviated:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],wide:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]},defaultWidth:"wide"}),dayPeriod:eU({values:{narrow:{am:"a",pm:"p",midnight:"mi",noon:"n",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"},abbreviated:{am:"AM",pm:"PM",midnight:"midnight",noon:"noon",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"},wide:{am:"a.m.",pm:"p.m.",midnight:"midnight",noon:"noon",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"}},defaultWidth:"wide",formattingValues:{narrow:{am:"a",pm:"p",midnight:"mi",noon:"n",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"},abbreviated:{am:"AM",pm:"PM",midnight:"midnight",noon:"noon",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"},wide:{am:"a.m.",pm:"p.m.",midnight:"midnight",noon:"noon",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"}},defaultFormattingWidth:"wide"})},match:{ordinalNumber:(a={matchPattern:/^(\d+)(th|st|nd|rd)?/i,parsePattern:/\d+/i,valueCallback:function(e){return parseInt(e,10)}},function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=e.match(a.matchPattern);if(!n)return null;var r=n[0],o=e.match(a.parsePattern);if(!o)return null;var i=a.valueCallback?a.valueCallback(o[0]):o[0];return{value:i=t.valueCallback?t.valueCallback(i):i,rest:e.slice(r.length)}}),era:eW({matchPatterns:{narrow:/^(b|a)/i,abbreviated:/^(b\.?\s?c\.?|b\.?\s?c\.?\s?e\.?|a\.?\s?d\.?|c\.?\s?e\.?)/i,wide:/^(before christ|before common era|anno domini|common era)/i},defaultMatchWidth:"wide",parsePatterns:{any:[/^b/i,/^(a|c)/i]},defaultParseWidth:"any"}),quarter:eW({matchPatterns:{narrow:/^[1234]/i,abbreviated:/^q[1234]/i,wide:/^[1234](th|st|nd|rd)? quarter/i},defaultMatchWidth:"wide",parsePatterns:{any:[/1/i,/2/i,/3/i,/4/i]},defaultParseWidth:"any",valueCallback:function(e){return e+1}}),month:eW({matchPatterns:{narrow:/^[jfmasond]/i,abbreviated:/^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i,wide:/^(january|february|march|april|may|june|july|august|september|october|november|december)/i},defaultMatchWidth:"wide",parsePatterns:{narrow:[/^j/i,/^f/i,/^m/i,/^a/i,/^m/i,/^j/i,/^j/i,/^a/i,/^s/i,/^o/i,/^n/i,/^d/i],any:[/^ja/i,/^f/i,/^mar/i,/^ap/i,/^may/i,/^jun/i,/^jul/i,/^au/i,/^s/i,/^o/i,/^n/i,/^d/i]},defaultParseWidth:"any"}),day:eW({matchPatterns:{narrow:/^[smtwf]/i,short:/^(su|mo|tu|we|th|fr|sa)/i,abbreviated:/^(sun|mon|tue|wed|thu|fri|sat)/i,wide:/^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)/i},defaultMatchWidth:"wide",parsePatterns:{narrow:[/^s/i,/^m/i,/^t/i,/^w/i,/^t/i,/^f/i,/^s/i],any:[/^su/i,/^m/i,/^tu/i,/^w/i,/^th/i,/^f/i,/^sa/i]},defaultParseWidth:"any"}),dayPeriod:eW({matchPatterns:{narrow:/^(a|p|mi|n|(in the|at) (morning|afternoon|evening|night))/i,any:/^([ap]\.?\s?m\.?|midnight|noon|(in the|at) (morning|afternoon|evening|night))/i},defaultMatchWidth:"any",parsePatterns:{any:{am:/^a/i,pm:/^p/i,midnight:/^mi/i,noon:/^no/i,morning:/morning/i,afternoon:/afternoon/i,evening:/evening/i,night:/night/i}},defaultParseWidth:"any"})},options:{weekStartsOn:0,firstWeekContainsDate:1}},eV=/[yYQqMLwIdDecihHKkms]o|(\w)\1*|''|'(''|[^'])+('|$)|./g,e$=/P+p+|P+|p+|''|'(''|[^'])+('|$)|./g,eX=/^'([^]*?)'?$/,eG=/''/g,eY=/[a-zA-Z]/;function eQ(e,t,n){(0,ei.Z)(2,arguments);var r,o,i,a,l,s,c,u,d,f,p,h,m,g,v,y,b,x,w=String(t),k=null!==(r=null!==(o=null==n?void 0:n.locale)&&void 0!==o?o:eS.locale)&&void 0!==r?r:eK,S=(0,em.Z)(null!==(i=null!==(a=null!==(l=null!==(s=null==n?void 0:n.firstWeekContainsDate)&&void 0!==s?s:null==n?void 0:null===(c=n.locale)||void 0===c?void 0:null===(u=c.options)||void 0===u?void 0:u.firstWeekContainsDate)&&void 0!==l?l:eS.firstWeekContainsDate)&&void 0!==a?a:null===(d=eS.locale)||void 0===d?void 0:null===(f=d.options)||void 0===f?void 0:f.firstWeekContainsDate)&&void 0!==i?i:1);if(!(S>=1&&S<=7))throw RangeError("firstWeekContainsDate must be between 1 and 7 inclusively");var E=(0,em.Z)(null!==(p=null!==(h=null!==(m=null!==(g=null==n?void 0:n.weekStartsOn)&&void 0!==g?g:null==n?void 0:null===(v=n.locale)||void 0===v?void 0:null===(y=v.options)||void 0===y?void 0:y.weekStartsOn)&&void 0!==m?m:eS.weekStartsOn)&&void 0!==h?h:null===(b=eS.locale)||void 0===b?void 0:null===(x=b.options)||void 0===x?void 0:x.weekStartsOn)&&void 0!==p?p:0);if(!(E>=0&&E<=6))throw RangeError("weekStartsOn must be between 0 and 6 inclusively");if(!k.localize)throw RangeError("locale must contain localize property");if(!k.formatLong)throw RangeError("locale must contain formatLong property");var O=(0,eo.Z)(e);if(!function(e){return(0,ei.Z)(1,arguments),(!!ex(e)||"number"==typeof e)&&!isNaN(Number((0,eo.Z)(e)))}(O))throw RangeError("Invalid time value");var C=eD(O),j=function(e,t){return(0,ei.Z)(2,arguments),function(e,t){return(0,ei.Z)(2,arguments),new Date((0,eo.Z)(e).getTime()+(0,em.Z)(t))}(e,-(0,em.Z)(t))}(O,C),_={firstWeekContainsDate:S,weekStartsOn:E,locale:k,_originalDate:O};return w.match(e$).map(function(e){var t=e[0];return"p"===t||"P"===t?(0,eR[t])(e,k.formatLong):e}).join("").match(eV).map(function(r){if("''"===r)return"'";var o,i=r[0];if("'"===i)return(o=r.match(eX))?o[1].replace(eG,"'"):r;var a=eN[i];if(a)return null!=n&&n.useAdditionalWeekYearTokens||-1===ez.indexOf(r)||eZ(r,t,String(e)),null!=n&&n.useAdditionalDayOfYearTokens||-1===eL.indexOf(r)||eZ(r,t,String(e)),a(j,r,k.localize,_);if(i.match(eY))throw RangeError("Format string contains an unescaped latin alphabet character `"+i+"`");return r}).join("")}var eJ=n(1153);let e0=(0,eJ.fn)("DateRangePicker"),e1=(e,t,n,r)=>{var o;if(n&&(e=null===(o=r.get(n))||void 0===o?void 0:o.from),e)return ea(e&&!t?e:ef([e,t]))},e2=(e,t,n,r)=>{var o,i;if(n&&(e=ea(null!==(i=null===(o=r.get(n))||void 0===o?void 0:o.to)&&void 0!==i?i:el())),e)return ea(e&&!t?e:ep([e,t]))},e4=[{value:"tdy",text:"Today",from:el()},{value:"w",text:"Last 7 days",from:ey(el(),{days:7})},{value:"t",text:"Last 30 days",from:ey(el(),{days:30})},{value:"m",text:"Month to Date",from:es(el())},{value:"y",text:"Year to Date",from:eb(el())}],e3=(e,t,n,r)=>{let o=(null==n?void 0:n.code)||"en-US";if(!e&&!t)return"";if(e&&!t)return r?eQ(e,r):e.toLocaleDateString(o,{year:"numeric",month:"short",day:"numeric"});if(e&&t){if(function(e,t){(0,ei.Z)(2,arguments);var n=(0,eo.Z)(e),r=(0,eo.Z)(t);return n.getTime()===r.getTime()}(e,t))return r?eQ(e,r):e.toLocaleDateString(o,{year:"numeric",month:"short",day:"numeric"});if(e.getMonth()===t.getMonth()&&e.getFullYear()===t.getFullYear())return r?"".concat(eQ(e,r)," - ").concat(eQ(t,r)):"".concat(e.toLocaleDateString(o,{month:"short",day:"numeric"})," - \n ").concat(t.getDate(),", ").concat(t.getFullYear());{if(r)return"".concat(eQ(e,r)," - ").concat(eQ(t,r));let n={year:"numeric",month:"short",day:"numeric"};return"".concat(e.toLocaleDateString(o,n)," - \n ").concat(t.toLocaleDateString(o,n))}}return""};function e6(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e),n=t.getMonth();return t.setFullYear(t.getFullYear(),n+1,0),t.setHours(23,59,59,999),t}function e8(e,t){(0,ei.Z)(2,arguments);var n=(0,eo.Z)(e),r=(0,em.Z)(t),o=n.getFullYear(),i=n.getDate(),a=new Date(0);a.setFullYear(o,r,15),a.setHours(0,0,0,0);var l=function(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e),n=t.getFullYear(),r=t.getMonth(),o=new Date(0);return o.setFullYear(n,r+1,0),o.setHours(0,0,0,0),o.getDate()}(a);return n.setMonth(r,Math.min(i,l)),n}function e5(e,t){(0,ei.Z)(2,arguments);var n=(0,eo.Z)(e),r=(0,em.Z)(t);return isNaN(n.getTime())?new Date(NaN):(n.setFullYear(r),n)}function e7(e,t){(0,ei.Z)(2,arguments);var n=(0,eo.Z)(e),r=(0,eo.Z)(t);return 12*(n.getFullYear()-r.getFullYear())+(n.getMonth()-r.getMonth())}function e9(e,t){(0,ei.Z)(2,arguments);var n=(0,eo.Z)(e),r=(0,eo.Z)(t);return n.getFullYear()===r.getFullYear()&&n.getMonth()===r.getMonth()}function te(e,t){(0,ei.Z)(2,arguments);var n=(0,eo.Z)(e),r=(0,eo.Z)(t);return n.getTime()=0&&u<=6))throw RangeError("weekStartsOn must be between 0 and 6 inclusively");var d=(0,eo.Z)(e),f=d.getDay();return d.setDate(d.getDate()-((fr.getTime()}function ti(e,t){(0,ei.Z)(2,arguments);var n=ea(e),r=ea(t);return Math.round((n.getTime()-eD(n)-(r.getTime()-eD(r)))/864e5)}function ta(e,t){(0,ei.Z)(2,arguments);var n=(0,em.Z)(t);return(0,eh.Z)(e,7*n)}function tl(e,t){(0,ei.Z)(2,arguments);var n=(0,em.Z)(t);return(0,ev.Z)(e,12*n)}function ts(e,t){(0,ei.Z)(1,arguments);var n,r,o,i,a,l,s,c,u=(0,em.Z)(null!==(n=null!==(r=null!==(o=null!==(i=null==t?void 0:t.weekStartsOn)&&void 0!==i?i:null==t?void 0:null===(a=t.locale)||void 0===a?void 0:null===(l=a.options)||void 0===l?void 0:l.weekStartsOn)&&void 0!==o?o:eS.weekStartsOn)&&void 0!==r?r:null===(s=eS.locale)||void 0===s?void 0:null===(c=s.options)||void 0===c?void 0:c.weekStartsOn)&&void 0!==n?n:0);if(!(u>=0&&u<=6))throw RangeError("weekStartsOn must be between 0 and 6 inclusively");var d=(0,eo.Z)(e),f=d.getDay();return d.setDate(d.getDate()+((fe7(l,a)&&(a=(0,ev.Z)(l,-1*((void 0===c?1:c)-1))),s&&0>e7(a,s)&&(a=s),u=es(a),f=t.month,h=(p=(0,d.useState)(u))[0],m=[void 0===f?h:f,p[1]])[0],v=m[1],[g,function(e){if(!t.disableNavigation){var n,r=es(e);v(r),null===(n=t.onMonthChange)||void 0===n||n.call(t,r)}}]),x=b[0],w=b[1],k=function(e,t){for(var n=t.reverseMonths,r=t.numberOfMonths,o=es(e),i=e7(es((0,ev.Z)(o,r)),o),a=[],l=0;l=e7(i,n)))return(0,ev.Z)(i,-(r?void 0===o?1:o:1))}}(x,y),O=function(e){return k.some(function(t){return e9(e,t)})};return th.jsx(tP.Provider,{value:{currentMonth:x,displayMonths:k,goToMonth:w,goToDate:function(e,t){O(e)||(t&&te(e,t)?w((0,ev.Z)(e,1+-1*y.numberOfMonths)):w(e))},previousMonth:E,nextMonth:S,isDateDisplayed:O},children:e.children})}function tM(){var e=(0,d.useContext)(tP);if(!e)throw Error("useNavigation must be used within a NavigationProvider");return e}function tN(e){var t,n=tS(),r=n.classNames,o=n.styles,i=n.components,a=tM().goToMonth,l=function(t){a((0,ev.Z)(t,e.displayIndex?-e.displayIndex:0))},s=null!==(t=null==i?void 0:i.CaptionLabel)&&void 0!==t?t:tE,c=th.jsx(s,{id:e.id,displayMonth:e.displayMonth});return th.jsxs("div",{className:r.caption_dropdowns,style:o.caption_dropdowns,children:[th.jsx("div",{className:r.vhidden,children:c}),th.jsx(tj,{onChange:l,displayMonth:e.displayMonth}),th.jsx(t_,{onChange:l,displayMonth:e.displayMonth})]})}function tA(e){return th.jsx("svg",tu({width:"16px",height:"16px",viewBox:"0 0 120 120"},e,{children:th.jsx("path",{d:"M69.490332,3.34314575 C72.6145263,0.218951416 77.6798462,0.218951416 80.8040405,3.34314575 C83.8617626,6.40086786 83.9268205,11.3179931 80.9992143,14.4548388 L80.8040405,14.6568542 L35.461,60 L80.8040405,105.343146 C83.8617626,108.400868 83.9268205,113.317993 80.9992143,116.454839 L80.8040405,116.656854 C77.7463184,119.714576 72.8291931,119.779634 69.6923475,116.852028 L69.490332,116.656854 L18.490332,65.6568542 C15.4326099,62.5991321 15.367552,57.6820069 18.2951583,54.5451612 L18.490332,54.3431458 L69.490332,3.34314575 Z",fill:"currentColor",fillRule:"nonzero"})}))}function tI(e){return th.jsx("svg",tu({width:"16px",height:"16px",viewBox:"0 0 120 120"},e,{children:th.jsx("path",{d:"M49.8040405,3.34314575 C46.6798462,0.218951416 41.6145263,0.218951416 38.490332,3.34314575 C35.4326099,6.40086786 35.367552,11.3179931 38.2951583,14.4548388 L38.490332,14.6568542 L83.8333725,60 L38.490332,105.343146 C35.4326099,108.400868 35.367552,113.317993 38.2951583,116.454839 L38.490332,116.656854 C41.5480541,119.714576 46.4651794,119.779634 49.602025,116.852028 L49.8040405,116.656854 L100.804041,65.6568542 C103.861763,62.5991321 103.926821,57.6820069 100.999214,54.5451612 L100.804041,54.3431458 L49.8040405,3.34314575 Z",fill:"currentColor"})}))}var tR=(0,d.forwardRef)(function(e,t){var n=tS(),r=n.classNames,o=n.styles,i=[r.button_reset,r.button];e.className&&i.push(e.className);var a=i.join(" "),l=tu(tu({},o.button_reset),o.button);return e.style&&Object.assign(l,e.style),th.jsx("button",tu({},e,{ref:t,type:"button",className:a,style:l}))});function tD(e){var t,n,r=tS(),o=r.dir,i=r.locale,a=r.classNames,l=r.styles,s=r.labels,c=s.labelPrevious,u=s.labelNext,d=r.components;if(!e.nextMonth&&!e.previousMonth)return th.jsx(th.Fragment,{});var f=c(e.previousMonth,{locale:i}),p=[a.nav_button,a.nav_button_previous].join(" "),h=u(e.nextMonth,{locale:i}),m=[a.nav_button,a.nav_button_next].join(" "),g=null!==(t=null==d?void 0:d.IconRight)&&void 0!==t?t:tI,v=null!==(n=null==d?void 0:d.IconLeft)&&void 0!==n?n:tA;return th.jsxs("div",{className:a.nav,style:l.nav,children:[!e.hidePrevious&&th.jsx(tR,{name:"previous-month","aria-label":f,className:p,style:l.nav_button_previous,disabled:!e.previousMonth,onClick:e.onPreviousClick,children:"rtl"===o?th.jsx(g,{className:a.nav_icon,style:l.nav_icon}):th.jsx(v,{className:a.nav_icon,style:l.nav_icon})}),!e.hideNext&&th.jsx(tR,{name:"next-month","aria-label":h,className:m,style:l.nav_button_next,disabled:!e.nextMonth,onClick:e.onNextClick,children:"rtl"===o?th.jsx(v,{className:a.nav_icon,style:l.nav_icon}):th.jsx(g,{className:a.nav_icon,style:l.nav_icon})})]})}function tL(e){var t=tS().numberOfMonths,n=tM(),r=n.previousMonth,o=n.nextMonth,i=n.goToMonth,a=n.displayMonths,l=a.findIndex(function(t){return e9(e.displayMonth,t)}),s=0===l,c=l===a.length-1;return th.jsx(tD,{displayMonth:e.displayMonth,hideNext:t>1&&(s||!c),hidePrevious:t>1&&(c||!s),nextMonth:o,previousMonth:r,onPreviousClick:function(){r&&i(r)},onNextClick:function(){o&&i(o)}})}function tz(e){var t,n,r=tS(),o=r.classNames,i=r.disableNavigation,a=r.styles,l=r.captionLayout,s=r.components,c=null!==(t=null==s?void 0:s.CaptionLabel)&&void 0!==t?t:tE;return n=i?th.jsx(c,{id:e.id,displayMonth:e.displayMonth}):"dropdown"===l?th.jsx(tN,{displayMonth:e.displayMonth,id:e.id}):"dropdown-buttons"===l?th.jsxs(th.Fragment,{children:[th.jsx(tN,{displayMonth:e.displayMonth,displayIndex:e.displayIndex,id:e.id}),th.jsx(tL,{displayMonth:e.displayMonth,displayIndex:e.displayIndex,id:e.id})]}):th.jsxs(th.Fragment,{children:[th.jsx(c,{id:e.id,displayMonth:e.displayMonth,displayIndex:e.displayIndex}),th.jsx(tL,{displayMonth:e.displayMonth,id:e.id})]}),th.jsx("div",{className:o.caption,style:a.caption,children:n})}function tZ(e){var t=tS(),n=t.footer,r=t.styles,o=t.classNames.tfoot;return n?th.jsx("tfoot",{className:o,style:r.tfoot,children:th.jsx("tr",{children:th.jsx("td",{colSpan:8,children:n})})}):th.jsx(th.Fragment,{})}function tB(){var e=tS(),t=e.classNames,n=e.styles,r=e.showWeekNumber,o=e.locale,i=e.weekStartsOn,a=e.ISOWeek,l=e.formatters.formatWeekdayName,s=e.labels.labelWeekday,c=function(e,t,n){for(var r=n?tn(new Date):tt(new Date,{locale:e,weekStartsOn:t}),o=[],i=0;i<7;i++){var a=(0,eh.Z)(r,i);o.push(a)}return o}(o,i,a);return th.jsxs("tr",{style:n.head_row,className:t.head_row,children:[r&&th.jsx("td",{style:n.head_cell,className:t.head_cell}),c.map(function(e,r){return th.jsx("th",{scope:"col",className:t.head_cell,style:n.head_cell,"aria-label":s(e,{locale:o}),children:l(e,{locale:o})},r)})]})}function tF(){var e,t=tS(),n=t.classNames,r=t.styles,o=t.components,i=null!==(e=null==o?void 0:o.HeadRow)&&void 0!==e?e:tB;return th.jsx("thead",{style:r.head,className:n.head,children:th.jsx(i,{})})}function tH(e){var t=tS(),n=t.locale,r=t.formatters.formatDay;return th.jsx(th.Fragment,{children:r(e.date,{locale:n})})}var tq=(0,d.createContext)(void 0);function tU(e){return tm(e.initialProps)?th.jsx(tW,{initialProps:e.initialProps,children:e.children}):th.jsx(tq.Provider,{value:{selected:void 0,modifiers:{disabled:[]}},children:e.children})}function tW(e){var t=e.initialProps,n=e.children,r=t.selected,o=t.min,i=t.max,a={disabled:[]};return r&&a.disabled.push(function(e){var t=i&&r.length>i-1,n=r.some(function(t){return tr(t,e)});return!!(t&&!n)}),th.jsx(tq.Provider,{value:{selected:r,onDayClick:function(e,n,a){if(null===(l=t.onDayClick)||void 0===l||l.call(t,e,n,a),(!n.selected||!o||(null==r?void 0:r.length)!==o)&&(n.selected||!i||(null==r?void 0:r.length)!==i)){var l,s,c=r?td([],r,!0):[];if(n.selected){var u=c.findIndex(function(t){return tr(e,t)});c.splice(u,1)}else c.push(e);null===(s=t.onSelect)||void 0===s||s.call(t,c,e,n,a)}},modifiers:a},children:n})}function tK(){var e=(0,d.useContext)(tq);if(!e)throw Error("useSelectMultiple must be used within a SelectMultipleProvider");return e}var tV=(0,d.createContext)(void 0);function t$(e){return tg(e.initialProps)?th.jsx(tX,{initialProps:e.initialProps,children:e.children}):th.jsx(tV.Provider,{value:{selected:void 0,modifiers:{range_start:[],range_end:[],range_middle:[],disabled:[]}},children:e.children})}function tX(e){var t=e.initialProps,n=e.children,r=t.selected,o=r||{},i=o.from,a=o.to,l=t.min,s=t.max,c={range_start:[],range_end:[],range_middle:[],disabled:[]};if(i?(c.range_start=[i],a?(c.range_end=[a],tr(i,a)||(c.range_middle=[{after:i,before:a}])):c.range_end=[i]):a&&(c.range_start=[a],c.range_end=[a]),l&&(i&&!a&&c.disabled.push({after:eg(i,l-1),before:(0,eh.Z)(i,l-1)}),i&&a&&c.disabled.push({after:i,before:(0,eh.Z)(i,l-1)}),!i&&a&&c.disabled.push({after:eg(a,l-1),before:(0,eh.Z)(a,l-1)})),s){if(i&&!a&&(c.disabled.push({before:(0,eh.Z)(i,-s+1)}),c.disabled.push({after:(0,eh.Z)(i,s-1)})),i&&a){var u=s-(ti(a,i)+1);c.disabled.push({before:eg(i,u)}),c.disabled.push({after:(0,eh.Z)(a,u)})}!i&&a&&(c.disabled.push({before:(0,eh.Z)(a,-s+1)}),c.disabled.push({after:(0,eh.Z)(a,s-1)}))}return th.jsx(tV.Provider,{value:{selected:r,onDayClick:function(e,n,o){null===(s=t.onDayClick)||void 0===s||s.call(t,e,n,o);var i,a,l,s,c,u=(a=(i=r||{}).from,l=i.to,a&&l?tr(l,e)&&tr(a,e)?void 0:tr(l,e)?{from:l,to:void 0}:tr(a,e)?void 0:to(a,e)?{from:e,to:l}:{from:a,to:e}:l?to(e,l)?{from:l,to:e}:{from:e,to:l}:a?te(e,a)?{from:e,to:a}:{from:a,to:e}:{from:e,to:void 0});null===(c=t.onSelect)||void 0===c||c.call(t,u,e,n,o)},modifiers:c},children:n})}function tG(){var e=(0,d.useContext)(tV);if(!e)throw Error("useSelectRange must be used within a SelectRangeProvider");return e}function tY(e){return Array.isArray(e)?td([],e,!0):void 0!==e?[e]:[]}(l=c||(c={})).Outside="outside",l.Disabled="disabled",l.Selected="selected",l.Hidden="hidden",l.Today="today",l.RangeStart="range_start",l.RangeEnd="range_end",l.RangeMiddle="range_middle";var tQ=c.Selected,tJ=c.Disabled,t0=c.Hidden,t1=c.Today,t2=c.RangeEnd,t4=c.RangeMiddle,t3=c.RangeStart,t6=c.Outside,t8=(0,d.createContext)(void 0);function t5(e){var t,n,r,o=tS(),i=tK(),a=tG(),l=((t={})[tQ]=tY(o.selected),t[tJ]=tY(o.disabled),t[t0]=tY(o.hidden),t[t1]=[o.today],t[t2]=[],t[t4]=[],t[t3]=[],t[t6]=[],o.fromDate&&t[tJ].push({before:o.fromDate}),o.toDate&&t[tJ].push({after:o.toDate}),tm(o)?t[tJ]=t[tJ].concat(i.modifiers[tJ]):tg(o)&&(t[tJ]=t[tJ].concat(a.modifiers[tJ]),t[t3]=a.modifiers[t3],t[t4]=a.modifiers[t4],t[t2]=a.modifiers[t2]),t),s=(n=o.modifiers,r={},Object.entries(n).forEach(function(e){var t=e[0],n=e[1];r[t]=tY(n)}),r),c=tu(tu({},l),s);return th.jsx(t8.Provider,{value:c,children:e.children})}function t7(){var e=(0,d.useContext)(t8);if(!e)throw Error("useModifiers must be used within a ModifiersProvider");return e}function t9(e,t,n){var r=Object.keys(t).reduce(function(n,r){return t[r].some(function(t){if("boolean"==typeof t)return t;if(ex(t))return tr(e,t);if(Array.isArray(t)&&t.every(ex))return t.includes(e);if(t&&"object"==typeof t&&"from"in t)return r=t.from,o=t.to,r&&o?(0>ti(o,r)&&(r=(n=[o,r])[0],o=n[1]),ti(e,r)>=0&&ti(o,e)>=0):o?tr(o,e):!!r&&tr(r,e);if(t&&"object"==typeof t&&"dayOfWeek"in t)return t.dayOfWeek.includes(e.getDay());if(t&&"object"==typeof t&&"before"in t&&"after"in t){var n,r,o,i=ti(t.before,e),a=ti(t.after,e),l=i>0,s=a<0;return to(t.before,t.after)?s&&l:l||s}return t&&"object"==typeof t&&"after"in t?ti(e,t.after)>0:t&&"object"==typeof t&&"before"in t?ti(t.before,e)>0:"function"==typeof t&&t(e)})&&n.push(r),n},[]),o={};return r.forEach(function(e){return o[e]=!0}),n&&!e9(e,n)&&(o.outside=!0),o}var ne=(0,d.createContext)(void 0);function nt(e){var t=tM(),n=t7(),r=(0,d.useState)(),o=r[0],i=r[1],a=(0,d.useState)(),l=a[0],s=a[1],c=function(e,t){for(var n,r,o=es(e[0]),i=e6(e[e.length-1]),a=o;a<=i;){var l=t9(a,t);if(!(!l.disabled&&!l.hidden)){a=(0,eh.Z)(a,1);continue}if(l.selected)return a;l.today&&!r&&(r=a),n||(n=a),a=(0,eh.Z)(a,1)}return r||n}(t.displayMonths,n),u=(null!=o?o:l&&t.isDateDisplayed(l))?l:c,f=function(e){i(e)},p=tS(),h=function(e,r){if(o){var i=function e(t,n){var r=n.moveBy,o=n.direction,i=n.context,a=n.modifiers,l=n.retry,s=void 0===l?{count:0,lastFocused:t}:l,c=i.weekStartsOn,u=i.fromDate,d=i.toDate,f=i.locale,p=({day:eh.Z,week:ta,month:ev.Z,year:tl,startOfWeek:function(e){return i.ISOWeek?tn(e):tt(e,{locale:f,weekStartsOn:c})},endOfWeek:function(e){return i.ISOWeek?tc(e):ts(e,{locale:f,weekStartsOn:c})}})[r](t,"after"===o?1:-1);"before"===o&&u?p=ef([u,p]):"after"===o&&d&&(p=ep([d,p]));var h=!0;if(a){var m=t9(p,a);h=!m.disabled&&!m.hidden}return h?p:s.count>365?s.lastFocused:e(p,{moveBy:r,direction:o,context:i,modifiers:a,retry:tu(tu({},s),{count:s.count+1})})}(o,{moveBy:e,direction:r,context:p,modifiers:n});tr(o,i)||(t.goToDate(i,o),f(i))}};return th.jsx(ne.Provider,{value:{focusedDay:o,focusTarget:u,blur:function(){s(o),i(void 0)},focus:f,focusDayAfter:function(){return h("day","after")},focusDayBefore:function(){return h("day","before")},focusWeekAfter:function(){return h("week","after")},focusWeekBefore:function(){return h("week","before")},focusMonthBefore:function(){return h("month","before")},focusMonthAfter:function(){return h("month","after")},focusYearBefore:function(){return h("year","before")},focusYearAfter:function(){return h("year","after")},focusStartOfWeek:function(){return h("startOfWeek","before")},focusEndOfWeek:function(){return h("endOfWeek","after")}},children:e.children})}function nn(){var e=(0,d.useContext)(ne);if(!e)throw Error("useFocusContext must be used within a FocusProvider");return e}var nr=(0,d.createContext)(void 0);function no(e){return tv(e.initialProps)?th.jsx(ni,{initialProps:e.initialProps,children:e.children}):th.jsx(nr.Provider,{value:{selected:void 0},children:e.children})}function ni(e){var t=e.initialProps,n=e.children,r={selected:t.selected,onDayClick:function(e,n,r){var o,i,a;if(null===(o=t.onDayClick)||void 0===o||o.call(t,e,n,r),n.selected&&!t.required){null===(i=t.onSelect)||void 0===i||i.call(t,void 0,e,n,r);return}null===(a=t.onSelect)||void 0===a||a.call(t,e,e,n,r)}};return th.jsx(nr.Provider,{value:r,children:n})}function na(){var e=(0,d.useContext)(nr);if(!e)throw Error("useSelectSingle must be used within a SelectSingleProvider");return e}function nl(e){var t,n,r,o,i,a,l,s,u,f,p,h,m,g,v,y,b,x,w,k,S,E,O,C,j,_,P,T,M,N,A,I,R,D,L,z,Z,B,F,H,q,U,W=(0,d.useRef)(null),K=(t=e.date,n=e.displayMonth,a=tS(),l=nn(),s=t9(t,t7(),n),u=tS(),f=na(),p=tK(),h=tG(),g=(m=nn()).focusDayAfter,v=m.focusDayBefore,y=m.focusWeekAfter,b=m.focusWeekBefore,x=m.blur,w=m.focus,k=m.focusMonthBefore,S=m.focusMonthAfter,E=m.focusYearBefore,O=m.focusYearAfter,C=m.focusStartOfWeek,j=m.focusEndOfWeek,_={onClick:function(e){var n,r,o,i;tv(u)?null===(n=f.onDayClick)||void 0===n||n.call(f,t,s,e):tm(u)?null===(r=p.onDayClick)||void 0===r||r.call(p,t,s,e):tg(u)?null===(o=h.onDayClick)||void 0===o||o.call(h,t,s,e):null===(i=u.onDayClick)||void 0===i||i.call(u,t,s,e)},onFocus:function(e){var n;w(t),null===(n=u.onDayFocus)||void 0===n||n.call(u,t,s,e)},onBlur:function(e){var n;x(),null===(n=u.onDayBlur)||void 0===n||n.call(u,t,s,e)},onKeyDown:function(e){var n;switch(e.key){case"ArrowLeft":e.preventDefault(),e.stopPropagation(),"rtl"===u.dir?g():v();break;case"ArrowRight":e.preventDefault(),e.stopPropagation(),"rtl"===u.dir?v():g();break;case"ArrowDown":e.preventDefault(),e.stopPropagation(),y();break;case"ArrowUp":e.preventDefault(),e.stopPropagation(),b();break;case"PageUp":e.preventDefault(),e.stopPropagation(),e.shiftKey?E():k();break;case"PageDown":e.preventDefault(),e.stopPropagation(),e.shiftKey?O():S();break;case"Home":e.preventDefault(),e.stopPropagation(),C();break;case"End":e.preventDefault(),e.stopPropagation(),j()}null===(n=u.onDayKeyDown)||void 0===n||n.call(u,t,s,e)},onKeyUp:function(e){var n;null===(n=u.onDayKeyUp)||void 0===n||n.call(u,t,s,e)},onMouseEnter:function(e){var n;null===(n=u.onDayMouseEnter)||void 0===n||n.call(u,t,s,e)},onMouseLeave:function(e){var n;null===(n=u.onDayMouseLeave)||void 0===n||n.call(u,t,s,e)},onPointerEnter:function(e){var n;null===(n=u.onDayPointerEnter)||void 0===n||n.call(u,t,s,e)},onPointerLeave:function(e){var n;null===(n=u.onDayPointerLeave)||void 0===n||n.call(u,t,s,e)},onTouchCancel:function(e){var n;null===(n=u.onDayTouchCancel)||void 0===n||n.call(u,t,s,e)},onTouchEnd:function(e){var n;null===(n=u.onDayTouchEnd)||void 0===n||n.call(u,t,s,e)},onTouchMove:function(e){var n;null===(n=u.onDayTouchMove)||void 0===n||n.call(u,t,s,e)},onTouchStart:function(e){var n;null===(n=u.onDayTouchStart)||void 0===n||n.call(u,t,s,e)}},P=tS(),T=na(),M=tK(),N=tG(),A=tv(P)?T.selected:tm(P)?M.selected:tg(P)?N.selected:void 0,I=!!(a.onDayClick||"default"!==a.mode),(0,d.useEffect)(function(){var e;!s.outside&&l.focusedDay&&I&&tr(l.focusedDay,t)&&(null===(e=W.current)||void 0===e||e.focus())},[l.focusedDay,t,W,I,s.outside]),D=(R=[a.classNames.day],Object.keys(s).forEach(function(e){var t=a.modifiersClassNames[e];if(t)R.push(t);else if(Object.values(c).includes(e)){var n=a.classNames["day_".concat(e)];n&&R.push(n)}}),R).join(" "),L=tu({},a.styles.day),Object.keys(s).forEach(function(e){var t;L=tu(tu({},L),null===(t=a.modifiersStyles)||void 0===t?void 0:t[e])}),z=L,Z=!!(s.outside&&!a.showOutsideDays||s.hidden),B=null!==(i=null===(o=a.components)||void 0===o?void 0:o.DayContent)&&void 0!==i?i:tH,F={style:z,className:D,children:th.jsx(B,{date:t,displayMonth:n,activeModifiers:s}),role:"gridcell"},H=l.focusTarget&&tr(l.focusTarget,t)&&!s.outside,q=l.focusedDay&&tr(l.focusedDay,t),U=tu(tu(tu({},F),((r={disabled:s.disabled,role:"gridcell"})["aria-selected"]=s.selected,r.tabIndex=q||H?0:-1,r)),_),{isButton:I,isHidden:Z,activeModifiers:s,selectedDays:A,buttonProps:U,divProps:F});return K.isHidden?th.jsx("div",{role:"gridcell"}):K.isButton?th.jsx(tR,tu({name:"day",ref:W},K.buttonProps)):th.jsx("div",tu({},K.divProps))}function ns(e){var t=e.number,n=e.dates,r=tS(),o=r.onWeekNumberClick,i=r.styles,a=r.classNames,l=r.locale,s=r.labels.labelWeekNumber,c=(0,r.formatters.formatWeekNumber)(Number(t),{locale:l});if(!o)return th.jsx("span",{className:a.weeknumber,style:i.weeknumber,children:c});var u=s(Number(t),{locale:l});return th.jsx(tR,{name:"week-number","aria-label":u,className:a.weeknumber,style:i.weeknumber,onClick:function(e){o(t,n,e)},children:c})}function nc(e){var t,n,r,o=tS(),i=o.styles,a=o.classNames,l=o.showWeekNumber,s=o.components,c=null!==(t=null==s?void 0:s.Day)&&void 0!==t?t:nl,u=null!==(n=null==s?void 0:s.WeekNumber)&&void 0!==n?n:ns;return l&&(r=th.jsx("td",{className:a.cell,style:i.cell,children:th.jsx(u,{number:e.weekNumber,dates:e.dates})})),th.jsxs("tr",{className:a.row,style:i.row,children:[r,e.dates.map(function(t){return th.jsx("td",{className:a.cell,style:i.cell,role:"presentation",children:th.jsx(c,{displayMonth:e.displayMonth,date:t})},function(e){return(0,ei.Z)(1,arguments),Math.floor(function(e){return(0,ei.Z)(1,arguments),(0,eo.Z)(e).getTime()}(e)/1e3)}(t))})]})}function nu(e,t,n){for(var r=(null==n?void 0:n.ISOWeek)?tc(t):ts(t,n),o=(null==n?void 0:n.ISOWeek)?tn(e):tt(e,n),i=ti(r,o),a=[],l=0;l<=i;l++)a.push((0,eh.Z)(o,l));return a.reduce(function(e,t){var r=(null==n?void 0:n.ISOWeek)?function(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e);return Math.round((tn(t).getTime()-(function(e){(0,ei.Z)(1,arguments);var t=function(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e),n=t.getFullYear(),r=new Date(0);r.setFullYear(n+1,0,4),r.setHours(0,0,0,0);var o=tn(r),i=new Date(0);i.setFullYear(n,0,4),i.setHours(0,0,0,0);var a=tn(i);return t.getTime()>=o.getTime()?n+1:t.getTime()>=a.getTime()?n:n-1}(e),n=new Date(0);return n.setFullYear(t,0,4),n.setHours(0,0,0,0),tn(n)})(t).getTime())/6048e5)+1}(t):function(e,t){(0,ei.Z)(1,arguments);var n=(0,eo.Z)(e);return Math.round((tt(n,t).getTime()-(function(e,t){(0,ei.Z)(1,arguments);var n,r,o,i,a,l,s,c,u=(0,em.Z)(null!==(n=null!==(r=null!==(o=null!==(i=null==t?void 0:t.firstWeekContainsDate)&&void 0!==i?i:null==t?void 0:null===(a=t.locale)||void 0===a?void 0:null===(l=a.options)||void 0===l?void 0:l.firstWeekContainsDate)&&void 0!==o?o:eS.firstWeekContainsDate)&&void 0!==r?r:null===(s=eS.locale)||void 0===s?void 0:null===(c=s.options)||void 0===c?void 0:c.firstWeekContainsDate)&&void 0!==n?n:1),d=function(e,t){(0,ei.Z)(1,arguments);var n,r,o,i,a,l,s,c,u=(0,eo.Z)(e),d=u.getFullYear(),f=(0,em.Z)(null!==(n=null!==(r=null!==(o=null!==(i=null==t?void 0:t.firstWeekContainsDate)&&void 0!==i?i:null==t?void 0:null===(a=t.locale)||void 0===a?void 0:null===(l=a.options)||void 0===l?void 0:l.firstWeekContainsDate)&&void 0!==o?o:eS.firstWeekContainsDate)&&void 0!==r?r:null===(s=eS.locale)||void 0===s?void 0:null===(c=s.options)||void 0===c?void 0:c.firstWeekContainsDate)&&void 0!==n?n:1);if(!(f>=1&&f<=7))throw RangeError("firstWeekContainsDate must be between 1 and 7 inclusively");var p=new Date(0);p.setFullYear(d+1,0,f),p.setHours(0,0,0,0);var h=tt(p,t),m=new Date(0);m.setFullYear(d,0,f),m.setHours(0,0,0,0);var g=tt(m,t);return u.getTime()>=h.getTime()?d+1:u.getTime()>=g.getTime()?d:d-1}(e,t),f=new Date(0);return f.setFullYear(d,0,u),f.setHours(0,0,0,0),tt(f,t)})(n,t).getTime())/6048e5)+1}(t,n),o=e.find(function(e){return e.weekNumber===r});return o?o.dates.push(t):e.push({weekNumber:r,dates:[t]}),e},[])}function nd(e){var t,n,r,o=tS(),i=o.locale,a=o.classNames,l=o.styles,s=o.hideHead,c=o.fixedWeeks,u=o.components,d=o.weekStartsOn,f=o.firstWeekContainsDate,p=o.ISOWeek,h=function(e,t){var n=nu(es(e),e6(e),t);if(null==t?void 0:t.useFixedWeeks){var r=function(e,t){return(0,ei.Z)(1,arguments),function(e,t,n){(0,ei.Z)(2,arguments);var r=tt(e,n),o=tt(t,n);return Math.round((r.getTime()-eD(r)-(o.getTime()-eD(o)))/6048e5)}(function(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e),n=t.getMonth();return t.setFullYear(t.getFullYear(),n+1,0),t.setHours(0,0,0,0),t}(e),es(e),t)+1}(e,t);if(r<6){var o=n[n.length-1],i=o.dates[o.dates.length-1],a=ta(i,6-r),l=nu(ta(i,1),a,t);n.push.apply(n,l)}}return n}(e.displayMonth,{useFixedWeeks:!!c,ISOWeek:p,locale:i,weekStartsOn:d,firstWeekContainsDate:f}),m=null!==(t=null==u?void 0:u.Head)&&void 0!==t?t:tF,g=null!==(n=null==u?void 0:u.Row)&&void 0!==n?n:nc,v=null!==(r=null==u?void 0:u.Footer)&&void 0!==r?r:tZ;return th.jsxs("table",{id:e.id,className:a.table,style:l.table,role:"grid","aria-labelledby":e["aria-labelledby"],children:[!s&&th.jsx(m,{}),th.jsx("tbody",{className:a.tbody,style:l.tbody,children:h.map(function(t){return th.jsx(g,{displayMonth:e.displayMonth,dates:t.dates,weekNumber:t.weekNumber},t.weekNumber)})}),th.jsx(v,{displayMonth:e.displayMonth})]})}var nf="undefined"!=typeof window&&window.document&&window.document.createElement?d.useLayoutEffect:d.useEffect,np=!1,nh=0;function nm(){return"react-day-picker-".concat(++nh)}function ng(e){var t,n,r,o,i,a,l,s,c=tS(),u=c.dir,f=c.classNames,p=c.styles,h=c.components,m=tM().displayMonths,g=(r=null!=(t=c.id?"".concat(c.id,"-").concat(e.displayIndex):void 0)?t:np?nm():null,i=(o=(0,d.useState)(r))[0],a=o[1],nf(function(){null===i&&a(nm())},[]),(0,d.useEffect)(function(){!1===np&&(np=!0)},[]),null!==(n=null!=t?t:i)&&void 0!==n?n:void 0),v=c.id?"".concat(c.id,"-grid-").concat(e.displayIndex):void 0,y=[f.month],b=p.month,x=0===e.displayIndex,w=e.displayIndex===m.length-1,k=!x&&!w;"rtl"===u&&(w=(l=[x,w])[0],x=l[1]),x&&(y.push(f.caption_start),b=tu(tu({},b),p.caption_start)),w&&(y.push(f.caption_end),b=tu(tu({},b),p.caption_end)),k&&(y.push(f.caption_between),b=tu(tu({},b),p.caption_between));var S=null!==(s=null==h?void 0:h.Caption)&&void 0!==s?s:tz;return th.jsxs("div",{className:y.join(" "),style:b,children:[th.jsx(S,{id:g,displayMonth:e.displayMonth,displayIndex:e.displayIndex}),th.jsx(nd,{id:v,"aria-labelledby":g,displayMonth:e.displayMonth})]},e.displayIndex)}function nv(e){var t=tS(),n=t.classNames,r=t.styles;return th.jsx("div",{className:n.months,style:r.months,children:e.children})}function ny(e){var t,n,r=e.initialProps,o=tS(),i=nn(),a=tM(),l=(0,d.useState)(!1),s=l[0],c=l[1];(0,d.useEffect)(function(){o.initialFocus&&i.focusTarget&&(s||(i.focus(i.focusTarget),c(!0)))},[o.initialFocus,s,i.focus,i.focusTarget,i]);var u=[o.classNames.root,o.className];o.numberOfMonths>1&&u.push(o.classNames.multiple_months),o.showWeekNumber&&u.push(o.classNames.with_weeknumber);var f=tu(tu({},o.styles.root),o.style),p=Object.keys(r).filter(function(e){return e.startsWith("data-")}).reduce(function(e,t){var n;return tu(tu({},e),((n={})[t]=r[t],n))},{}),h=null!==(n=null===(t=r.components)||void 0===t?void 0:t.Months)&&void 0!==n?n:nv;return th.jsx("div",tu({className:u.join(" "),style:f,dir:o.dir,id:o.id,nonce:r.nonce,title:r.title,lang:r.lang},p,{children:th.jsx(h,{children:a.displayMonths.map(function(e,t){return th.jsx(ng,{displayIndex:t,displayMonth:e},t)})})}))}function nb(e){var t=e.children,n=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&0>t.indexOf(r)&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var o=0,r=Object.getOwnPropertySymbols(e);ot.indexOf(r[o])&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]]);return n}(e,["children"]);return th.jsx(tk,{initialProps:n,children:th.jsx(tT,{children:th.jsx(no,{initialProps:n,children:th.jsx(tU,{initialProps:n,children:th.jsx(t$,{initialProps:n,children:th.jsx(t5,{children:th.jsx(nt,{children:t})})})})})})})}function nx(e){return th.jsx(nb,tu({},e,{children:th.jsx(ny,{initialProps:e})}))}let nw=e=>{var t=(0,u._T)(e,[]);return d.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),d.createElement("path",{d:"M10.8284 12.0007L15.7782 16.9504L14.364 18.3646L8 12.0007L14.364 5.63672L15.7782 7.05093L10.8284 12.0007Z"}))},nk=e=>{var t=(0,u._T)(e,[]);return d.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),d.createElement("path",{d:"M13.1717 12.0007L8.22192 7.05093L9.63614 5.63672L16.0001 12.0007L9.63614 18.3646L8.22192 16.9504L13.1717 12.0007Z"}))},nS=e=>{var t=(0,u._T)(e,[]);return d.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),d.createElement("path",{d:"M4.83582 12L11.0429 18.2071L12.4571 16.7929L7.66424 12L12.4571 7.20712L11.0429 5.79291L4.83582 12ZM10.4857 12L16.6928 18.2071L18.107 16.7929L13.3141 12L18.107 7.20712L16.6928 5.79291L10.4857 12Z"}))},nE=e=>{var t=(0,u._T)(e,[]);return d.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),d.createElement("path",{d:"M19.1642 12L12.9571 5.79291L11.5429 7.20712L16.3358 12L11.5429 16.7929L12.9571 18.2071L19.1642 12ZM13.5143 12L7.30722 5.79291L5.89301 7.20712L10.6859 12L5.89301 16.7929L7.30722 18.2071L13.5143 12Z"}))};var nO=n(84264);n(41649);var nC=n(1526),nj=n(7084),n_=n(26898);let nP={xs:{paddingX:"px-2",paddingY:"py-0.5",fontSize:"text-xs"},sm:{paddingX:"px-2.5",paddingY:"py-1",fontSize:"text-sm"},md:{paddingX:"px-3",paddingY:"py-1.5",fontSize:"text-md"},lg:{paddingX:"px-3.5",paddingY:"py-1.5",fontSize:"text-lg"},xl:{paddingX:"px-3.5",paddingY:"py-1.5",fontSize:"text-xl"}},nT={xs:{paddingX:"px-2",paddingY:"py-0.5",fontSize:"text-xs"},sm:{paddingX:"px-2.5",paddingY:"py-0.5",fontSize:"text-sm"},md:{paddingX:"px-3",paddingY:"py-0.5",fontSize:"text-md"},lg:{paddingX:"px-3.5",paddingY:"py-0.5",fontSize:"text-lg"},xl:{paddingX:"px-4",paddingY:"py-1",fontSize:"text-xl"}},nM={xs:{height:"h-4",width:"w-4"},sm:{height:"h-4",width:"w-4"},md:{height:"h-4",width:"w-4"},lg:{height:"h-5",width:"w-5"},xl:{height:"h-6",width:"w-6"}},nN={[nj.wu.Increase]:{bgColor:(0,eJ.bM)(nj.fr.Emerald,n_.K.background).bgColor,textColor:(0,eJ.bM)(nj.fr.Emerald,n_.K.text).textColor},[nj.wu.ModerateIncrease]:{bgColor:(0,eJ.bM)(nj.fr.Emerald,n_.K.background).bgColor,textColor:(0,eJ.bM)(nj.fr.Emerald,n_.K.text).textColor},[nj.wu.Decrease]:{bgColor:(0,eJ.bM)(nj.fr.Rose,n_.K.background).bgColor,textColor:(0,eJ.bM)(nj.fr.Rose,n_.K.text).textColor},[nj.wu.ModerateDecrease]:{bgColor:(0,eJ.bM)(nj.fr.Rose,n_.K.background).bgColor,textColor:(0,eJ.bM)(nj.fr.Rose,n_.K.text).textColor},[nj.wu.Unchanged]:{bgColor:(0,eJ.bM)(nj.fr.Orange,n_.K.background).bgColor,textColor:(0,eJ.bM)(nj.fr.Orange,n_.K.text).textColor}},nA={[nj.wu.Increase]:e=>{var t=(0,u._T)(e,[]);return d.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),d.createElement("path",{d:"M13.0001 7.82843V20H11.0001V7.82843L5.63614 13.1924L4.22192 11.7782L12.0001 4L19.7783 11.7782L18.3641 13.1924L13.0001 7.82843Z"}))},[nj.wu.ModerateIncrease]:e=>{var t=(0,u._T)(e,[]);return d.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),d.createElement("path",{d:"M16.0037 9.41421L7.39712 18.0208L5.98291 16.6066L14.5895 8H7.00373V6H18.0037V17H16.0037V9.41421Z"}))},[nj.wu.Decrease]:e=>{var t=(0,u._T)(e,[]);return d.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),d.createElement("path",{d:"M13.0001 16.1716L18.3641 10.8076L19.7783 12.2218L12.0001 20L4.22192 12.2218L5.63614 10.8076L11.0001 16.1716V4H13.0001V16.1716Z"}))},[nj.wu.ModerateDecrease]:e=>{var t=(0,u._T)(e,[]);return d.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),d.createElement("path",{d:"M14.5895 16.0032L5.98291 7.39664L7.39712 5.98242L16.0037 14.589V7.00324H18.0037V18.0032H7.00373V16.0032H14.5895Z"}))},[nj.wu.Unchanged]:e=>{var t=(0,u._T)(e,[]);return d.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),d.createElement("path",{d:"M16.1716 10.9999L10.8076 5.63589L12.2218 4.22168L20 11.9999L12.2218 19.778L10.8076 18.3638L16.1716 12.9999H4V10.9999H16.1716Z"}))}},nI=(0,eJ.fn)("BadgeDelta");d.forwardRef((e,t)=>{let{deltaType:n=nj.wu.Increase,isIncreasePositive:r=!0,size:o=nj.u8.SM,tooltip:i,children:a,className:l}=e,s=(0,u._T)(e,["deltaType","isIncreasePositive","size","tooltip","children","className"]),c=nA[n],f=(0,eJ.Fo)(n,r),p=a?nT:nP,{tooltipProps:h,getReferenceProps:m}=(0,nC.l)();return d.createElement("span",Object.assign({ref:(0,eJ.lq)([t,h.refs.setReference]),className:(0,ec.q)(nI("root"),"w-max flex-shrink-0 inline-flex justify-center items-center cursor-default rounded-tremor-full bg-opacity-20 dark:bg-opacity-25",nN[f].bgColor,nN[f].textColor,p[o].paddingX,p[o].paddingY,p[o].fontSize,l)},m,s),d.createElement(nC.Z,Object.assign({text:i},h)),d.createElement(c,{className:(0,ec.q)(nI("icon"),"shrink-0",a?(0,ec.q)("-ml-1 mr-1.5"):nM[o].height,nM[o].width)}),a?d.createElement("p",{className:(0,ec.q)(nI("text"),"text-sm whitespace-nowrap")},a):null)}).displayName="BadgeDelta";var nR=n(47323);let nD=e=>{var{onClick:t,icon:n}=e,r=(0,u._T)(e,["onClick","icon"]);return d.createElement("button",Object.assign({type:"button",className:(0,ec.q)("flex items-center justify-center p-1 h-7 w-7 outline-none focus:ring-2 transition duration-100 border border-tremor-border dark:border-dark-tremor-border hover:bg-tremor-background-muted dark:hover:bg-dark-tremor-background-muted rounded-tremor-small focus:border-tremor-brand-subtle select-none dark:focus:border-dark-tremor-brand-subtle focus:ring-tremor-brand-muted dark:focus:ring-dark-tremor-brand-muted text-tremor-content-subtle dark:text-dark-tremor-content-subtle hover:text-tremor-content dark:hover:text-dark-tremor-content")},r),d.createElement(nR.Z,{onClick:t,icon:n,variant:"simple",color:"slate",size:"sm"}))};function nL(e){var{mode:t,defaultMonth:n,selected:r,onSelect:o,locale:i,disabled:a,enableYearNavigation:l,classNames:s,weekStartsOn:c=0}=e,f=(0,u._T)(e,["mode","defaultMonth","selected","onSelect","locale","disabled","enableYearNavigation","classNames","weekStartsOn"]);return d.createElement(nx,Object.assign({showOutsideDays:!0,mode:t,defaultMonth:n,selected:r,onSelect:o,locale:i,disabled:a,weekStartsOn:c,classNames:Object.assign({months:"flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",month:"space-y-4",caption:"flex justify-center pt-2 relative items-center",caption_label:"text-tremor-default text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis font-medium",nav:"space-x-1 flex items-center",nav_button:"flex items-center justify-center p-1 h-7 w-7 outline-none focus:ring-2 transition duration-100 border border-tremor-border dark:border-dark-tremor-border hover:bg-tremor-background-muted dark:hover:bg-dark-tremor-background-muted rounded-tremor-small focus:border-tremor-brand-subtle dark:focus:border-dark-tremor-brand-subtle focus:ring-tremor-brand-muted dark:focus:ring-dark-tremor-brand-muted text-tremor-content-subtle dark:text-dark-tremor-content-subtle hover:text-tremor-content dark:hover:text-dark-tremor-content",nav_button_previous:"absolute left-1",nav_button_next:"absolute right-1",table:"w-full border-collapse space-y-1",head_row:"flex",head_cell:"w-9 font-normal text-center text-tremor-content-subtle dark:text-dark-tremor-content-subtle",row:"flex w-full mt-0.5",cell:"text-center p-0 relative focus-within:relative text-tremor-default text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis",day:"h-9 w-9 p-0 hover:bg-tremor-background-subtle dark:hover:bg-dark-tremor-background-subtle outline-tremor-brand dark:outline-dark-tremor-brand rounded-tremor-default",day_today:"font-bold",day_selected:"aria-selected:bg-tremor-background-emphasis aria-selected:text-tremor-content-inverted dark:aria-selected:bg-dark-tremor-background-emphasis dark:aria-selected:text-dark-tremor-content-inverted ",day_disabled:"text-tremor-content-subtle dark:text-dark-tremor-content-subtle disabled:hover:bg-transparent",day_outside:"text-tremor-content-subtle dark:text-dark-tremor-content-subtle"},s),components:{IconLeft:e=>{var t=(0,u._T)(e,[]);return d.createElement(nw,Object.assign({className:"h-4 w-4"},t))},IconRight:e=>{var t=(0,u._T)(e,[]);return d.createElement(nk,Object.assign({className:"h-4 w-4"},t))},Caption:e=>{var t=(0,u._T)(e,[]);let{goToMonth:n,nextMonth:r,previousMonth:o,currentMonth:a}=tM();return d.createElement("div",{className:"flex justify-between items-center"},d.createElement("div",{className:"flex items-center space-x-1"},l&&d.createElement(nD,{onClick:()=>a&&n(tl(a,-1)),icon:nS}),d.createElement(nD,{onClick:()=>o&&n(o),icon:nw})),d.createElement(nO.Z,{className:"text-tremor-default tabular-nums capitalize text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis font-medium"},eQ(t.displayMonth,"LLLL yyy",{locale:i})),d.createElement("div",{className:"flex items-center space-x-1"},d.createElement(nD,{onClick:()=>r&&n(r),icon:nk}),l&&d.createElement(nD,{onClick:()=>a&&n(tl(a,1)),icon:nE})))}}},f))}nL.displayName="DateRangePicker",n(27281);var nz=n(57365),nZ=n(44140);let nB=el(),nF=d.forwardRef((e,t)=>{var n,r;let{value:o,defaultValue:i,onValueChange:a,enableSelect:l=!0,minDate:s,maxDate:c,placeholder:f="Select range",selectPlaceholder:p="Select range",disabled:h=!1,locale:m=eK,enableClear:g=!0,displayFormat:v,children:y,className:b,enableYearNavigation:x=!1,weekStartsOn:w=0,disabledDates:k}=e,S=(0,u._T)(e,["value","defaultValue","onValueChange","enableSelect","minDate","maxDate","placeholder","selectPlaceholder","disabled","locale","enableClear","displayFormat","children","className","enableYearNavigation","weekStartsOn","disabledDates"]),[E,O]=(0,nZ.Z)(i,o),[C,j]=(0,d.useState)(!1),[_,P]=(0,d.useState)(!1),T=(0,d.useMemo)(()=>{let e=[];return s&&e.push({before:s}),c&&e.push({after:c}),[...e,...null!=k?k:[]]},[s,c,k]),M=(0,d.useMemo)(()=>{let e=new Map;return y?d.Children.forEach(y,t=>{var n;e.set(t.props.value,{text:null!==(n=(0,eu.qg)(t))&&void 0!==n?n:t.props.value,from:t.props.from,to:t.props.to})}):e4.forEach(t=>{e.set(t.value,{text:t.text,from:t.from,to:nB})}),e},[y]),N=(0,d.useMemo)(()=>{if(y)return(0,eu.sl)(y);let e=new Map;return e4.forEach(t=>e.set(t.value,t.text)),e},[y]),A=(null==E?void 0:E.selectValue)||"",I=e1(null==E?void 0:E.from,s,A,M),R=e2(null==E?void 0:E.to,c,A,M),D=I||R?e3(I,R,m,v):f,L=es(null!==(r=null!==(n=null!=R?R:I)&&void 0!==n?n:c)&&void 0!==r?r:nB),z=g&&!h;return d.createElement("div",Object.assign({ref:t,className:(0,ec.q)("w-full min-w-[10rem] relative flex justify-between text-tremor-default max-w-sm shadow-tremor-input dark:shadow-dark-tremor-input rounded-tremor-default",b)},S),d.createElement(J,{as:"div",className:(0,ec.q)("w-full",l?"rounded-l-tremor-default":"rounded-tremor-default",C&&"ring-2 ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted z-10")},d.createElement("div",{className:"relative w-full"},d.createElement(J.Button,{onFocus:()=>j(!0),onBlur:()=>j(!1),disabled:h,className:(0,ec.q)("w-full outline-none text-left whitespace-nowrap truncate focus:ring-2 transition duration-100 rounded-l-tremor-default flex flex-nowrap border pl-3 py-2","rounded-l-tremor-default border-tremor-border text-tremor-content-emphasis focus:border-tremor-brand-subtle focus:ring-tremor-brand-muted","dark:border-dark-tremor-border dark:text-dark-tremor-content-emphasis dark:focus:border-dark-tremor-brand-subtle dark:focus:ring-dark-tremor-brand-muted",l?"rounded-l-tremor-default":"rounded-tremor-default",z?"pr-8":"pr-4",(0,eu.um)((0,eu.Uh)(I||R),h))},d.createElement(en,{className:(0,ec.q)(e0("calendarIcon"),"flex-none shrink-0 h-5 w-5 -ml-0.5 mr-2","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle"),"aria-hidden":"true"}),d.createElement("p",{className:"truncate"},D)),z&&I?d.createElement("button",{type:"button",className:(0,ec.q)("absolute outline-none inset-y-0 right-0 flex items-center transition duration-100 mr-4"),onClick:e=>{e.preventDefault(),null==a||a({}),O({})}},d.createElement(er.Z,{className:(0,ec.q)(e0("clearIcon"),"flex-none h-4 w-4","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})):null),d.createElement(ee.u,{className:"absolute z-10 min-w-min left-0",enter:"transition ease duration-100 transform",enterFrom:"opacity-0 -translate-y-4",enterTo:"opacity-100 translate-y-0",leave:"transition ease duration-100 transform",leaveFrom:"opacity-100 translate-y-0",leaveTo:"opacity-0 -translate-y-4"},d.createElement(J.Panel,{focus:!0,className:(0,ec.q)("divide-y overflow-y-auto outline-none rounded-tremor-default p-3 border my-1","bg-tremor-background border-tremor-border divide-tremor-border shadow-tremor-dropdown","dark:bg-dark-tremor-background dark:border-dark-tremor-border dark:divide-dark-tremor-border dark:shadow-dark-tremor-dropdown")},d.createElement(nL,Object.assign({mode:"range",showOutsideDays:!0,defaultMonth:L,selected:{from:I,to:R},onSelect:e=>{null==a||a({from:null==e?void 0:e.from,to:null==e?void 0:e.to}),O({from:null==e?void 0:e.from,to:null==e?void 0:e.to})},locale:m,disabled:T,enableYearNavigation:x,classNames:{day_range_middle:(0,ec.q)("!rounded-none aria-selected:!bg-tremor-background-subtle aria-selected:dark:!bg-dark-tremor-background-subtle aria-selected:!text-tremor-content aria-selected:dark:!bg-dark-tremor-background-subtle"),day_range_start:"rounded-r-none rounded-l-tremor-small aria-selected:text-tremor-brand-inverted dark:aria-selected:text-dark-tremor-brand-inverted",day_range_end:"rounded-l-none rounded-r-tremor-small aria-selected:text-tremor-brand-inverted dark:aria-selected:text-dark-tremor-brand-inverted"},weekStartsOn:w},e))))),l&&d.createElement(et.R,{as:"div",className:(0,ec.q)("w-48 -ml-px rounded-r-tremor-default",_&&"ring-2 ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted z-10"),value:A,onChange:e=>{let{from:t,to:n}=M.get(e),r=null!=n?n:nB;null==a||a({from:t,to:r,selectValue:e}),O({from:t,to:r,selectValue:e})},disabled:h},e=>{var t;let{value:n}=e;return d.createElement(d.Fragment,null,d.createElement(et.R.Button,{onFocus:()=>P(!0),onBlur:()=>P(!1),className:(0,ec.q)("w-full outline-none text-left whitespace-nowrap truncate rounded-r-tremor-default transition duration-100 border px-4 py-2","border-tremor-border shadow-tremor-input text-tremor-content-emphasis focus:border-tremor-brand-subtle","dark:border-dark-tremor-border dark:shadow-dark-tremor-input dark:text-dark-tremor-content-emphasis dark:focus:border-dark-tremor-brand-subtle",(0,eu.um)((0,eu.Uh)(n),h))},n&&null!==(t=N.get(n))&&void 0!==t?t:p),d.createElement(ee.u,{className:"absolute z-10 w-full inset-x-0 right-0",enter:"transition ease duration-100 transform",enterFrom:"opacity-0 -translate-y-4",enterTo:"opacity-100 translate-y-0",leave:"transition ease duration-100 transform",leaveFrom:"opacity-100 translate-y-0",leaveTo:"opacity-0 -translate-y-4"},d.createElement(et.R.Options,{className:(0,ec.q)("divide-y overflow-y-auto outline-none border my-1","shadow-tremor-dropdown bg-tremor-background border-tremor-border divide-tremor-border rounded-tremor-default","dark:shadow-dark-tremor-dropdown dark:bg-dark-tremor-background dark:border-dark-tremor-border dark:divide-dark-tremor-border")},null!=y?y:e4.map(e=>d.createElement(nz.Z,{key:e.value,value:e.value},e.text)))))}))});nF.displayName="DateRangePicker"},92414:function(e,t,n){"use strict";n.d(t,{Z:function(){return v}});var r=n(5853),o=n(2265);n(42698),n(64016),n(8710);var i=n(33232),a=n(44140),l=n(58747);let s=e=>{var t=(0,r._T)(e,[]);return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),o.createElement("path",{d:"M18.031 16.6168L22.3137 20.8995L20.8995 22.3137L16.6168 18.031C15.0769 19.263 13.124 20 11 20C6.032 20 2 15.968 2 11C2 6.032 6.032 2 11 2C15.968 2 20 6.032 20 11C20 13.124 19.263 15.0769 18.031 16.6168ZM16.0247 15.8748C17.2475 14.6146 18 12.8956 18 11C18 7.1325 14.8675 4 11 4C7.1325 4 4 7.1325 4 11C4 14.8675 7.1325 18 11 18C12.8956 18 14.6146 17.2475 15.8748 16.0247L16.0247 15.8748Z"}))};var c=n(4537),u=n(9528),d=n(33044);let f=e=>{var t=(0,r._T)(e,[]);return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",width:"100%",height:"100%",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},t),o.createElement("line",{x1:"18",y1:"6",x2:"6",y2:"18"}),o.createElement("line",{x1:"6",y1:"6",x2:"18",y2:"18"}))};var p=n(97324),h=n(1153),m=n(96398);let g=(0,h.fn)("MultiSelect"),v=o.forwardRef((e,t)=>{let{defaultValue:n,value:h,onValueChange:v,placeholder:y="Select...",placeholderSearch:b="Search",disabled:x=!1,icon:w,children:k,className:S}=e,E=(0,r._T)(e,["defaultValue","value","onValueChange","placeholder","placeholderSearch","disabled","icon","children","className"]),[O,C]=(0,a.Z)(n,h),{reactElementChildren:j,optionsAvailable:_}=(0,o.useMemo)(()=>{let e=o.Children.toArray(k).filter(o.isValidElement);return{reactElementChildren:e,optionsAvailable:(0,m.n0)("",e)}},[k]),[P,T]=(0,o.useState)(""),M=(null!=O?O:[]).length>0,N=(0,o.useMemo)(()=>P?(0,m.n0)(P,j):_,[P,j,_]),A=()=>{T("")};return o.createElement(u.R,Object.assign({as:"div",ref:t,defaultValue:O,value:O,onChange:e=>{null==v||v(e),C(e)},disabled:x,className:(0,p.q)("w-full min-w-[10rem] relative text-tremor-default",S)},E,{multiple:!0}),e=>{let{value:t}=e;return o.createElement(o.Fragment,null,o.createElement(u.R.Button,{className:(0,p.q)("w-full outline-none text-left whitespace-nowrap truncate rounded-tremor-default focus:ring-2 transition duration-100 border pr-8 py-1.5","border-tremor-border shadow-tremor-input focus:border-tremor-brand-subtle focus:ring-tremor-brand-muted","dark:border-dark-tremor-border dark:shadow-dark-tremor-input dark:focus:border-dark-tremor-brand-subtle dark:focus:ring-dark-tremor-brand-muted",w?"pl-11 -ml-0.5":"pl-3",(0,m.um)(t.length>0,x))},w&&o.createElement("span",{className:(0,p.q)("absolute inset-y-0 left-0 flex items-center ml-px pl-2.5")},o.createElement(w,{className:(0,p.q)(g("Icon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})),o.createElement("div",{className:"h-6 flex items-center"},t.length>0?o.createElement("div",{className:"flex flex-nowrap overflow-x-scroll [&::-webkit-scrollbar]:hidden [scrollbar-width:none] gap-x-1 mr-5 -ml-1.5 relative"},_.filter(e=>t.includes(e.props.value)).map((e,n)=>{var r;return o.createElement("div",{key:n,className:(0,p.q)("max-w-[100px] lg:max-w-[200px] flex justify-center items-center pl-2 pr-1.5 py-1 font-medium","rounded-tremor-small","bg-tremor-background-muted dark:bg-dark-tremor-background-muted","bg-tremor-background-subtle dark:bg-dark-tremor-background-subtle","text-tremor-content-default dark:text-dark-tremor-content-default","text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis")},o.createElement("div",{className:"text-xs truncate "},null!==(r=e.props.children)&&void 0!==r?r:e.props.value),o.createElement("div",{onClick:n=>{n.preventDefault();let r=t.filter(t=>t!==e.props.value);null==v||v(r),C(r)}},o.createElement(f,{className:(0,p.q)(g("clearIconItem"),"cursor-pointer rounded-tremor-full w-3.5 h-3.5 ml-2","text-tremor-content-subtle hover:text-tremor-content","dark:text-dark-tremor-content-subtle dark:hover:text-tremor-content")})))})):o.createElement("span",null,y)),o.createElement("span",{className:(0,p.q)("absolute inset-y-0 right-0 flex items-center mr-2.5")},o.createElement(l.Z,{className:(0,p.q)(g("arrowDownIcon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")}))),M&&!x?o.createElement("button",{type:"button",className:(0,p.q)("absolute inset-y-0 right-0 flex items-center mr-8"),onClick:e=>{e.preventDefault(),C([]),null==v||v([])}},o.createElement(c.Z,{className:(0,p.q)(g("clearIconAllItems"),"flex-none h-4 w-4","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})):null,o.createElement(d.u,{className:"absolute z-10 w-full",enter:"transition ease duration-100 transform",enterFrom:"opacity-0 -translate-y-4",enterTo:"opacity-100 translate-y-0",leave:"transition ease duration-100 transform",leaveFrom:"opacity-100 translate-y-0",leaveTo:"opacity-0 -translate-y-4"},o.createElement(u.R.Options,{className:(0,p.q)("divide-y overflow-y-auto outline-none rounded-tremor-default max-h-[228px] left-0 border my-1","bg-tremor-background border-tremor-border divide-tremor-border shadow-tremor-dropdown","dark:bg-dark-tremor-background dark:border-dark-tremor-border dark:divide-dark-tremor-border dark:shadow-dark-tremor-dropdown")},o.createElement("div",{className:(0,p.q)("flex items-center w-full px-2.5","bg-tremor-background-muted","dark:bg-dark-tremor-background-muted")},o.createElement("span",null,o.createElement(s,{className:(0,p.q)("flex-none w-4 h-4 mr-2","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})),o.createElement("input",{name:"search",type:"input",autoComplete:"off",placeholder:b,className:(0,p.q)("w-full focus:outline-none focus:ring-none bg-transparent text-tremor-default py-2","text-tremor-content-emphasis","dark:text-dark-tremor-content-emphasis"),onKeyDown:e=>{"Space"===e.code&&""!==e.target.value&&e.stopPropagation()},onChange:e=>T(e.target.value),value:P})),o.createElement(i.Z.Provider,Object.assign({},{onBlur:{handleResetSearch:A}},{value:{selectedValue:t}}),N))))})});v.displayName="MultiSelect"},46030:function(e,t,n){"use strict";n.d(t,{Z:function(){return u}});var r=n(5853);n(42698),n(64016),n(8710);var o=n(33232),i=n(2265),a=n(97324),l=n(1153),s=n(9528);let c=(0,l.fn)("MultiSelectItem"),u=i.forwardRef((e,t)=>{let{value:n,className:u,children:d}=e,f=(0,r._T)(e,["value","className","children"]),{selectedValue:p}=(0,i.useContext)(o.Z),h=(0,l.NZ)(n,p);return i.createElement(s.R.Option,Object.assign({className:(0,a.q)(c("root"),"flex justify-start items-center cursor-default text-tremor-default p-2.5","ui-active:bg-tremor-background-muted ui-active:text-tremor-content-strong ui-selected:text-tremor-content-strong text-tremor-content-emphasis","dark:ui-active:bg-dark-tremor-background-muted dark:ui-active:text-dark-tremor-content-strong dark:ui-selected:text-dark-tremor-content-strong dark:ui-selected:bg-dark-tremor-background-muted dark:text-dark-tremor-content-emphasis",u),ref:t,key:n,value:n},f),i.createElement("input",{type:"checkbox",className:(0,a.q)(c("checkbox"),"flex-none focus:ring-none focus:outline-none cursor-pointer mr-2.5","accent-tremor-brand","dark:accent-dark-tremor-brand"),checked:h,readOnly:!0}),i.createElement("span",{className:"whitespace-nowrap truncate"},null!=d?d:n))});u.displayName="MultiSelectItem"},30150:function(e,t,n){"use strict";n.d(t,{Z:function(){return f}});var r=n(5853),o=n(2265);let i=e=>{var t=(0,r._T)(e,[]);return o.createElement("svg",Object.assign({},t,{xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:"2.5"}),o.createElement("path",{d:"M12 4v16m8-8H4"}))},a=e=>{var t=(0,r._T)(e,[]);return o.createElement("svg",Object.assign({},t,{xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:"2.5"}),o.createElement("path",{d:"M20 12H4"}))};var l=n(97324),s=n(1153),c=n(69262);let u="flex mx-auto text-tremor-content-subtle dark:text-dark-tremor-content-subtle",d="cursor-pointer hover:text-tremor-content dark:hover:text-dark-tremor-content",f=o.forwardRef((e,t)=>{let{onSubmit:n,enableStepper:f=!0,disabled:p,onValueChange:h,onChange:m}=e,g=(0,r._T)(e,["onSubmit","enableStepper","disabled","onValueChange","onChange"]),v=(0,o.useRef)(null),[y,b]=o.useState(!1),x=o.useCallback(()=>{b(!0)},[]),w=o.useCallback(()=>{b(!1)},[]),[k,S]=o.useState(!1),E=o.useCallback(()=>{S(!0)},[]),O=o.useCallback(()=>{S(!1)},[]);return o.createElement(c.Z,Object.assign({type:"number",ref:(0,s.lq)([v,t]),disabled:p,makeInputClassName:(0,s.fn)("NumberInput"),onKeyDown:e=>{var t;if("Enter"===e.key&&!e.ctrlKey&&!e.altKey&&!e.shiftKey){let e=null===(t=v.current)||void 0===t?void 0:t.value;null==n||n(parseFloat(null!=e?e:""))}"ArrowDown"===e.key&&x(),"ArrowUp"===e.key&&E()},onKeyUp:e=>{"ArrowDown"===e.key&&w(),"ArrowUp"===e.key&&O()},onChange:e=>{p||(null==h||h(parseFloat(e.target.value)),null==m||m(e))},stepper:f?o.createElement("div",{className:(0,l.q)("flex justify-center align-middle")},o.createElement("div",{tabIndex:-1,onClick:e=>e.preventDefault(),onMouseDown:e=>e.preventDefault(),onTouchStart:e=>{e.cancelable&&e.preventDefault()},onMouseUp:()=>{var e,t;p||(null===(e=v.current)||void 0===e||e.stepDown(),null===(t=v.current)||void 0===t||t.dispatchEvent(new Event("input",{bubbles:!0})))},className:(0,l.q)(!p&&d,u,"group py-[10px] px-2.5 border-l border-tremor-border dark:border-dark-tremor-border")},o.createElement(a,{"data-testid":"step-down",className:(y?"scale-95":"")+" h-4 w-4 duration-75 transition group-active:scale-95"})),o.createElement("div",{tabIndex:-1,onClick:e=>e.preventDefault(),onMouseDown:e=>e.preventDefault(),onTouchStart:e=>{e.cancelable&&e.preventDefault()},onMouseUp:()=>{var e,t;p||(null===(e=v.current)||void 0===e||e.stepUp(),null===(t=v.current)||void 0===t||t.dispatchEvent(new Event("input",{bubbles:!0})))},className:(0,l.q)(!p&&d,u,"group py-[10px] px-2.5 border-l border-tremor-border dark:border-dark-tremor-border")},o.createElement(i,{"data-testid":"step-up",className:(k?"scale-95":"")+" h-4 w-4 duration-75 transition group-active:scale-95"}))):null},g))});f.displayName="NumberInput"},54250:function(e,t,n){"use strict";n.d(t,{Z:function(){return m}});var r=n(5853),o=n(2265),i=n(44140),a=n(34237),l=n(33044),s=n(58747),c=n(4537),u=n(97324),d=n(1153),f=n(96398);let p=(0,d.fn)("SearchSelect"),h=(0,d.fn)("SearchSelect"),m=o.forwardRef((e,t)=>{let{defaultValue:n,value:d,onValueChange:m,placeholder:g="Select...",disabled:v=!1,icon:y,enableClear:b=!0,children:x,className:w}=e,k=(0,r._T)(e,["defaultValue","value","onValueChange","placeholder","disabled","icon","enableClear","children","className"]),[S,E]=(0,o.useState)(""),[O,C]=(0,i.Z)(n,d),{reactElementChildren:j,valueToNameMapping:_}=(0,o.useMemo)(()=>{let e=o.Children.toArray(x).filter(o.isValidElement);return{reactElementChildren:e,valueToNameMapping:(0,f.sl)(e)}},[x]),P=(0,o.useMemo)(()=>(0,f.n0)(S,j),[S,j]);return o.createElement(a.h,Object.assign({as:"div",ref:t,defaultValue:O,value:O,onChange:e=>{null==m||m(e),C(e)},disabled:v,className:(0,u.q)("w-full min-w-[10rem] relative text-tremor-default",w)},k),e=>{let{value:t}=e;return o.createElement(o.Fragment,null,o.createElement(a.h.Button,{className:"w-full"},y&&o.createElement("span",{className:(0,u.q)("absolute inset-y-0 left-0 flex items-center ml-px pl-2.5")},o.createElement(y,{className:(0,u.q)(p("Icon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})),o.createElement(a.h.Input,{className:(0,u.q)("w-full outline-none text-left whitespace-nowrap truncate rounded-tremor-default focus:ring-2 transition duration-100 text-tremor-default pr-14 border py-2","border-tremor-border shadow-tremor-input focus:border-tremor-brand-subtle focus:ring-tremor-brand-muted","dark:border-dark-tremor-border dark:shadow-dark-tremor-input dark:focus:border-dark-tremor-brand-subtle dark:focus:ring-dark-tremor-brand-muted",y?"pl-10":"pl-3",v?"placeholder:text-tremor-content-subtle dark:placeholder:text-tremor-content-subtle":"placeholder:text-tremor-content dark:placeholder:text-tremor-content",(0,f.um)((0,f.Uh)(t),v)),placeholder:g,onChange:e=>E(e.target.value),displayValue:e=>{var t;return null!==(t=_.get(e))&&void 0!==t?t:""}}),o.createElement("div",{className:(0,u.q)("absolute inset-y-0 right-0 flex items-center pr-2.5")},o.createElement(s.Z,{className:(0,u.q)(p("arrowDownIcon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")}))),b&&O?o.createElement("button",{type:"button",className:(0,u.q)("absolute inset-y-0 right-0 flex items-center mr-8"),onClick:e=>{e.preventDefault(),C(""),E(""),null==m||m("")}},o.createElement(c.Z,{className:(0,u.q)(h("clearIcon"),"flex-none h-4 w-4","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})):null,P.length>0&&o.createElement(l.u,{className:"absolute z-10 w-full",enter:"transition ease duration-100 transform",enterFrom:"opacity-0 -translate-y-4",enterTo:"opacity-100 translate-y-0",leave:"transition ease duration-100 transform",leaveFrom:"opacity-100 translate-y-0",leaveTo:"opacity-0 -translate-y-4"},o.createElement(a.h.Options,{className:(0,u.q)("divide-y overflow-y-auto outline-none rounded-tremor-default text-tremor-default max-h-[228px] left-0 border my-1","bg-tremor-background border-tremor-border divide-tremor-border shadow-tremor-dropdown","dark:bg-dark-tremor-background dark:border-dark-tremor-border dark:divide-dark-tremor-border dark:shadow-dark-tremor-dropdown")},P)))})});m.displayName="SearchSelect"},70450:function(e,t,n){"use strict";n.d(t,{Z:function(){return c}});var r=n(5853),o=n(2265),i=n(97324),a=n(1153),l=n(34237);let s=(0,a.fn)("SearchSelectItem"),c=o.forwardRef((e,t)=>{let{value:n,icon:a,className:c,children:u}=e,d=(0,r._T)(e,["value","icon","className","children"]);return o.createElement(l.h.Option,Object.assign({className:(0,i.q)(s("root"),"flex justify-start items-center cursor-default text-tremor-default p-2.5","ui-active:bg-tremor-background-muted ui-active:text-tremor-content-strong ui-selected:text-tremor-content-strong ui-selected:bg-tremor-background-muted text-tremor-content-emphasis","dark:ui-active:bg-dark-tremor-background-muted dark:ui-active:text-dark-tremor-content-strong dark:ui-selected:text-dark-tremor-content-strong dark:ui-selected:bg-dark-tremor-background-muted dark:text-dark-tremor-content-emphasis",c),ref:t,key:n,value:n},d),a&&o.createElement(a,{className:(0,i.q)(s("icon"),"flex-none h-5 w-5 mr-3","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")}),o.createElement("span",{className:"whitespace-nowrap truncate"},null!=u?u:n))});c.displayName="SearchSelectItem"},27281:function(e,t,n){"use strict";n.d(t,{Z:function(){return h}});var r=n(5853),o=n(2265),i=n(58747),a=n(4537),l=n(97324),s=n(1153),c=n(96398),u=n(9528),d=n(33044),f=n(44140);let p=(0,s.fn)("Select"),h=o.forwardRef((e,t)=>{let{defaultValue:n,value:s,onValueChange:h,placeholder:m="Select...",disabled:g=!1,icon:v,enableClear:y=!0,children:b,className:x}=e,w=(0,r._T)(e,["defaultValue","value","onValueChange","placeholder","disabled","icon","enableClear","children","className"]),[k,S]=(0,f.Z)(n,s),E=(0,o.useMemo)(()=>{let e=o.Children.toArray(b).filter(o.isValidElement);return(0,c.sl)(e)},[b]);return o.createElement(u.R,Object.assign({as:"div",ref:t,defaultValue:k,value:k,onChange:e=>{null==h||h(e),S(e)},disabled:g,className:(0,l.q)("w-full min-w-[10rem] relative text-tremor-default",x)},w),e=>{var t;let{value:n}=e;return o.createElement(o.Fragment,null,o.createElement(u.R.Button,{className:(0,l.q)("w-full outline-none text-left whitespace-nowrap truncate rounded-tremor-default focus:ring-2 transition duration-100 border pr-8 py-2","border-tremor-border shadow-tremor-input focus:border-tremor-brand-subtle focus:ring-tremor-brand-muted","dark:border-dark-tremor-border dark:shadow-dark-tremor-input dark:focus:border-dark-tremor-brand-subtle dark:focus:ring-dark-tremor-brand-muted",v?"pl-10":"pl-3",(0,c.um)((0,c.Uh)(n),g))},v&&o.createElement("span",{className:(0,l.q)("absolute inset-y-0 left-0 flex items-center ml-px pl-2.5")},o.createElement(v,{className:(0,l.q)(p("Icon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})),o.createElement("span",{className:"w-[90%] block truncate"},n&&null!==(t=E.get(n))&&void 0!==t?t:m),o.createElement("span",{className:(0,l.q)("absolute inset-y-0 right-0 flex items-center mr-3")},o.createElement(i.Z,{className:(0,l.q)(p("arrowDownIcon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")}))),y&&k?o.createElement("button",{type:"button",className:(0,l.q)("absolute inset-y-0 right-0 flex items-center mr-8"),onClick:e=>{e.preventDefault(),S(""),null==h||h("")}},o.createElement(a.Z,{className:(0,l.q)(p("clearIcon"),"flex-none h-4 w-4","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})):null,o.createElement(d.u,{className:"absolute z-10 w-full",enter:"transition ease duration-100 transform",enterFrom:"opacity-0 -translate-y-4",enterTo:"opacity-100 translate-y-0",leave:"transition ease duration-100 transform",leaveFrom:"opacity-100 translate-y-0",leaveTo:"opacity-0 -translate-y-4"},o.createElement(u.R.Options,{className:(0,l.q)("divide-y overflow-y-auto outline-none rounded-tremor-default max-h-[228px] left-0 border my-1","bg-tremor-background border-tremor-border divide-tremor-border shadow-tremor-dropdown","dark:bg-dark-tremor-background dark:border-dark-tremor-border dark:divide-dark-tremor-border dark:shadow-dark-tremor-dropdown")},b)))})});h.displayName="Select"},57365:function(e,t,n){"use strict";n.d(t,{Z:function(){return s}});var r=n(5853),o=n(2265),i=n(9528),a=n(97324);let l=(0,n(1153).fn)("SelectItem"),s=o.forwardRef((e,t)=>{let{value:n,icon:s,className:c,children:u}=e,d=(0,r._T)(e,["value","icon","className","children"]);return o.createElement(i.R.Option,Object.assign({className:(0,a.q)(l("root"),"flex justify-start items-center cursor-default text-tremor-default px-2.5 py-2.5","ui-active:bg-tremor-background-muted ui-active:text-tremor-content-strong ui-selected:text-tremor-content-strong ui-selected:bg-tremor-background-muted text-tremor-content-emphasis","dark:ui-active:bg-dark-tremor-background-muted dark:ui-active:text-dark-tremor-content-strong dark:ui-selected:text-dark-tremor-content-strong dark:ui-selected:bg-dark-tremor-background-muted dark:text-dark-tremor-content-emphasis",c),ref:t,key:n,value:n},d),s&&o.createElement(s,{className:(0,a.q)(l("icon"),"flex-none w-5 h-5 mr-1.5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")}),o.createElement("span",{className:"whitespace-nowrap truncate"},null!=u?u:n))});s.displayName="SelectItem"},92858:function(e,t,n){"use strict";n.d(t,{Z:function(){return T}});var r=n(5853),o=n(2265),i=n(62963),a=n(90945),l=n(13323),s=n(17684),c=n(80004),u=n(93689),d=n(38198),f=n(47634),p=n(56314),h=n(27847),m=n(64518);let g=(0,o.createContext)(null),v=Object.assign((0,h.yV)(function(e,t){let n=(0,s.M)(),{id:r="headlessui-description-".concat(n),...i}=e,a=function e(){let t=(0,o.useContext)(g);if(null===t){let t=Error("You used a component, but it is not inside a relevant parent.");throw Error.captureStackTrace&&Error.captureStackTrace(t,e),t}return t}(),l=(0,u.T)(t);(0,m.e)(()=>a.register(r),[r,a.register]);let c={ref:l,...a.props,id:r};return(0,h.sY)({ourProps:c,theirProps:i,slot:a.slot||{},defaultTag:"p",name:a.name||"Description"})}),{});var y=n(37388);let b=(0,o.createContext)(null),x=Object.assign((0,h.yV)(function(e,t){let n=(0,s.M)(),{id:r="headlessui-label-".concat(n),passive:i=!1,...a}=e,l=function e(){let t=(0,o.useContext)(b);if(null===t){let t=Error("You used a

Z=)$yz~)lN`WkOM z6@rVN0}QgfJ-kLUAJ>eYFjxU~^lBEp?Der~@wlDzL{8;8`>2g|;^7?A-8Bdav5lAu z0<`Mp?Fc6njw>8zKW#TsD1I9D2vK{h-sF&t0&;LUlXnAs;02Mh_uyDsako*FP%}~$ znnn3dR>?o>(wkbrHhWCRM{ldj?9pUX)V-rKgeMwMDam|#3);zlAN+LdcAjH7(92ZK zf1|9HN5y8}E}2~Kfg_aKH@x^mcyJb4O)^InfwL;xrT5$a$vdBDWH7F*N^;?ZH6bd1Q(G|<6gGVZhxpZ-2pzSGqn7l;>< z@x>P${eDmNN{KKSIHHcY)i#5+nKH9Ok8Diucl=Q&T!H4B!oK+xwmWYA;W|3Zi>Or zeXEP;2m?b8az&9Vh!4x?w_TUs^Z$zu{$H^V6vi}HKi>UH#CLQF#maa9f2Ej-T8b9L&to@vw zzuLtVzm_+()WXf|eU-)&;i z{kpLHJvJ!PpK2~#g;vVxc{bg;l^&f=i*h)-N79x{I_;N57Ow^QzJWbl;40|EX;)qJ zk_4b8PVR+WaRAstm+1sa82dLknio)NeW*Rtydk1H{|HR90Y)-M1r@BMm6R9iLHsB0 zj;a0X-7XW6>8J{)ixdTL)k}MhzX1c}|DYz&K_zTXFoE3i&33G~u?tNQFLQVGQ3qo1 zh{JZD+3hRf=3K1cu(qP%qKkJ0qYW#(&sZD$maUpya*A=gJ7sLEiZYym_?RKigf?Lk zN2{ByKoW!*b#*oG;jeA7*yO6YnH1mUJL(ST+DmWECAQVXIZLdUuGHl(GQK&d`%1dm zC-_LSWpace|5p10o(VmPt)rMi(Z{S;xsPa|QGG`qzywpY{0bzIyKp>Vq zu{0}+%k2uUpi}qU^Ja0&CFZKW{6{A^k(l-X^Qa~5qF6LtK9Vz#KM0k_0yxY?DL}p9x@3W}zuaSELOr}G~iLNi-X}o%>p49d& z)hnA#G)y^W@%-3)9G#b#(1-V|K&94qUNs}0_izicwnqH^hMIeF2NsQv(Er#(hQM|K zW$4&}ROTTOfa{Crucs!%^8lIRD&xZ!n_I$!CNimjSm$oq^10>m8Q$d)Z)g1Eqs7>} zuZU5^uikM1oc9OG;@0t2#MkdeMb%RwdRI>qIY2ow+$Wp z8Anur_1>9(dN4XTZ<%CSFK*kW`5YH$yGW3js9Wu*0*BX|Ly{R#O}7wvqoQq1M_!=S zh6po&?pjNxudQ^G+S~%?^|o6LdSn$kwn(Wq8p4O7g5Ktd@8>GFN+jsYB=d!rRhyST zIcvSzq{Su~m%2r5UQmiV*u{+EvcxpD*HY5i$}^8CT(AwQ1?O>{jfBu>%&+k$G~ z0x8_!zO7Zr*X+$#@{73CeE3wzNSCFhr3Nft#Sg|0mb)VHQTxu~lzInr$4!*zF-)_B z6ZCD{Ib`(=AD)qv+gak+(1Gk`7Y2y9*SiRXj9=9EWhtGc?$X*oACPeLYVip*B6x9x3^TO7cHW<7t9_@(@O0R{Q2L6ek$|_-WJVJb$puW(G!4ZJSTN0xKl2ZgjMVOGp^L8T8x;WxYY zW&p}@%E!JrR( z91V|h7%S}5dpAl@I(oLUeJ099O0>V$?To5fl&4sk$Z^$Qy)SWbDh6q|xc`Eu^bL%0 ze3lg5xOU4sgpaHczfDIgYYe)spJV7QC|4*jgsu5DHamMXRUB1*)GKklos!P>veht> z`Q${tnnpjK7h@apJ3u~bN!Rlsac-bwEOp<(>2EnHW*3 zb$X3c9J`jG@ujlT>|6e8@XKF0eI{%#j^s-9g#w$JjfHJM3uoI@FzK7xXu9f4JgsK6 z@83y(u2NP++X%d8uED{c(M9if_GOm7f@*Bjsg3?m-C`2Ciu5d!E9@xZIubon0i)19R z5}07OoVJIOW>#xYiBE&EpnH0!NvV!F9x7Fkl+?Zfovo=Yb%W?O2EvB;*X z?~jc!IP5vl2a+JsIoThI)eTr5&}p{_=+7-oDa$Ug+YyfgSHJP%mm7MeO84>dEB3HW z)gO(K6(pZ|mZGk!`D-iH#L)=x*(bg z>)l;rl-mPAZ3bt17cX1O1s7tEiJ&Ig&cnq-ReH)=2cCIkQP7riym-CpklRUVrG zNj1*&d3?ek)fX4THHZ9Ne>*E`SW`CI*z(z@|NCQprQ*Q%US#M8t(tix&>IS+h}gkL zToocap^*&azs`;DNig19*nfwN{~bz3c*A;JUm7+z+%n!`&`1F7ZNzkRrSBFsm|y>S zckS?gQa^Q5|8^Pw{ULw-rapkLxuM!l-eT-8y~tkB&jtz*C=C6d0v}%u$;prJ-jceio{fozpA+xt@qdn0R>Z!5Mpu=T*NoR{%9FR}m%* zulQ1vC5=quf8PJ=6eB8sPW-OCLoNPCOz!hLe!gHXLrl9}sKX`f4zDqX%{@>MjQ>>l zyRkmgzej`f)sX$z-4qgr6guhP#(RW9BV zNal0{Nbti2$Vgs>sVzd%f1As%@VA2P=go0`Ouzc)VgXTH%lUVL=Ucwpy*26bzx`nX zCLX#0ai^{2?d42i97eV2QS3xw;MKonU&I+ z1-JOOxmfRnKAhqqzrk`#O|jgT(Z75a%WEgMok8)>q5oI_^{=1$j`U-TuqdDZR@I7_ zgT^(KdtS9;Fp*=bQA-;{cx0s1hdIgj<~P{4KHwAAKwyMw%Hf5*j*|^ficFG!AxDtt*I(=1bBaf9lc0T z?T){?zZKX*jp+NdNkz!iN|^V%6eeFHidqG;0xa)1v!daDx7>W-f5L=={=O_y;b{pi z?Hg2_sPPWlcYk(b7{Wo-JNI$FB4Bt5LaPY7TiIT-oUP7d{Ozwl-+=y;JBhv*a-fCg%%&O?5Yj&^&A0T+sO$*-Jjglh!4gI= z9Y0emw%l)e)nfKf2`^$yxPO($e)m$JFNlDTy(=G5W^Zh4ly`9vDluom`lSpO0fFsp zmORpfNs#?;++tdT8+&`n_xkr6J0AOqYrU@$_g9eeJI9po?&p&PncXV*raddwl9a^h zy9_^|d?;e@>x?Vtpz2Nfeto(qU(1tbAnDa^hMSm6n1Lt%t1}BY)Og${uKhlyRIYo1 zf7?2+;c^3y(0Gbj$q<7qu51H{EF>e-z>ek_^Dp;=5A>|M$1GNEEc*iD|AV?o z{ByUbB>z|t-yI6Q*EEyY3#k=_NY4Mf<-neX`;$tR(js#m%fZR1*wVD@a6XL_v|0Kd z=`YKoLWyvXUM$>5A-SgY86^7@_n5Hq@^|aWVuRv3&t}ux_?Pf>L=tS>)>e1sp_y#k z#6&XgiO>Ayer+#ZD`L`U50a#NKd@8$JP3X^bmzE#01wyVlK7GOUwhbJo*Q_KFh%*G z`Fg@qb0Q+B-k@R&J_y4r`kq07?+1xGD3Q_7gyGRk)6$4~C^$&{8k~3ej3m%9S3Gsa z9W7LIMX|V|^&G{IzqEG{?d$|jDAlFm#gHGRT6YCf<)(VxdN(yU@751nO%%T~eD!o! zn6LZfb6e&{2Oew;l_u#_ms+ZOKD%hqxrFtmOfrw4vp`3l#wMJ@L%VRbPP4hNoxSvX zXZ!Oj900T=?qh6~<)Kv1cpF2hPF*jIwJZBFHG9GNSiX2KW~xUutZMUYvk)M1B}V!; z$9(a;Ra9nkzm)(J;KCo_9x3GusK-!KQ-1>{(&qrURsM(1U-kO9rpJ^9K5BIu{P)u-zt z!Q}KOb^XgdY=~%78o=;3UMYTlvfcJB$vKr9+lyI>1}Df1K$F5>Yn{~LXVAnuco&0J z7N0G9ojUIsX03sWo<-4w*KgJKq5;gTcB8JZig@sV`deax(H3k z!J&~%MdC{Mc&JC0-LN~otz$lnyx;UyvmRJY!R-;S&ydo8yKU*nz zR&|w1BOZGymu!BazfvNeBPsqRftNZB+;Y3)ZQC)qiXli_cq(70f@VHe^;yF!Pes}7 z^mZF%h=)zf+p>Br(o^QmJ13yUF)JRBv!xL6e}+ngk7|B&RN`=~1MTalLqzA3lW17a67QYe#ya?5jKq132*g2V2l0sA5rT<>?2O;Bw(^+DM6+*R;qwav75@N}K;vI6vVkV}+{Yw-hr`)EduqR+7bda2F!;RWgBjN)Nd1LalV*+W(6ukH~b74D`e@$UooX zmhs|5a4Pgh|G_u(@S8rC)J1RxBY@Pi zkK6bEFGnKDr}b`7p9&jg!(uls<=9w>Z?S+&tCVyIe1xHKc28-(n1}rypV{Wf7^H6TC!tm(G3vG+(}>K_rpe9dV+S(|LlS<`toCfoh>vqy~!( zpV2c3Y_n;bWtjoh;>@@^$4t=6@bQ*s{^SQb?_;QyzcldPXEA(!f90M323^mN$^Lu` z2aD&VSsC6Z|8y&NN(Fyrg$6-lgBtaP2ZjR=o zLCV;G@J8ds)(=mm7Of{s_4aWp#Y+ah`rG_-zY^W5N^6?LE!!hFC893 zK#AvP1JAH5ugvmtVC!Ox6B@bPCy}Nx-Oc=j5RY@CJNsK+hn*7!G~H?m*S&?zsOMxu zSwopaJ8>(q6D(V_Yu^%C3dSd7YbubQsmEnv(_0+r!@c6jnFo=dkki~+ysq8;jO9K) z^Be*<+Ss8x)<+ZT_6pGUd3U&oE#~g>YAHM@UBVJ7=eexMOrAy`AtV&LcDFQUf3gng zh_-}QDQSs^YbHP0H%+W|Kt-4HYY)5A2*G-VQKcw(5Y4#d;!SCRs1>ecbAw|Vz{nn7 zy!FZ}Cv-GD`G~VOR^9A7D**jA@X}0wy@zbrZm@*=a}{U?*AX`;_}b1$QeM^Cg5iHjX11@V)C}MuR@idV*_lY-xw(;?AKtAx5$s>7 zzemz7WgWY;n3<|4or$@!(irP-TralUVf=q6d+V^Ow!Upt5d}o)MpC-FyBp~SrMqj< zh;%F(>6QlRW`T64ba!{>H{Dy`XP;-k=bY=iF8^3kbIvix7{3^Ij5&a=tSPRxFkY`) z420vd7l;JwTW;hr@t&wxLQf3>;J6V0MHJprrq1Yta|%*|q-|7>G_>f`ihkzSs-D_E zALA;qw7Ii!$(kbE^GetQ3|CaPPsEcW3A8e#sP<>wgH+2Fgw&=}6H;QvHq$%~6DSUC z?DF4lmA;FNc9&PQnn1T(UyO-9b$V0>!j3u$;GYl;Q}a7R`>zu3&ws}ND9^l<>A+_M zcU3qyCwEQR_1oKZW~m38Zr!BXW%=p@yZ5;U2 zjJUJd-ChBCL?$#6@hykTCXXlAg;w0Kebs8gLwb`sW zQdA#4K2YpSnbhN+QG+T!P#<3+2j-{F`5TB5z71lUWpQ!LXbMQ$6cL`lkhoFE{~4Fd z3bEDJ+9d%(7MwuExtdc7B?V(jzYUu8iJXE{9Nn4^SAHe7dNC zSc~~P+U_(CP<-NjP9Fk=TGG&|$P*dHMaoL>#iue}8<}Ozgly(|2Xf`|h);Gti1H~( z%uyHjEiGJTpL6krm!8gx`m!nDaSUbNaoS5gyv3eKpi*I|fBqUCJ`YUhT{3oay_p%Z z@j{)%A^38iQ^aIQ7hpvMVBO5^S%{yFteqZJC#$55k>?Gisci|;XiZ|2$I4(8T`QSv z>!n#boA}12r6l+P`t=>%!;rbF5qkJ1#f^kb2g371URE#uTgz9??Ls)bqs4dbvs3(| zFZd9p;%VgkU$3im4Me=*|iD=luZl3KlGuq%lA;eAZLzMD{M<#YE zRb=eaMlR3##l_`cjhkZ_`1^~FIHb-McqYZDmqwOb@eO%D8o_~1I54M3Q6@lm~V9sxwFQLdUC^(*0FOSXWzP^s5 zz>d-6koeTr< z10p{~znJuP8HK{=kxUQfpp89JFsUC*mr0U50^^#)iofG^QF1$yEDZnA73D53)nD6< ze?k~J_p9We6Mr`QUv&}Ca0wC+@S)~C`9cnfjFU`*L31Pta6XFIOz>l*{KoJAuXkxC zUv)f1q1ni$y!rm)82@u%fR=}L8id&S*BlUuE3NM{_o?q2sW-gEOi zu#v@1x|7xpB=zp?N}vo%$aXdwyg~rvB#Wr(dNWW@`KDjhCSwQ>zqV&8>TS9QD7HtK zELB)ThS}}ju)$;Jwfw}QF+9zXij!XnZ&GLU<5#%rOPRUq>3x??GA9B+FyMPh58xy z^KGIsM%!?nQj3x*o#B3x{QcbSp0H&iNj)eGk5zqx+Nhb=D+>juC+7%&Jm$Lvx0?51 zR&qRkC%U(-k_Vs%C-1G0M+1xUaKk;_zcXgiU?_I5j_-rKN4Ak)(0&hLjhxaww5rQS z#ylpzjg-8BGg#8!qx}YVPT;L6GF7^iwP%@``JoL81GF8!Mn05Z;m&s#`zcBgOjNFz z=}*Abto_0={0xBm;r>7@k0yGs^wJGQGTHw!94CsLn$Vofu+Y4ffifQkx?^qs+@(i7 zHJ$`vr<5iR)>vW~X3qsl;^rU_-(qoHgBm|`>8KLaydw5S0RVG3kLNAPwMQRhL|B5S zCE-cV`Ob_+v;4zlk<)@+VjvGSvF1SRJmW1U)R?!%ynk@}eIYo*pi{{LyE$1DI!oe; zz_NT=Wv9vFkiu1$Av!cCkx6CL3g&Zj@=od){QAX$1_RB43_`2XHq|{w^MSf>wdAQk zfgRD45mX!!&vf44#MPWS&Dx_1fgiu2*+e>-+^?5LX*RZ+ZO}qLBfDy@@v1iKmy13- z(3h|u()mPz@h#f@NU}IQ$?yGDT5Nn-e=7iv1Dwh#H^~0CI7(=O;(Aft#Nwum zU-K}b{T1@)AZ4Y>@y8g*5~a@ZrXhr0xo3FOODn2d9=d3o9N)%ThDz#7opga> zZc0-An@=Y`Ak%i^2hnq)wm6&C!Q=_0y*3l!34q{&XHfKxNn_zaBSz&p=i>OdpPS=O z)OhS;IOl0Q8XT>*DMdHV$$fddMMwzfE~g6l{em` zrNSRa;HqaKN0q=y9-R>Q1iny!klKusayj?Uuz8--*@I zLRLQLkJN8xR#X;~95^8uD*46efs3pb4Lnh1F(F9i)0q@1v%DKQ%g2kszS-dD`U9XZ zEiy&XP0G=gzkG?j{WUP+fPfi*Mj!QUMW%@V_|O5x)ihlTAO4Q3wRBr}te2DNB3l7? ziq$Xm+*-#-CjqeK1AfhRbn&8&97C!i#lseM1`{O??x`fBxi!USJYbarHcV0gk>LE04os{HDzc}k1%7ZG`M|k4t6qXZX@$C&&?s?DC%bvx zPw(hJb;6{MRc;3te$eUHdBIczOc8AK^#;l<_2DfpEUVbPCm%1eOudn)wL&Ul1{9)n z^{J68#2KQjY?Cx5xY>?eb^W%K)EhVb?&(>Rq9s!P`l+BEsdSf`h&T=c0-I{-LpWml zD8fmpu4e9OB#s@PcbqQFcZ)G;8`K=vh|u=fzB&A~eMSN!+W*3``KE|xSg6?)YR!qV z$}GyKBeV>YWf6R@hR0+plbgtoqV{%50+Y!`u+OC}q9Ihqz2%2oA2HtFr^Id`QL>Y) z`zm;%bXX>-JpC*n0g<^XgORJElLYzYTc zZTZTDT}pfEUVzIqdBU$T1Dg zEBcVoDg4mWzzD8Ls!Hf}El2>3c)HG7t}c3bYnCO@D)9)$6X!>skgYz~Awc%ETxbEy zop3bU9~6$d*nh7}Vnm$uEV&}3MkLS!e92x@!|5)@uYROwcL`bc=E|v&udc?oqb|4n z>6TiexoZcy>0|W^-KS6~n#K5UmX09~i3CQM5T=_tn0 zAc>fhu-08Il?=pGN811t@$zbGX@=tYNVMP7o6`pmx_*K-SqN9J zjXEZ@Halx&37eRbwR>SWD5Qp8Bn~*b-3?bR)y6gl1Py@}UN_PVr`utOQ~p5H)ixiR z;*Q-><-&`gm%b^ofRIqi0>l*WU;Se@sA_Z22r9Ud62Y=OY0>klrcGp1TP%#Bp1VMM zMgxw~da=o7KOt_lQ>Vbu3=<=?$$Xe|Pm*K<+K%-RrN)8de22rbbl3(a) zu16_jSMXPR;$XW(NOB*9!&^M}FL4Xrh|pv$8tG$A-aZ~(>w6hkw}P|>|9)C3p!vJw z4gaq;rFm_gNuh2@7D}S!oegF%JltO71P3^RO+$R-2lm-os?KWX^ybOrUQ$7gzCklT z=UhSW9i}89995hWYrQTQwI(|9Ydu#jkS>rOD)h^P2t5A2u`cgo#^L;A6w?b-q zXUL6GeT7lJ45hbb#re^?DpBW7HnHs0Gd1?PDKc^lyiSp1uSQYPL}-OcC}hfc+QVy# zB6q0}>i7=d;ETrOhDBj1{e!k!-m;6+@yq8Xj37SF)#j^K|MwT3rK=Z7P{B!>_n zN&dBqEpE)M267_A)UAv?Ggjq?3_7tp#@LxoFUxTY+eeg-RBSWG_yjMS>pz9zt8=4L^CV}DB~`= zo}3j!H{Vhc$g^k$g5aN&V>an|mQ>*QsbXnm?+~FCj_Z*yxRYACCc7n4p!Zyx`Dx?z zK|(TzfnT&&?@X#Xr`Fp{Wb`7E6V_I&-3IBbQBT(ytpSjKNN@OOkfzq&sZp(rSmSBV z5|A9uN<|;q8G%rAX|x=VPGcA72gfT>^m|tpn;GD})HKSeRiXP%)sDG>kk*D4K5XcB zXgk_&n4i*MY~K$gY|)ccncE*WnKhZM3^u%-n;;r?vXURf;kA6%VtXPL=uco46{uiK zOkHrGglvV!Vf!V`a;`6{BpA)iC7}))`HBu@sQlyd90A6NMopIeeTP|1s~TII9WkAH ztyfWqw@Zfrd=W7~&R@U&@&gQ=s4j|TXhFi{MW@?R+yTF-&uD?ks~U)tym9S@f#w)Z zSN>seelVD%&TfrO3#qDVNdKLw0ckzx=H_PPdemm5I|ia+?Yuc~xA4*Yy?bDGUp!T> z6_=pJq*Z1Om{s0(Uc-5Tcv3Ux9_I;HYK#60#b}@P(SQ=cwBdjD$V9_gHHuG z4$sl;PCqJGhpuf|8!x(7{AeD=H|X#aa3)>;8phaIgK+WRQsV%k;R0GJj_KhXC?nGc zk;w0**r`sRH*=mNyv1`)*raDjRKlbt(rNrELU4+F__GM{`X%Funv=+jTX#i;ryNL% ztn~50EJ!JtTqX+=ugCMwc9ilNj*^Gqg;;-oEsF7AQ`HcRki#ovg#2;yWnxiwdhvRjy}HN+>VW`k zD?gw1sk2c(GFZIaan&?3Sdy_GVt0lRDm-a3FGk?S29&~F`p7oTnGiJ0@f%7ww^PrP zv7hFO+FN#;`!8>o+?7l(mXaNn8)z}CA}%wX=Mrr+m?`i}7m57C0;7 z#vn`qHHoTMi4z!DP?oY73hBX^HNp~*#2R9J9^Jii0t&m2+bpfMv&LkQPjfZvCTUfX=BtcZYTjny*@R?6Dmiwr+q|%S- zMwa9?ios>@C|(+dILYRIPjf&=F#PTqa10N~CaMRi6sxC+J|AK+{AfceGuKET$!2*w zHM+Y0&0^_WE($J zxAoLl-YR(O@KdvYZ~R_Q7U(hhKT~;8j~Kiy-hs?S<`h}ptwD6yl6{risaPDL zuIIOZaTgH&6jk?us!fR9K_ME%`|C)vvS6vm{j;;v`dSRQ#4PbJb*dghLb5e4d(hmi ziaWTMGT~<4FHhyY592#8_6&pHUJIPv{IA1+epocB?Ho;yM-QRVKrv)|I-%DwUxil9 zCv9_+YY9f;q%HegD|)4Z5^7OBPwIQGJYu@=s|K6cjZZ@ad2I~ zj`$|6#(C8+RT{-eZvzJpZA?~LN3+SN{YBxNQWlSTDqL#wcG`QHO-GNYe~}8DQ$Hlp z1!${UGp@UOKm7=QUrH>e{-e1!KiaV zPKZ`IInE`0eY0(`JN$~MHP^@(7=qkacnn?T2imj-JPfj&tTath^h^kO2Q`1tK1(HrSO6S84S)=`In|?UAi?xzj4V~TYxyDA4(AY$& zh6;JL`s<_9jsB6|7?TMRmy%ud9TcsRnf_8*MN~K5D>TnOPa5LD*7xKE9Pm&GFJBeF zxrm=yb#C?2@yUOdPt7eSkjb5`E*d}3s;$=z{JBD#)S=}oNd<}B+=!v4Z+^75NjmMM z(Y)`*!Jq343x3S$tEz?o$CNC#0`j-FbD;D79}o#>b#&_3PyL^f&5QdPRf^*CRB3UF z+B=)0f+$4LF4p8jC#rW?^!hvXKZphD+ApNA_^B4px31Q$kyyd4T6_=kdyPpcB41@) zSV$ri@|@l|&jyk%Ki-nmeo)YAFq4dMT&)W25{_SxKoxK~Yqf`CxN!7Sy~-}F%nSg0 zD`CjoUbuDXIDRMBiG=9;t=52t*0@)zAAP+@gF6)TS~4)5Z#Wr|5&ZU z?+i7XAKp1dUxmzkWg>l~Uc{YxkfG`NqXl4Kwni2cis|hgQzyE+vt?3vKp>qRw=G=L zaoQbbBh%er?|gG+qWvIS-S+hN!mm&Cky&lw`;N$JsjeoArMnhPb#tmoHczESCk=4r zrJmc-ePYR@!%NRlEy0PQ)`FU;-}6BcxUPWBfRUf?TZbSJJI5{;tH~rwol_La-P0Fj zjDO8LzdX%~YS;ZnK38o>BrbV+c9Pr0Gs>^OEe%(JTFdO|?nFG?lqQ8ntPdO_7Jk@@ z02ftVwk)y@OUz?5UChE47O!>tnXzhVaJU}l+m|uyUtq?kZEo@`3-*zQl8kq$7{Y7*U*_?@d|3cZX2V3!oDhX;ZO=~a#n z#HhV*ad>jRK#hqf&-D4)=n|OEb{M{GOLT-UaR4tlvmk1%DcT2{n9o#B%EXN4)pUVl zNw-$AOOhjtVwijDP<&(5kD~at0slwItk0YHdhAL~=L;i5^o5V;pIUKh-Z`v_FoZ@u zY^ScASPQLIl&inlo{B3WKzqNlwE>vm9`eG}rxz9Q#eHd@aZjOP3Y|mRBLX~`LV>nz zMhEWPG^-ev);Ts)ep$&n-0GJd+!VltL&3W8nPq!`cieV6fbeL(l(Jdl+LTCDj7iXT z;Bql9+GT%8uE&UvMNyvh4JuTj8lk4YP|o79Yb5b>N*ksvuRGmmmLF+-O?T z=LuwPy`JxPNK;{j2K+ASY`=uwoK%$F&D`S9-@QKNz+u~dN!1yzZJ5$iE&N4{SQ%u| z1CL?dV6Ivu;_2p0#?MCvz)e0y6=;mVn(*t?*+I6{@hiUb+?z}V-j{(6bfl=p_h@xj zCjpnj)_9px-Fh*Lr2+qNk|d2wz@yFLeac47% zifY}u4{e_+;Qy|C*NveLtxr&$XQJ8xGRn@WWp;Xc2hT>v6w_wW&3+Go6OcYi@cQoP zT}P9CK zb)R9xqN=vy%d73EK+RhF+-=j!=Wo47*YlvLdvqu0aO@Z`B+Oy6jPhulJYCbP zxftC37dLq9bL*5IsEB^k2ufUjHAl3C3w-Wo{PU)t>3tsqJ$+tj|B;WD%i);kBi@Hr zdWi7a3YYvkU@on+*aTui3;p^tfxdW9cq9sSxBpryuCJos|4U5gr30WD++3=4lvD+ytarKs@=4M=U%~yi= zo*tn(D!r^El#65Z$jDK<+IDdSB8gb*9t~abeD|Rka`qmJYZf^ZS|Tvm$oo2ej*w8F z&V>cTmC?~#5mC|c>OFP}0xh53gpTIXQ3rO`D|~k=$12`vI@S#UV+`mSvGY|*B{!>o z-)Z0Sdu)!1c2{x6CYWbaaO+)%^me1-dd4-F9LA{wB#3|DRR4V5kyt*ByOWy#Freo> z75V);&_;>nSf31s;nA>9e+7w8kY!J9@m_Wbx<@&A4tYoN7&0vNJer}11soh6?jmTA zT7RSFRDQK#q~Y-zvxxq1^~#n@F7(e|PZSi;{Xy;$txVR*#8v!g!c42x65-~&ClEfZ z7z~|Ab8oTns#>?Qu*pUqt3y%3+|L7f$bU{c35No%|EsH8{?5J{eg_$^EMNzAMr>BEL;dSWegZg=9= zCkq|2{+J|&TBnqV7z+(XXY?2hBHA-4D^HQwwI1~-d#1Hn%Q8=UKF(PJVyH@T=*k3d z^VI_#KgIDQVl=yR&7JM~t2M?|$)zwL*gAF-vk!p*yqYgyGNpLaYhIzyEG73(abkqy|UcLEBeFf28z*@J?xkFrH5g0nCmIIhT*w zqx5VHiU_H#IoZxWjnnU9r@wZ5(};=wG%4~vU@Ldj;4uH-SC4b*BU9v0r}`GUP_011U=b7!Fla?g zh66vix?;VIrItnSiwBaDD#DwFcSKgO%4NDy9-c8Z6;W)tqywEx5C)Y}?f4SRoFQJh zA1TQHfJ|YFKaUltLy@8*jM6y2PC1e<&lsT#8JETaKC9pLf@&I$ZgLynn~_IRvdw9yFtd^Jgv? zzJ#)uk9Lol&tBK{|9koYbcc8R7{dJbSeekMWA?~Cu5L+KK`+COyn{Z_>9WKPlLd@b zTaY13;IqLJ{|L^=P;cJ@fXCF29og8FRqCwTUIRpLS+=$$W*etut~2-WYgwpKivGKz{hy`r zp-N58A#09S9KQvy8 zqa`;C2LV&pP^jc%+r8~hG;*uLtG`B5paL!k{_Eb9&52>UNr1+qt%;h+3@6a5Pof)U zyS7csZ&+Em3(49#=WgXSnNS%Dx z%}Y&viMk8@EAH$_E>9F-Me~ab8Fswja_B0ridav;F#FL?FjDVOj$q`J9+S$urmh*t zBtrbJ{_fxKd|w)IfK{MTDqUgYf4C4zs7GL9-@mEHYO;hcF!(o0`5h_Y71b{kJ^Y`$ z`u~7DRWSden>ZX9Wj2HcUv6(x^d)z5!~cA(@`q4!2mKd@y*}`N0owmJ^d9I30;~b& zgL)d(Bfy0RaB3RRuNHFpeR)E=zxfn@mc@Vn_CI(6hH3!7-f$|dU;iQ^B!WmD@rp*3 zS{ddykqCvH@QUvL`i8LBVvcqgdDqcze_3Y0Q z1!hwsKoIDsJfrr-`RDKDz(42N%Y|1p{0CJD_+jARE8tDPK~w+pVt-Aro;Of{;G=RK z!xw_YHhiPs&7@zkNen5{gcr2=M=K@~8fcrDF0~Oh*q$ghupaML?-hmt-o@XvuzRq6 zzm`8!>8~~X_azA!aqkHz(qI{eIWb_KdIw`^k|MMo-rJzp3 z$cNp2qo{X;0bU%8KW2`-;HwEVRk;?zMGF6GwiB|26p0f*&6?4*+jVuHEr$ zRI8bqmSyCyqyeMIqeC^M}DdduUz`AL66`m20n(dJop14*rMa+4Bg!o+y`g5Q6EVTKaJApm%A5t@+o2Nt7n!;rUG5 zo^3iQAG+NWr6e_t{NdY|!imnveio-&FyMbe)R(|cfKI(B|EsC>iwz;GRfCJ3;P?yW zMlPSWvb6fBT(19Yp$>4ef4IjzSR^pbfp5kYbFp4vtGVb)<<2ZbU}UV}U!=hOAVsdy zZ~r{Em%OZ2Go5vdUGwaU>~&~R25@*0Q=3PAu;B8QFY!Jc#a|sb8Cn}gw*VS9+yx&q zKbs$EV3f){u8KFxR;OgmV|Mx5lU_(Y!72fPH0G@oPusD;P%1puw#IHhs{G7O#| z0YBZt&O>GDeAjaGlbP5f(-VTCot1{_Se0-*z!}mLka@@efW(CtXP82!n^$K$vvGUo z+^kQ{BYE(lav>w9=z9e=Wpc_8UD`MuWn~rsk|_efw^=^zYpt)Wl&*RkBKKGC)o$O0 zCo2chf=Ms{z`i(Y7vg(@bPtjr;gsVh9{zsxE6b+Nt&BX zr>L0N*fqE%qqTL-aeZ5|P_e#AL52oiJ)mhekV=Kdq=S?laZRB3PZS|T_UQ!6K!Npt z?-+kUiDjVAr_w88gMDkPw09CCM#v33{J$|c-A1~F!pGiTSWpWF1#y#HZw_Y11BlmUF#Tb{MKo#1D9l=b zLUMO<(ZTukaNJ7*;#;K5P5?13hD?8Xkfr^Tg!DG@#VpUHOfa9}!q1;^N$dsWMUJ-v zqSdiafWxQYs*o5Uo;=jv?w58jt*W}yb24sT@Kp4Hs8#5k=%ja0c*~#k_XcN(@w`c4 znC6t?pS<)|!VCyhT<>!@{hMrUP7}>rH5NQx;KZ*PrBejAxBM^h>))#}(jT_2A^0VG zR-Z~rfdP#3(GR-&_4*<0>nQ)i(86ECBM?FOVPtrWsjFZazEJ0GCabG)jZCvjLAwUP zyPQO$aIjda2R>?7rhWb_H_~W8Uam#wU8s?bQf++#3#x34Ie%@hXWIQE+6X}90Wdl$ zJF(o?Mg>l1;s6ZvSzrk5FT72|YG+8{0pP?~-PA9HVYfuw#s?GgOpJ8(={4H%c#(Nx zG!G3^>))9x5RagM*uwOjD@2CxFQ1pUY-g(994SvPF3OVy9Ub4urf~W7#Zt*NyYX1C z>$n{Tt-}leX0br-hZ{$y@|kBwJQL(f$gE~8DDRjk`iV87_3?%8`Q9o&Aj@c#|H-ex ztZT#Z7(3~<(*@%u-0SWwm1fm<(Ugm>o$)r7r&@@*=%Nof39jamTkDo>di4zk) z7mut`mRcOZSjj9t4K6ofJRKaF6i7a;jpUI4YE$-#esH_(g%X$3CVDC-syt%5u7b>Z zqf05rdsu;IP)I({pb3xJUaN<=_<>Sn@@24szLc$R} zJUo6L$4)EQhETB3DS z0OZYLrcB^Vpy7&VYYLl_=MxQ;l6S8raNqdMTU$p@!56A5U*W}|y&FENmEi9e2@G3P zqR-`#ZNTPIvD5zBzecg^HQFTwMNvHEn(P91t+X9GGc$9cPG#Xe;5jiy_at29l+6E7 z`BU%hJp(Ii=CKL#=@MCeiz0qN09B<~Qp0+`Fi#VFf>!O(*!|0*?alBxN78F9baZTi z#hhbXMxKaVMXne`Q@ zgQqguczT?KyH~GS_3k4(>9=m;@+Q5;VKqCc_n6VEb!Hv4R(Xx+EtdQ^BlqZznD0xl z>J--?ALvX=$#_`FEI?6LS-x&>Vz+{?pFFX-LH|Y(m>K&g@0}nX#OUNE zP@I$j;FSaIYw*R7bUw8`5>|uGHJ@Mt5F~I+Pz_X>wrIM558eD@3ZyI z4u90mdzSqZTo^CgY=03X8CzS}g9Yzc^Pl-S_};^d!c6Uwm10gEBW%48VF_8>ci&IX zXB-MYl#)C?$^Yzn15Nnrfoq;Y@iS??>t>c(Zf$$dw4EL4E_iG5z+^9pl?Qg=L$eJl zE^!Q$SHP*RsK`PO-J%M$VcC#*2NVJTy*ZgF`57Yjj~OCY&o5Zy9~#53`E_1-dd%g) zJn)=rIgO9Bc~UNK+c{|@QW8VK7(Z~y(RFoqj|28K1$XQ(%oacAPMj6VttOnVD-BW} zd`zl`N?9RoJ}etJ;EVu@CV}Mx)Ddrm)_&M)*Spg9ZokI+*Q#i}Bg%MaOuoFov%BPw z{`IvPVIPQhql+2&V>k$1_QA^cjb%^5aNFj+?Jy&J z?qO%quUomZmIb{h9h??K1d!UEfsc>#R>9mlJF^$bOcH)`R9X$L_Odw5B?gW1rUS8@ zRmKmhT%L!-APoELMq*rcMCx{b9Yw0Hnv-=ijt4gPV9TM|St-LSx+KN%X@tKYum=fP zV}`KQIG#$rl;c~yWUnrc3Q*76s2>Wi$;{INC#Rd70vlR#To`Z7rk^<_nsE4JFJ+`} zG)U|33()y_VM!t&B?I< zvKI;2kyG86XvHV8?S)(6-Ps!x!h*wu^GHJNK&6>l%f(2sutX7;!v##_@iw#9_Z;3t zL1aLVEe?k1BSagDRa8-bi$5Z5C@_i_6XlJK$G^iq3*q;n3tl_R>{FJ*K_;+RcCtM*dN=ySnoXw)WCGX0~{R2Kh`zQufCO3K&maS zM<;^$0YgXptlThqVQ+l$#~14XZW(N}$qE1scETuarooDR@Im+tp3mfNd92vUgmZ&{ z)j~Pf_rWc{pQqBQ+xqrlcw;pwHS68vWQTCwJQ^@F zd9iwjxv?BO{?XM93sXOa&CFP!cOUrzriLtZU zE!9c9k?ARrN-s1#xwh9mbgo3FGU;CKdu@M-4*D6e7PJU69#(y}knc}0v{@XH|Bn98 ze6OSg?yjn@c024Ue&qud5(4*m=!c_SI&1tbzu9_V0nxoutVrdxfI#&=nif}l4 z!V|_|e>h*EWK`5BK(RwU;p{6itcldDE>tX%-|aVgJUIzu-7e@kw4+$&SZHtxEOyXP zYJXKN%Fy#-rY5+D;YX*)y*zUq>P>rFo806AZH47K1P-DpC(}K-rOTUW44PZ%SCJHg zB=8k7pF&gYiMZD@b-S6fnHqKOc zA?`T4wf5=;b3LTDmPQ7W<)h{6>&Lzo+~596cb@smU4LpJ;wLErzyiZx zcZtzcNjK1ZUoa|^SApi_+=#{|cQE0l;qB4Q*6QZbpJO1l<16qb@DNw0k~K`xxzMHR z?03MgZpz}06+$NaeP9uMOvkg#vXrgzhP9*bSZ&69iaY!7s$#FN)zENpsa?X|&904{ z?GF|qPmwq*dxXqU6Gd8nQW$4h+*br6HL32~*@zaq&|dTP?%-XodCj*7W(!%tt|u*t zL&2>v4qNnNN0sD7fwvAP<|hPjPl{_`{v7!v4-&6L2%+bUYx}Oha?!VR%>)xVy%wQ6 zgjXZWnc1dyrOm`4bK(f$6@blq)D6jpxA(4(_b-21OsKxHv%QQdP30bOh&r{YeG=4c z+V>8I;tv3)7N^Y_AR`Z5i3bif??gU*6PXgu6W*z6O*FiIY;fOdc^94cS-xI`cB|)8 z$)cW=D%OVAT3-Zlh;E}y3X>Les{tEjqq!{f2e@fMp;j{G#l^F})R z23%Mmdq-2DdrF~tXJuvWoWwk>kI-#yHk5rH7#Qdv;A2Db*1m{(r`tI&z%ojpPkG?L z``F})oVX4t0tIEN7|Y<5?q|v2dMZmpCr%JQ3X5=S4MHI9b&X@&D=!S)Wv^u+S#rq^y97CxDIVQdX` zlMLB<-c}g+p9_4~jiNoO;g{Dv=kKLw~_E&wS`zK5CwEPF9j?yN5f(%lG1OD&{ zL6u$A76m&c&)qUzT9X7$;|Y->tW+^vXs8Ssa2U%GLA1%fA7b) zJ6S3g>ir~Zyt85e#^(?JHhk-6Ja7Zt&$M(w1m#~@*q;RR?-J?PhPEjA9N(D)GKsVs zAQxS!ur;`5XsVRuWOLfB+y{9uWA8g?mQ0?tB4r;Iq@&~{spYqkE_my_j`;fTzeh;@ z66rcB?v|rQ(D(b7XRt$j89JJtvno0?n$t04Vc6g3OeSPfXT454PZ0I<5eG*o(NSM| z@xa2z1-hV_t%lLIO$h}JZy8ypBqjP>;Y}Y|a8|t)7XVs??46tD%0(o5TasAGCxxa7 ze&n$-a49(Q+`RZOiN$l@MQgMGPt>TkwWgGx*Hpr|x^3#7C7HpL${tD`(@@ieuH3^~FD|>TZ?4IMp+uP-UYyOBN)(3T~ zv&bH*AGvc0ct0}`bq^n$Ot66}hOKWza?a)UjBIzdpgH9f{F=|m&^g-2z;~A`O{00! zrv5&v4*Z!;Po-9+G#~Ex)@4{1+AE*xt1qzW{O6tiFCSe2gOuwNI_*xE{dey=UA?pq zO^t$EQ8QE~9`zGRea0+gMq1R9<%s zBIbx=B>Ew_2+7Iu#a2PeS%0NtyA?Z%r7ruM5HdpF!08omrv2T}!W%^M@~V-X4^`!3 zZ#?y57?hFoc-C8Es$!)D6E>`kq~au;ulCrjdk;E8Tz4lN9LBZh95DSRE)qnHG3h{O zTt$1X-|A`;-nogp=T;EFSYT6O`mtDH&jvXC9qj+JrT=?$U2%b_f5N6)?b0+Xr`h(5 zM;Xqfn1PWo6R7zVOii&saRq4wP@a$-sf|9gJU}rSho{tCaF7A->Zk?3uj8X=N!+{h z)~y(2%KaTVZnhE`^U8+BBGXTt!!u}?Pj~DG(>wCiPqPBawFj~E9eFb8)PBOTG&qcm zj1?_<8zC%4`m2nXplqgMc*KISi#^|t8Ohs=eW^GRAE}iehiJHW(dH8tbaRrsft4}B zipdmQnTQ40M+|x4WDG>TVoMcoM@OS7y3dSdvY2w4-(I$SAd(G0^}`)E%Z>Irj&p6P zJ9fDIw$T|%G4sVgSF}P_H1sYsG_l_LwFthva-;1CCZ_O=`~b~BRT0XR3Y03soz2+H zH=3Ny7NyHJw10)fe~sUa#xq}nnVa3FJkPa+;dftK+jaJ5oApdwgU*Q+bR8jK?uq&_ zJR-rD45YW*u{0~BY9%w*h_YBu?=>{cD=4<7qz5a)VI4mN0ALq>2M1=rwM+O0bV3|$ zJgg;S;C&N^L8n`QkiNQE+WYPJy4Rc1IA*%Rs`kyTDIHT<*!7LmWt68r#n)IV`;!-P z5eTQGd?t2)BM|XWVRW2-rIsxmB3Rcs zNcfLo*XmL$32P-u?2(~U!n%-mwYB}SKpoVJc?K|eD1QE=)N+VNr+Zh`+DYeP&8gyL zIbWfaD0@~V_`qeqRPRkAJs~F1GbpmhWG|YUq+)dTyc=r#fI)63iO5nejmzNs;Ob*-cm0Av$VeJJp(vq5Gtc0=aXY_#v;56e`%9GqWx>)r_cH(n-{5le+-j*>O-I#u zcXG@STC>izxZJJYR0&r?f~DQfOKkaX8r-l@@viHY~`95h~;t`HA?pwzA%-?EC^wbv{V z;^a&vfszKc=3JD{W{SLUbf?EvrBt^rx-cxZAgG8}V|9Ap#&^cI^2MtA&VlyGELDK^ zN*IO~dnrRr0n1OV27B7U#M&S(XH#L(P`9zQ?+lvNn>I75+2GyXDo&?Wi9%W#ezWn! zThH-usRTO7;j~9_aZ#Vwh^UnZN9aHe`)Vd;e9SWGvOuL>{*c`gm@J@O z8UWAOo9T(eUQ9og&gYOO2W<|;K?ki9wHJYpsC+~Z7%|-D>sr<8-T>z0q=_Zp`qh>c z&*X7FJl@l-t8@qtHf4Ewdr#!xc|Mv^Gj>Ga>7e=|?};o6Ki-4P4x|3IncCbNC(Z&M2nBwroNn@X_n;|E-(gaGf>T-^t+ zjKEBA&*qu{O0bg@xH9fk=2vKhx0)i0Ma2+62Uf{XmD| zcQf)=qx)Yw*zbCo!WfAz&BKoef;z);j(l&fYq%t|i+RK3E{Z9fG?CcXxLU9^BnMgy6y532wpNCAfQV z-MG8+7TveMbKbk%H|Ku;?H}y5R_$6~6&NFOC?HTTaU#DIehQ$F-8lP|w=GQ3miX zQK=a*Cvg}D%3UnCJ$`E=SQ0nYnP(z1KZ&TFwq29o0NoLAE%KGAOTWQ8l>(YbYO?rp zUEwptnS^Aa&^0=Ma?yQ9^Pg9rNE5GoDP8Ult`c=%w+cFh7e%;9a@!r?8=fZk;nhk~ zQ&I-qpIl-!%a4;nvH7-($sEmwZ}p5B-kmHo3Tr8W^IsqGijeflP5>%X!VN^sHi9b% zeh;SuiAs9C$&I`c)#mFT{E86#ZteWrvfqoQc7{B;Pp3I`$)v#&y{0RCJdGJAobKZ$ zE}tqYw}3r|ix6^v^X_pCPq{SJjyr{XX=?C;wT+F9U2uv>I%CC`AY34FejAR(9DqTm zLG@6m^6)q{J6M`Vr^OO>4}Yu3M~^$)6)Ave9Mo~dUA=`ZG@Ls$(M+*Y0FC1#R@(2t{?@pB4WZoJhEB_M@{LiZZz|K*@ z9~b1^QGdw9qbeuFvs*}oLJfU=$CKdKh?THbRn`5#1^SL1C-3&tG1LoL_D(0Q8eLrG za0u>5{^PG$IBwIrR`&x0Aovjs;9VZ*?Ijl;1IgkL3u34$6L(6p9CD~>Zwur!GF8vu|ELW4P z>2*aW<1Oc?g8IL0s8xL4ZH{MbZe@0MD@;tSw+SN(&xj7|-;Ji%%Wz^Jr7rz=){5$| zNe-Ni+;5r6UH_xA?6i8MyMrTSt>lsTl%%Ljty8_Hl{kr20yHXoqNiFVnLWB{#=BkW6~@m^e+TtxWs`4Fpm=B zZm}UKmz~N0VGu%k9U!m|#bIZ3U;48<8_R+!(yel_cE~i4O+OvX53l)g@8`hWyab$z zqI&J^mf<;_nu-p9QLm7LJ?A0$5e(>6-kkAc#9(%+{rS%Q4$1#(kX5NP_Yi+Zg{LlW zTO!-5&pCno!06De*+%se0|vH~Xwq^{40ZD$JQYqPozGi-hJJ;j2WA!7 z5@>ZH{RrN@`7coE&pZBiE7LFX3epm@AwYq_IfJ%V$8|3Lx{ z;J~VogI@e!T*8I{42`XgWB2`^i}QDrAR*Vm(-{8ijo%?55+0uH4!w5Xcwc0H?_kQi zEm!g}p<^QhgY2o+@e1+F_A9M{0xLjiP}DNY_7qSuC$u|%AyHLVLwaHmaK&CS#K>6b zL`3)98k@GhcV!$KSrp@_JD)rYdm!CQ%*)DI zJ1#_}rqXC6Z7NRtb%OuBv15^at{)L&{H~<-_He=bGn@6i?AcklQ`4zm`Umeb4OnW; z+^8nL(@Ur(w=?SHMo)m=gK6>#@51NJA8Z5lYtu9s-08h2>~=FWo}B7?_YX`6T8n#b zyRBhs-p!db7K>dCPS+Vj9F1(XpD_GbZD3Y>0l1M|dOmB(o)(^&djR^$ZS{nh1~_P- z1W@q=KQsD=HQmunQqb>a{J+*EDGfmG8o5_6(m=7Q!-(bBpUcu!_)xNC1 za|O!p^UQy|fKL*CVDrNjebPyQ)y5Rgvn$$DpZKK5@zG#9@`;j(DfIA&KgwCR%}a73 zn(>l$BLLT-XR14#P8Tj=@g17o_oYV1n8xH}InOqOU-4^eNDHoOF!6Njc{ZL``{;Nu z<8T)n%5}N8bYjbtCS|%)ka5$+HVwYy47%A&IZiVmB{-*8w zTnXNPt6z$?Z(Q6llbX_jsIHz!8VR`L51^3)+Aln`C^PmI^*OrB{L0nNUKgs+jcFu7 z;d0C8#D(3{(_iUyn#7Qi$7ZZ|=%I#W2z7M>DAh-Q`r)YCQUYgZALsBJ+9sx+H*b-_ zBIm|vuM@_8Hl#3)&ko>?DG9ay?Xvx^FW~uYtx6oI^ngP$i4*eiWn>d)Zz)iv%?mdJ zBEe|DV9e@@UhUs27HyPQJMUV_=y!H?rFqdL03C=Z)e4jy~A06I{3S`{SB1UYB5gf zmw2RpZ7^kJ7}B z)`14-vcG4@yaCs~8}^A)3kJ=>6*ZZGZt_F)+nnIpQwsi<)9{7%JKwVGYEyqoy%nDQ zrkY_ifDS(29uh`8=Fde!{)pnR8DW)g0#wHt7#LIxbOkK!Z56?W+!yHF0j(v`@R0>G z@S&W+x!i7(EaGVfiyN)1`&&POrLSQevHgwA=AS+q@Nq2v@NuOmluG@w`1hGdo_jMi zvL=W}Z9jn>gITT}Tf&37Z(%EcAVDibOp?EJX4+ag*{eG!F zbvUK-Xr;|Nkz<#nUPta69h=<;>o#5vDd=r-f48NNg@;mS0ZTl+`j-qIA7X#5FJ*!K z@cH>ETB|>|oR7HPcDE*JX(=Sf^svAOXOa`6!eIU=c1ULa@;F}JywIM?ZZj26L*$@! zWf8wyaN>DzDPKcaZ%&|G)`9ppO{2hjs9V{Z^KGqUYFVlwDZ5~kVK`zS+lnx-c;25W z85%@5IZ(msmqFu!#}p0~X$#JOq#Kjx#FFld1eOI3a;LUV$Z}pztKIEclU< z*Rz;z6#kFI#=IHj7wn(@XiW#b|J$kt^Cs5G{#B>@UsP5`5r0W+(rF5Ag?*x_3D;ve zH?k%Za4Ih zqey=~@_&k`tKvV$jF4zKzf1o;$oQx2l6VDasc^o(`FnBYYarjB0Bq3yghZ(HFMrqpPeentQ41@Ld~OK1$(-dwA%@Bezu zzxm!@CY4a@HCEHo#c+D}_a^<-q~+2Ap=w;X0>Byn&v*axK}OQA8Q;**7rDQzn*T9L zBK*TI?v;qELbmwL_x_T2`B_l`L$h+%7^nK1+YWf4QB^=Xro<)$|G&88!|TvK(8F>8 z8SLL3p3-YNHYipI_V+KT%6rWeLB@gwf6tY?PPo9}t2>#8Vf|lRQilc%t*(W37wzxw z3JHx49uRdQm|rPsa74uSHK5v=lH;pYDeXYB8IgmlBboBlAwjj%*}1C6`R!ZP>nkGW z>YA&Yl_zKx%V(t~EU$D24Xuf9(@|>jZi}a1)x7AferilGKY5d2nFOqe&-zAia_vp-(1j;hAv0tLXAp;izF@S~+N~`&rKMd=& zgqCTyiP2!l$+IO97WuS$T!nfd;)_mfkTnP7K_!O(6@K#%X|Y_G!UvqYhD&jr)A^!? z`_td^zEdYxMWiQ|X-eDEZ&IB)i2_Zc-o0XPK@k~*^Rr;{t04-6Hq?9|GWS~{&j!pUX7j~lSmkoU$X4W$zY5kF9AIc3HP zoW~^ekAg)&FPrH|9$g*lzYxfT>TprlqDOrnmxnBTPhAq8VNw@6fj9IuH1OD-@6hH2 z0&rlmKC09vLiBbpzn$N2D8T9!8^~T}M_I${=@zrlHDb3>2pSAcTn1D&1BrW`b2bWN zVEhvcdDW|-!vy95YinQ#_iI*Ijy`y?l&M0Ri#6$)VegsXP?CIvdB;=LM zPVLvG8h7`EqUXO?^&aS2`cikYBt2)dl>428e4-rHM~T^irC2W(fKiIP=3^{O)qc#2 z?9gfFOQ8?NdUHYkEQB{Cmjn~O;ZV=uvt{)DWEs{GNeZd}`;D<}!2`Cv8+X~1=iCqk z7zK#aceV`5d!Z@B<5?DGIxk`wI`kXd*zWKhVf545NMQY1``0;w3?bg6S-81Pc@qRi z)H7nG>nxQ41FPA}IgX30ci*fPH z^7gDq{?febqO-nQ^Yd@~MxSOI%pJ|vheI4ve_*lgTwH&c_BfdwUc9C?{>fYB%j+IY zqO2V2^w2hGOEC0X?-2-CS^Zy7men8El*Wzc{Wp4eoISb(VK;s71v=+!qjFl)N9(+y zBgW}o{GIL}MbFMBWK1k>?}rc!8X~~CuIQkkrZ(aaepX=5Z80#_ZP2d(;-fG?R8-bh zu~g&C{z0oX188zAwCHj>#0Kl$FjciU;hV{*mQ3>2`l1M1z*A5}bV&)U zEH<;stou1j+OuYTazyYZzZa5N0BobO@#^_>nXw`AFMaBR)5x(VZ|Mx1GL>pEGR^C` z)l165x3XAQyz$h>d9xFl^RBy;M+@Ep8O*ku7suxsG}KyB?EPYkgw$N2gJ?JTh?WnE zM=7dTskuOQIg$T?dvCfC*2qR*(kI@lPzZbZIxd|KTCboz;OMM_`R5UEbjtV+o+cSi zlKsgu#QsY+7WMv~oS_eXE{mwv*JVJIS?HClblLqGfB!xYXg0HL{h~BxlR5BRr&l&Z zxIlCjV>Cs=5u;2Y$b7!~1__VXCv<23Z6Es~jq)mf&##+e%L*{uPG9)qj-J|% zWuPGO54h+h3{{qC*jOXuYfYWiukhH)$FhL*-UpJk@a zW+_3bF7%*HLfh4wWW{Iukt}7TdvK{$p2sRKv;VxS6Daha9Xx(hFu*H~dwSXqueEm? zjq0SD#(dLL?f`Xve!*sxN>i|qF)5a+79PIuUpe-xI(UcW;Rf`xd)H0@*O%SrV?p4p zoMrx-5_2t}$UzJ?u`Ie9hu7mgZo&Cj*gZXMw|=18STFp^M{xm{GdGim&(zy|t~jJ0 z+)(m>-8KioIz_R%_iFlLCs}v{Kbg;!apv?QZ$-~rke-+X)V+x_$g zO1GVetpS^cD2f#v-v$cg_ku=GK>&0vp9&Gew^7k+nWm-GrKK_6$#}XPy1YYkyKnJ& zYfG=|mBHy6;sJk~?Dh*QPa8>);PL#fv>!uB0TS(2jZ&PE-q4ub-`%$ikRIMv?=YPj zC<&$FdadcyL@nVvT(H4E+Lyv1RMb@x6EJ@3$q|nPvQTMbhR+KUUBkNr%*G0S%`hFo z;UVaq#05qKmj_fsv$JYUW<6b#LK%FC>)}Cn~BhN zw((mkMxz%7H|3N`@werybYp-=06QH$~)vnA}rrrH|z`1 z8>IoQdP9TmV^%~##p4u_AO~%)7MPEe0=RIwaT~e;MW3blj1}cD%RfqMx!x(ad5t>U z4T3B;GO*;o>qky|SiO8aSW!JL0?1VPnv>7^o4`Rt!jkbQJuFWub&i>b{BC%+X!!Vo zBos0Q4EFpDp*vq9TYWANQrgY(s>0IVKcwv*##MBTAtd$^%p@p35ny0!*L>;~NudcO z8$389W$z1qNQcPj$`p-q(4%>;4`#2taK zPj6`r3*On`U!eT*%?Fno==57FUq*UEE1P^5gd4cCo!+dXX|%gu<#^+BUwpz48WEwG z3St|D(qSw{?Bz3DvgI3n78dU(4?1)OebNgy(~o`7&Igben}bEt`^$WpUcLa{knkjb>JyWXI&TfCVV}*$O z>l4|tf$#D3#J^v1+{Y7b+*K|m+E_y5|9~h|B>wW9>5qJ)Gh%>X)p2u6%j}hjfAHd8 zvt$+S=^0YrYu4Y~@;D-pY9n{`7fl z?;xA;pax39aHZjfc#a}VQ z2^c@}vGZt4ubzl0DU7RX)|*4u+RPHpC%^$NL8elSO_F~~sapn)XJ%6S93*(>I7`1l=BmzV1=dcxMHoTH+nz&2;uB3j z7LRZD2|Tf!t5(&Y&n?-Xz^&gO@IoP0i1sZ~ZCHBEMhT-~j>@R0gZi<`&*)7qbuhs3 z6a(U1onVMRD?aeVfwvo}e&K7thxU-c;f_n7zg4B!^{;n6nwIm%wn|(~C{{fK&7hHj zLrp5lWpGmXGv5d6qr_o0rLnF94SE6Q#vSg5G#xUvM(PPv`+?x@R; z!D>0TOp6?Zw=F%+7rN(vXwa<1j7_U;4Bt z+754PP~{mYU#Nt;Ut9k59Q&2XhfYWCE`_}I1Vlr1lB&(VSmTCQqX6jLl54+KTc0uz zYR4T|4m)Q1;*L^x&IJZBOpJ{)dXExsd_p)Ht3)-uOl9y$uI{vB!+An99h8h@s*rcR zYFz)G({?bY!{#XlIh3mctSF#5EVdc`t}f_4?IQHKm22nwTuKl~*IO!*wE<;O^hnxoYxKr`fW zPY`nfFZwyQsAzjGay$HBYQfmiNFY`dnfbR-LlKJG(@ta20(?AC)EPMLv_f-!=Hn^s zh_JZa#H{a4e>>L6O}80qbKjk`y0NLru#S+ln$7qg)=L0qBG6H801l03tlRH!#Wjg8 ztOFK-ZLI?`?`P{YWH9B>-FeICW#<81@bDkngJ^SbJA{}hJ`KQmj;)2G&FFTDJMMu& zmGk1_Kar3oxDLWK)-Ki)?=p^UjDRYE>aZYBL2M)kDk5h8sxt^i(qI(A>{4bHRb1)B zX*{*B2Oc3-Z=@tgvt;Z?+S1H(G&Vh)IKR`c5RVJDqoG=#OZ(($!%P~*aX4*pc+Vbb z8P}#!&qW8dQ>r&LjItSj*Dl&Pe~iqShV-HI1DC~_$vJwPEWiOnu~ta_IZLf}&W}>V# zEFfWUx?U$rYQRDbHr~~E*W@L7%>T)31CFi5FGyg5uJ;mH8(91|X@XxfIq??;2j=~} z27%+53+#DMG0SwLynESu$`%=@!q(^))YM9t1U*sg+jZZy zLa%`(5xAS~p0WBgxW|v}=FKbs;1yaa+s1tL&08*pOCR+7@oa9D#pc$P>jwX%8%&&H z%sfz<3Y5-aDWi1MHsFCHEjyd)c|)Ld{FO+)p>^@?D*U~*qe~`G@(mQ=Ec+4$AyXfN z^7WPrjYqXn-R^n3r#4|X)+2xl)Yt8n1A!i7lX8qa zef*28!Vr|r=L{4;No`aZpRshmxjFJ}r!i<@1Yr0Z({>1eHY%6S?fNV>=eQf6V%*L! zpLo!qUdWn))J&oX8F2@mJQ1g#Vp8pClJrcVIegAbvABepe?rXdB(6XNh38@5c?9dz zf3u`%+$634(SXH9rkS=_T>G-ClFH~>sZFOfsb$()2^+eZ9dGH3d*{`3i7S)t2^S|z zX56vTPOm|75GRF2Cx$KN-wHr4B|42XEv~@_B|1x@Vwj^Xb}Xl)0Jbu*B4S7}l1~sQ zPr5?is3ZyLX16{JtzE2k9<7||kOy)aS=HLAY0^9IZuv-VMCX35H`pmq)hTnUpUhA* z=1Xix^g*<-jf$5-vi3>enx7MLX#PTw#dsS$eVA#&AStDjUq0@}Sdo6rDCoFCrvLMI zxYgL{vu_%D(^JA~i*=Tnn!GHBj7Qh}g>(mF!*_-Oqs8+Vi36Ywk4`zR_!tdJRVC)j zaUiZ8%7V?KUhPizK*(0Lbjnm-$xwRTwAL7_(L5|M4x_kfAg%AXpo#&i_LGc2e&wb( zvq9`HT|KVFF8L{!k3Li+ZDCf{)_eUSv0J{o!Cq}8>LsO9h|i-dRgUGjF4CqDzbA;l zVKA^aky8s1qCzdy+6cAFq>4LY(qe)ULKl9V5V~BQg5k2?PV;I!Bz^X1k?J|G5)XiN zSGedU2(iNF-YseCHWG_U<8%z|_yTcxIL}N?{7jtbFWC@By)OESIX(;bDv)JXIjKXk z9%9myhJScea{kq^!eX&u%i^>}?_h?ESpQh#`J(TEcKsE=yE4|hu`Unh13cj011G|k zW74b0N7r+-ZxV0*0bSkz)YjJ-xCPaq;opncA1+xk%#`laFhPBcIag{eV%P@I)mgTJ zVWtj0PWb(~Y^9!7Bc0Ll$#Qkm6h2R6Z+?Sp26}n5>=A#kK9I{1!O}63KDXE}ghzW? z)v`5VD4mOmN`Qu62v23&{mhI)s_yXXSN{PXu9r@m5uK`btou)(7~IsmQ7I+yO0pKT zt{dgHOE|j4;NDB1lAnv|tx|8IG{bD{eKp8eV|PZa+2jGS4Di7~`jHb;)L$B%vh#EN zEeHinIkp!eCj-JaVP4s7H;p*snAq&|?!L3^bpnrjy4f`16Wmc*S&2?-j1wNSJ6k*r zNLJ5lL5Tt)NOK}MnYzp9sMnfFf>yzc-Ob%&@=Vz@W+@gT?AE=VhUp6$y;_o7dZR%7 z$x5Oyc-~c``Fsr@9*@_z2wZ+iUg$@eHjuP(9M*m?L@LKPZKU$XdWh1#utf$VVm9Y9x??@d37mY4wz4$hmUJ%14z|EmP}f1sqOV9izn3$zhg38RX@ zS)Zt1DH~L1w^L~lzyyF8NYCu;x6~VnN7A{(OmDx~q%SI#n7|KAPm8>vE`9PW@j9EY znFw-dz{=@YkoMg!)i-awCd^vrE%(AV+ zL)swpyVd&-`_dzsK`lOe)+yr{?T^!RJ!i3yI4(J8dkB?N6}BQTi>Z4UxF}?3Z1LpSU}vrk*Wj3 z*94^=XO>*K<@=<+pDr@QN%mVioj{#TgWv#8C#F3xe^Ak1^jRJtlgD>&y$fK3_O=rk zi96#j2IFz)BlVdsD;uCxP%|=60QYGq&cqT543nQ{;yQ3o|899DTR%pbDXlxdif+kf zUrTxd3wocNvZqVasN5BqdrW1lXAOPy6nDon8gXf!x@|=F#he2xNH2jxJ?=V=YL4}q zQ?gj+xVy%_yU@#Awl00ml6~u9&&-xWZ+|R_L)*x$KkwLv_`X<=V^jF5IDU(_S=Pvd z!}~b9G@A#pICdps(4A6wWq6+I&D9=xwUpf_FlcX;qFs{F>Q=c_DdjRcx=ZK!h^n#2 zC7?FYlrouP!?UWgQmr`8!h$X^_dWkPck#vnNwwOkoe+or>b}S2J z-cMaJ!pJcBaq{n{RZV8h*M(AuTsVSZpney~5L$yjCMdZp7sPHa;A6qaw;8X~Dy##-gIaSQ*rf=WDL|c=aBH#eF3IBd?blEdb98yt3 zOC#?tDbQsr3)T|Z3$&GV+gvP}TE^7ezrh&`I#sSeUW#AEqq^$+W%@0saox01z$Z~< z8HYz^YABH&Rj2Rr)@S`Bx@^_#%Pq!8hEec00B5a!yle8Lk4qC#kQ2i&hAH~SBPLci zB8GkSa)CW>Tl3E@I{(NTPoH6odQVD|?Z4gZgs%TpAIc=O=KYu+KQ<3weISgJm^;Ma zczLl~lOVR9yR&iFS2c)`uYp_WcU=?~hJiNl$H~?1oU92vmTl3hA8x027x`rM{Wcw$ zrPMrDUsADF$Vj9dghk8<=vm+35*k{qe+GAD{3fb~*}PPzu^b@M$Bw2FRjBli<{y*T z1(c%~e#*>USbJKnL0quDP+;lB7=_$aV=z4afUnz=)a89@=g690{20Nw?b(~u!66JS zL%leV>eX(?VC3~klhSOL$76IL|NcE!jdq_f$$v^sc6$aM5V$j8>_mZ^jIEV z-u`9@qWxcW?!|!_?(C3u7{9qI;RR4B8|^;Jf$|S_zoien+jsY3CC$71IJ2F7Cloc{ zH?kIu51&$I@H)=^t^oLTJd@N&ZR#i{Kx6}Z@<*9<}Dy# zv9|fEPU%nC$-lpkQRAzs;t?ea;=kSQ&%yjZRSSR#0#p^LIM@FR36KUIFa@;jkh{N` z-oK7QA{7|hy;<-JRn7Fj8Gw|~#=|tT(ZDdSL`6&~6 zQUC}k6i-BWgNSbT$kNmPjqJogbf9yEP7x23=CCy^2#5|OYP?tdNLZ{#GpF0tF^g4y zFxh+lQ_*3s;X1OqxVw?}X@9PS8B{kCZ}w7CI?mtwic$}3QmD+&1q}~RQrOV0nGtGy z)KCnlBHN2fs+UTVm-d~iie#Ll`*75@$9I2iyO6>7ATB00Q5kExyYj>^NOmmjTK}jr z*=&}*v-c`faqWC(PjLUL42iYAU+Nh`du9QIzW)@4B%=P`!jOSFV>3e!&H(gbQ+~#?VmRTiwcuNpU2|MkFKJ$;-tNT;;8nlPcLm(+NA4y#GAM3hGccS zh9|p(JX!NDTl1Cw29;+HXi6~XWJf+xz~q(-^p7mE*bL$pYXT9P`A6|l^^y%~;Ws`X z5YY$-=;yGxijy0uyl3;FYOUw{$G71$TzR+1?@qXB7j0In-p+?4W-Wjgu5egPHf$YK zH{UoHn$ATmq=H%;0n%n(@Qc~NV6Bm@t@PEw^!O^7Z~~{YVEyP3l_|o3h#-MihxLYNk&??H zA7S;Eq?8oul5Z&)JdSd8M~7QUg?y$Q*i04d0l`*Ks-kQNc`}*xNqk6tq~`FC3JU}M zRM7iLsi`FzWTicxV;iH!dq=m)Ws@8pk1@n2{WLZuI!Ey-1q~Hd79^U0k!KWip>Q9QvX}p(*?2q% zg<*SJ?eiBA6C8vTE|(3o@JyamF@y&;R@t^%=gg2&3IKo4{Mk;N^uvAgUNS@e$9q)~ zfvChKpNCNhF0aS97QOoyhR|&}JYj%rmpl%G|Eop(w-UoWidQ#TEh4=~`6-$~`*UqP znFiVLMFshN&%$8J``PlmGMm{3{G(s3kv^%_N!J8C33Pr0fI8OTBP#P1)&ldwVvL92 zpzm4R)C=KR80fuEys=5trk`m~*mKo_EE%`2G+fE4He85eyUnB@t*VIUJM|XNwjov)(oueOdO#@R%=aVESUHkVvJpu)r!a6KHy*npghtLdl#eA}U@2 zX12Ei*80YMmq=L|l@&?K)-jOocmhFl>Bb6!zbLrgXm6fe5bfhfET`i6;n zYri?FlRok&bmfavH@Qwiv0IGg5fFrVR-*0K_SgbHzdcwq`&7DEzqPk51#|?<@MUwta#1JC1)=S0M&z@Cm9=&N7*cUM z_j83HmyyDJJu+v#wz7q}h-v{^tyWzl#XU(dp!nmtyN_za9n#&yLt*t8uP7?0xru%( zjl(;GEd&!pF$TcCL4)Xnlj6h@rjK#+)5RJj7K_>6<-B#*K4l2mn$M(!Gts~x5~-*gY22QN)o}%C+XLRQMj8?;ozGVh5+O;^zP#)5 zDiN>$$0smZVaZ1xyW6izN4HYkkLg3 zzRjTi)^zP>*l?`-54?9LA#M>H!6;&$jMww)goUjSh*QN^T7Uz6IM;9*$&BQxx%+CN zR$huP%{dn>d_QrxnmF_g`$&^j5yJ8sk4vgqB!RH4O#61_;2+AS;s!t;Xn5F~Jw2Cc zvs|lQW^t=%&4uD7rg`qIsi5s>7#a}32`sWmlMy{3OnNAaCb8ZcKd#?ej3P{l<&UUE zwgjF6gtFj+r*sKvXWc-H=q@Fr8;1NtUMCqf8qLBexhpE>mn`Fw*$G)>B&8-J#zyO% zjWL|W&2y`z}u4Yobq#|MV#YNGicarCK9YvQ}HeN4fGaz;y(C^iL}-*_R2%>DlIvPn z-)rrRIiMZW)U3{P9DQar&vg9IoLNW38QS%3rN^!#CjHbYqw?PEK=sz{uE1S-x-nAk zt|YTD4)Rz=k#ntS>5m^yWs2)U#||2mYfbCx$Se+BPVZ1Vxyv>{+&7L-+juO#6H-U{ zX8#fuQGzRQaP;UxMw~|BwZ3_jA!4`Lo1W2YKtiKcXT_j@fJZ+b`+|*Xeq3%ySzaAm zn(Yn$jT2G$h`e6zL3g(71}J7@2@egO)RpyGcIQZe){pF8Y=+@=KPRwWstN-viRzN; z8JF`FrnZjuRxu}Rf-)eINFPBL%|b`S%)|3CIIO-%gt<-PG4vbuQOy3RNUhiOb$bPM zj%v*;%L5mr9rP`Q_R9<)Jdt;$jMra zW@C=qiHpSj-4g>D90GYZ;l&OYK`e~{WaWr?j;sY?cE zKaPhih{N+$5`FdD0Va+bohBIWU0?r8hoVI;t4#QF+aQxERs)h#z>?^8U|ba@<%0?e z82O|5wYlqk$E=zW-EO&6j39VXrGl%BNuaDxCS!#5IVhfff&YN*GXl_|8zhBU05n*L zo2LSf4whPl#}S9_xj#UX)@y;z;GhRDiOB!qa}$0&^5?s!~VVVzsGK+j}24z}9{mZK3ss;c9nM zu&mkj#Sc!Ys;V--QCU0|&?ak-X-IE>(3Vs7(Gph9dhcx%l7ty>W;uq`uB^zd z+jkU?@Q}bZ#ldjxSlkless#dG&utekP5(G>U!P89r`ZYM!@pVNcz% zx#s0-o*GT#yck_-J>w);#cmwvWpt`9bQ};3G(Y9}F^u?tzJZ_#8s`2suM!i=} z%SzKKhxJ6N(t(+V-B?hg=&RjCM;2itH zr}W*4%}qEWME-l3wwD)g(5=C^_=5d8W<= zJerau>!o<(HVNYkWE}C7Nz!;7mlZa|$A+_fZcX&tv!yWlotxyU04EXi5>d-CPJ?de zi>yeM`r=~hx>b|vTXhV#(-2xMq4oJZzoRw{B&n3^yc)EK0`I(oJm-bx72l%3PXUQ~ z?yP)|W(JohbL^?qhMcT4C)ZD4&+wNlW*&J}ukG zfr7L7Pp!3~f&@j!`z7$_pFvZskgB0k4X3-&CJZ0yJ|?zGbm|M34CXnk|=hXpr;?nr*tnU zefIG@PLS1@Zo4_X=02gZEXiS)dz!55xuIA-*rUM1(c?MNI#a=*ol;s8_n%AQ6L!%@e+R+>EL)Us7CPvH)+lqQOJ zP@)!F;Exui=VzW|<48$CZP}r8SAMJjss=GrWiZ|-DCgG4eVl0U6E>VmpQ-)2xBqeZ zKw`uI89@MK$-F2@)SFV@H25$1OxzFUBU36+bIW$pOYS!iXM>hG8|wTv{Y|G`Ve}wp zInGVbyQ;l%mL4H<4Qg1dBy}Vq={~7Ni38up<6f`14nJwp!C3`OU2j8`Z{9ntUY~27 ze5bC02)wCRXI8*&@{ej~viI&rtP7~*q@=B#yE7B`T0IhuBaY`;=q{4(a!Yl^tq-d< zXU_^~Ed^5o$)Ijan+69f=ksxX@_Bj{b9l>3&uMZFRVitzX&Co-Ia+K^3yig2O0~7l zp3X>@fDSB6`JDV9_}>^Q*RjC=!fHB?a=V|$BsPxSTP$$pdGHb@@VPD-+B`2Qqd5>y zc~2hCUj0Y>_jwg|S?PC^l5<#@vShIfG>{y*9#q0ZNtK2d%1WPAskOo%Il4vKa^Ty2 zl7Ou-XQ4UiOxLhmFY8)w-prOtSIDwUmY$Y>EeQ&sRWS7-TfQ+xSR#SFzIRH{*#UXI z&q}Q_&r-sOQm+yS%uKrpe>@S?;^V%$y3$Da4!v;D>eDnDdkK9OWT3U65&VSW3cd3ytg;l@8io%QyeUA;)o7$^)|%u4rot(LwXG#%l1Jikh{_Dfdyi&2Vq@EO+s@%u}ClcvDe~ z>-WAd?340S4pY7-5hRGq^?S!vN8e*IN|+}^Wv+F-oxs? zFCevO`yROFSEQT|3Q|r8aBp2@WxaS-Zr?s--Dra*2ualS=AG3u9ofixC#Y=R*T~<( zf$w%(`B6Ur6xO%cRAq&MJI%sDQhT;?d!x0g!VHFb!Nmz>lXu8-W$lJUg=ErN8??ip z$!T_SyRm0#3T9-x)k#1VCJk(3s*24TE6tp^C*mvpT`3x4>oV+;@+PUrJ zRKDC;eRKV_tuoC@d=+e*)zUAH5$T2ad=v{!rM2mrJ&jKaH-W8$E{wX|-93S%+<*2@3G?tfZ;H|)t`x2~&x0Ev+z@cvZi15U2D<>fE8r;+;ej!4$( z7oq1$ip+4-NPdGhY&Nc9!}_HahFa10w@;6zh%}bhGjprKFGig(<{h@apyAaA59ovDY=9Ri3LnTQ0?lEy*!9KGxH8j zpVj*=p{YMzHPNE>^KawaDlW)59(3dQ+h_FWYjIXd2E=h1D8>QKi{yAL{};#OT5Hvw zv5VOS*_=)@(dp*k@TR9J&~rXN6`R?#cXN!zs6@l|M`N{o&E1zD9N$fKbaX9;f4S!9 ztp@HXPDa%CvKSGXFZ>*)tvJXDi5=X}H@BF4sJK3jPgSG0Dd?zZ*SSwm|noCjtlZ%Fgp?%|5++!!3NLrx)wSvP=blmvz{qcPOE%t#i zL_^Wz^(4Re9t$je`?&|MdzZQW)v8T@r0Z5W&Woq`%98u)z2@Qdz|oM89M5&Ignt)m zwY&Tz3+x4TB>$a^k6E=Wdxm;E#!bODN&?voC6}?V?(819y0cIE_wkszZe-*#w-s*J ztL5#D4Z0s2Pd8Tkh%$LO5y7P%nq14n@anCx3h^I~Jp(L`bo1eump!k86Lq~(|4(}=?CFHU0#La5(!#+xYiY=efQ7L5U@TCObWb*F~->yKl z4?n2#j2nSaxvk>mJ7e-A?IS8KQ)U>jy z;icFnm}E<29xNU=)YoXv-_!fL8qZ(T`&{&Sc^;7qqUOcF`M^z>iLsBE_`<2{`!q5s z%t)Qewx97pFnVKMBiOwfIqTz+Z#1=?Th&{k-vhY%r?Ho%1dJcl0XrUhKq$mJhc@nT z`BpDHCxX9L^=P{Jwtwo3ea=Y6B6h%0XcaABF3ZvmVbXDaZbI z+U5ywL|!$Q`Y-;=m|H)FX*maQ2fw1?0f6S#ynHOxmI564ETDa z7b?&04@BPok~LoC<-@))@ozxnJ9kU78)P@E4xby#r%S?MUv+Wi+vPM{Q*v*OxQ~Ah zd$B)}?SJF_FnXnTfLV6t%YA~CXzJhe(g+&;=!&0=hVRqDcxssA3|mlZ-U!+z*(meG z|7Pp8BVMV=mgIA(|3L2$#^&6B9Rr3yVST617d9fzbE8etv{^-jNYrIsr=g>wXeSe}U@1q1)AIa~xKA0OfdEEgj%w2vDp%q=6ygx3{ZCk=(-6n&1-oYE67-U z3%|iUz5!jX7Bp)S)((!r;#azgYT{_Y31EoDY|V_yTy-*Wdlx{d_(>2*X?b*xMU>nn zh>=@Y@Y_Rpq{r3)tAO^&Y}DVv-Ap+g`joB)akR~`0Q<74wWJppu3GY1z|?o`&x>Yh z?4`Utb1x$d2<3c^7I>zo4DJq97A*O^Ew|ORx^w!Tz2JZduzn_-;m*04T<&Pht%W@; znh~x-d%RGC2wCYl$FXH^7!u*Mv6%f#pWjajBejHSoXKr30B1^ZxGdAF3&U9Ytrx3z z{5CyRemo3~2(i|C{!CCsiofid{Y2Y-(o4xgYh(2%ZKy&!1}^_U=vNi?Povr0ZH{f=zNEg!-a5#K@;$6KogXKDB=sa}DG|Z}9Q>kGlZ6WD0P`m3OB-kmyZ4OY+-! zN)y6S?6;P|mb5(?AWQ!_F5PPpr~i2K6nv0pNQ!0`uA=igM3g*;cLQ|?Yv1kcl2+gv zlzSL+EJxC}y+gR z1sJP3R>bE}95>+W=(TCy3GK4?kc`&h@;w#{elgL?H_Hv7-YL9yD#(?Q~X zZ~489(>8^BXP6l{UAxU$iupEu!<9>JxHT@H74XCcj6{b~qBiNY4GnPfo5P@+ce!5z`tv zf7zV(T&OvhUZ?={(?(dT)w!z8#KQB5J*6@0VFKdGJV#op`G~e@N%KjM;}sf8buijP zO0l=c=Gx6n+YovnxL3$Dl}3L|sh;DTCUyWZMm=d`>-&mGl%{&V!nhlex@Qb~Ulg_>f5@xneaP192U}sUd6q4yk5( zJ(*aJBIW-b0og)H+xhbgwQo|Dg;Ot&nngqmfL`9O@W{3NiP8V{pcw#o$BJGj*JYkar=7mJo=-9JU+E2*aV)=V z44at~qG{O`wtE+he+f40jrW)gCntCQuW5c-46Yo2pB0bSo8sEL@65G)1(A5Ab+upc z6!2AkI$b8y3Y`&rd&l4>UJ}g?9Duq8C29Y9%I|()pdk=A`km85=6?j0{%a4?=);FM zC(p{|{M^U(4@?yR#_p=jKmD&c{s;7aAt#Qnw)2PR|H3i-VN>%s1BeTJJ4pA7P{xl< zvtK;FkYvVIa{DJ92C=n>Nq>$5wu@-@m@f|3Y^F z%H!)%+oMxADT(fcm^olG!ybkTzLS9?NfesIy^L$)YB@M^(pZOi6b(C1l%oTKwjh$$ zVU~{pcX(*7=PKg#=>Lmo{!J>0MGOGH&8p__+rSm+!0m42$W@8Y`$r3TDbkVdM{UtV zA~$_9A$fTyye6r`BVv0r>z+Jd6ZvP{W5x*1P%8H{5*ZsU;iZ2sR*2V|)cjNi*?U*n zURhHq%fyTE9vqlB{{Ve5m(es}5qG9yUIGz=EaxJ(p+#w(EhI)D zsJCm#nLHno9C60{w%X)qelrVSAt%KyOu`u#ix%TAIz1BKcl~lw+vtpNceh#;9XpAD z6U{7g+n%lRlYcwBsoRlYMK`R6(B20Gi&F|cQ)SU?_Mo_nR; zMrlrv6u+XHz$9qd`Z7V%=MNOP`u!DF->lr%R;f=p-sA!(d+_Q^bOeO4+fwGm7Q-+~ zy**n};?`d20Z(6&EdC635tZErf5`X1I=QP|%pWCH<3uiAQHecC*;onK+q7(OK*#mB z?`8@lgkR%P#9@@yIm1?IP23O_6LEom~2?sw9pnvOA+>BkWRfXC}68Z=LJQF{MI z#OxQo;_BGn_1zi(2;dgN;4ayFI^3EcQXh0y=yAp@DwhT~ph&@D;;- zm*bG7G3$1e314TWW}i#@cYPRbaNeByU85*%Z*BZwLaqBa%a+(PTgMKdMC*Q@>tE0} z8=Qw&rVw(M*;9Wq?mr&HvVe1KT_3xajT5wJ4g%;#Q#a4`#dmHh=J~%+ipy>O(VXqLTa^waWv6H4Ox-i;(fLkJp)il`xaqpMZ3UvM>6R2){idbfi*Jr)@76L- zQ}n*F{{0+?fI>E!62JEg^l9(CxDvn8FeSPj!Tekp5MjWXuj%ahj+TGm@bBE&rnfb; zhcgZLAGG&#s=qx*M{UWuag)8@?Weze@y}JHEzbZ$^$>5NI}0QuV?ZE9!RYq<63*|- z#cKgW0fTJ)7rx5B;Bxia(mB*KdwvP$cgS6}w~poiTg0YME$!PQ_Ur?YfercX)G%C2 z+n8&eOc2F@F>m!ic-m@IL8IcG5y2O;q8M)m;)msPf+HzW#=%HrzphuxnfG#V4)dru zK}!{^dzr&fW2w05=*Jfvfk3MCreIvCC^_X6n7N5hD*a-t@mG;y+CozK*H3|fv(*#q zf2_~NZ(zpesG;{7!mL@GP}i$MxviATc}b4}}mMo~;B_2bYF<>JpK4f|GN#pA;0 zjNZ~l14l71id+$zL7Y%EQ&avGjS6Ttt7mdQ6Ga}T-RF_?%-O^7bi*l5<|YOxqqKKzS?Ze9TD=eRwRH3T@Ey@*0hYdU>ikA-WagQ+!!fQWW4eQ2Dwa>y~$CbP%+8v*UyJx);r#abUm@It|on8KreAleckzT_f^ z2`qm6wWQY=Lu*ZuzVQo$J!m)+x@gt_DUGMLsz|KS>a<`Lya$i69hYNwf51~Y`@wU$ zJ#7R*tKm8}2!jLVtDSM#85to%?$`@GdOkU+FB(e;<;3CD20q?b_GQBo&yN__%=Tr! z{u-E7xaV~eT%y=>6RtxoDF@s3%#N%p@u65M2`-*h;tsyE#kDEG(F#!vvZURpk<%7M< zDIJt}-S9M0iI<`wvA9ed3FiZqB`V*7?WHRnuz7UE*r7uA@GDcMd`o*NN*Z%Q9AdeD z#_{vZ>r&mMgV#2L>L?XnQxUzRl~S7uZy^)b{LaJ^XL3_~ej$wJwnU5ZE&O#&Kc9KX z4aW7erHzVCwI#5ikN#2I#)O(f93dP-O=+rJY_r4FK89Y{Jo-3>;@M6hH@!Le21pVl znNGGvj5eet1osYm3U*Kx-du?)fy|Tn8vVgFODUtHahAR*p0h!D&|4gylVKz756xG! zUewn~tr^$c0zzP`8*a2$W~vgxP)NfWr%z6D78?E8y6OF?=8dnHma_8XY+#l4WpkO3neS&dJ_2uC#dH+j>AHNkunPT;0pyUOrm^ z<&4J*-_Xpk8sZQ`Q%G9y=??|HRJm~}9gCPh)Njqr;CSZ&;^Q3Ophq1W^uYM8fJ6sh z^W|-zQ9$vCq?A!Y%-3hkCap!G$xlM_5}a>;TlS49w6}X>Ze^9Bsijp{<3nKL7P8<_ zm@6sS+|+?tmBT3^&8o^DLC<`Yn|h0$M06}cMTQ!6Fk+&mgHW>XbgaEcKQ}LO^>tx} z1=VHLzD`8F6t1Z_z$1~=;hv_7NKh^zHuag4{g>Q3g+yL(tgZQq!8^R4t=Q&LJbb+g zwE@6eB?f&ceN9@L>^gVHoj(CBz$n{PczhG z{b7sPeoke<@OiEhN+*xmJ^FqihD8)#FVFkzebN03fqMuSEQ;$X)~|_AJ*JpyJ^g-+ z^lFHN!6-Bx;_xl@Y)yhoYWbFR`Xt-~tPpasw6)?pjhgiwd5o~KLHPR8xJGNE)c!iz zf&8UsZ)!N?*nO4^E!h@g%!o5Wl8mc+uIEwKuZ-(JQ%9==O?2KbP%95{USy2z99{GMbH<<)`{IRYM zGY>ul73m1)O}ShR6y0EP73Xo3N*s+J$LkBldHc9pyUqN99RPd292zQYXV5p!Y<*R+1f{c)3(B< ziH&vgjE1`kjY=^kNV*4R(VXqydTy$PJ-vIZ&k}AI5bR*SlHfbN=IcWk@(AXTjK%z+ z?tWX4wa60lDn`(9Le8Qa!+x)=najK~QZEy>I6Ojqv9u@RQ*~;98@8IcaUco(Sq0HA zg`x=sw^BD|Ibf9}lf~u=JX#O~W=2u&);@5`GnNdnXDWOfUV7`nR0_TFL{r0?w9lt-4trPRL^4NKB5fJm(7a-m2IBjF!#gwX?yaXqe!Ei4-AS2|P`5s7V-)uTf)V1Sxep3at zw|ApL(}Kq$1zI097{y-4JnE#ID)T zFqtv~+t8=nvJT{_P3H`l65o(iZZ%cPsmqD8j$A{bEzOEQt0^W)TFu}msz8+A8OBzv zp)t$DD$xtj!N;92BBIi|uYV2QDr&4on*Btn-lQR)%piN=^ql;n;hbX(C6@ z30z85iDz;t{E)odYdzJ0V#K=Jx1eE+^khqgZCo_K3~lq;vOb3`%RZBQ2UmZq^Dz)( zsIgMM^_V94J0PZ9k5wv6?jf}!yG^bp>r`lQEQVjX13XM4gVRQaEtmlK5 zwYRTDM8UzaXBj1z`V7;OzdplJrqKl^7O{=5I1yoFN~(cFU{c7(#%m2bxaU zP_B)lOBz&Yx1$-Fy|krQ!~NNHkG_koV)&{)7mQI-g@+g&WYDPzXI8t?(}0N)FtZhZt; z6g~vg*vdXOe|cJ0+DD=-VT>Auih5#g$K3c z!#Mx2`*ocUu*h0Oz1?u)RC@BEynfHiPhFhP3=}mkoNi>v%3+cEa?qC99qaTWXKD7F z32lP>yE(egIZzo-zv*c?=;I{{WGRv7BQ$R**S(%kgHB{`qDJ@z(HWc&FvdMB^+LsX zq&Pz*zGBdQrez?U{URM_G7oEpU3J}+#W4-c#EeVf@`(Y5BJ+fpk&sFPX1~ZP+*kAGK(g8+d(E+u>bE2q+^>Z{tbJbpaf5J9hc<^; z96P0oJ~rTJbTb?%W910fO2E{+1#2|Ut~aoc4tb#QQd8l3n|R*Psuf~P)f46Dk8p)Q zihT-J&&bLNW-lEOGp-z&J}XWdGW{y|F8A{G-mY}-aljN5PH;&>HUSgHlSP&D)UH}D z_;#h25vLeBsEpv%{Kr=BcsskUZ#?R(PPP`-M$1Zlxna z#~702TBJ2|ic_Vr=w&jOab(NBNBSBad)zN@%V(mN6#a>vK!mF+wzFwEnS4>1|4pNF z({%%PyOL~GYn#a1)_ETf>fN~eF1s&!XxyJiI_eJg?&(Q#QSl;UmH zg{D4(SXW?Q9#s+@sHRTOBrUm!4%v;46ocj*viGYDni7@86C+B6+x7hyu|ixK$=1(f zRoaUu7M?esbF2}z$PL60B%^2kWO7f=uuxafxSoQTP;5M_@ntM5ByCSlhk)y(;@Sj| zVo>rh_(eQ-^7d9tw5@iYr%WvW(&O{WLmibKdA`RtzWF!?k%w8CYeoAk!iF28R5uOn zia+)jbcPHF`wVGm+e9ZkoSlU4*NV;2Efl1aPu_^p(X;Q?8kv~Mi|ze3Lj7i&t!?Po z(g>uPmuw7z2ZaEofNe?WD04(ukjsi)yv-_wg>z_cIw~&($|%{Fjq6%l{OJ|n%i-VU zaA5#l)gLbcv4Bo8pxMv803l&T-H`^+3JWry_~RJh_ip&0sg*S@H+#=x_SLfQpt0Ik z(3GTkTWBwrw1nCqv8z45wgCV;T0jo-g%`X(2^m%40%GK*P_65o(8k9MN?K{;Wbf|3D-D9={ohG)~ z=u&8#tS*)HFtI9yA(=~5Ki1uB$n7~k4PYP9&x|!NZP&awpYs(dq&V>&LDs<`yUyS^ z@nY!>?bg%_3g(|SK;h;mtn8Bje>V2$le0G;#Q2H@gu9vb7hAiS-?y^gn9GS8jtQ^0 zU<3oyYS_BQ)jVN~k5}#Z(q-Por#79=exuk3R*!x6&boS{l+^@1ajeAJ<%;9B_a!zw%U{&H;anA!FN$&*|)sbYk;<$+C zscmEFWh{k@+2y8pm|{OsNxb-#3O$#V?j^1#-sJiA36Iq45NnnX9g!~Gm;WK0f6DE2 zU%$*o!g~()yMB4TYkixge5;FmBb3)uLSl`k(q<1nB*CSsKPcyx&FdEsRR-#l72b@d zF`KCO7_;1mjD}r)-b|Pq5}h9g-h}V9=e2)9c~cz^fut&A%2N?JSd^e%4TC zJX7XD84Fok&x4ZDBQ>8IXOy4v(Qk-5uf1Yt75KPGEtF!8gRDjLZV78{hqMS1OSo`H zbA3H)ZWN(ycKZplCL zm4QPgHwrsFJw2ifXWwwx+^DBb3}~dOVxk;_IZmNa#Sb&XHOXIU8F_kCx#JuQ)oLJ6 zCf%h^fq9?BY#HIhl9gyRIW~3eyb@NN7_r#%JgXILn2|4l6XAQMbjbUW?@cG?P0kq@ zoI`R%idI!vqI`YXn>m3z*LOj1i1;LFB>5El)k?34=oRRyj6^tHy5UOH%t$@V@Z3dF zkXjEsr>su`Q{%ksWb&$-JShD9$T_xJ9Z)P?m7Php2Pr1)^Hv{%??xr_=dGZ}fMb#Q zy%+&5hw`aX-Aov5b&iksYLC%bZa^Kn8j{jb^<)g{Y9*DsV1(X(v@we|Je$&Q)@FC- zvUrq{`-8W%p`wvxK}@i{`=DUW>N8ATBX{ocBy>=)fg_5Wfwx?A7$ivJId@rkB8zL! z*N&#VMuAsCk21XQ@lI>hrrdI|ru6pXm1XGp%Fl(^(+XSz&2%^^@ng59|LofeM0LFZ zgKYDvErQrLRC?;W7wa%E_gu{F&Kcpd>d|8ZpN+`i<7mjY<(nh<{v#P_2@MIhwU)UR z>vPz$UXIE-FR69Cph5RCo+eDLo<&Um>L)zBalc!ChcRH6xlg|;zEv z>I9Tl;7Yue0*oVK>kIy7DYI+WOCdyg zzm)oeT2-j0%I3636gmRtaQN8S@S|_jKEPe)G!^}ome_+)Kv^6IUkm~eq&-1CjvJP~ zQ{0vl`$tmzM@%Hg1M4~~@77k-u?IdhuRYomEbdryMMZ3d)v3J>#Xa*e&3zaQvf~|y zv1fxdKjbf}0rOUlV4N27_*xi2RkQ?iKM+u+c!{HuP=9l6*>OCTt``jPMtjsOcALJ@ z3WjP0%P>6KucdYLqXZr86Sap|qvz!SmDqc8t=Ms5@6{>fAyBzTmxZ4cYIAN}O=n~A zw8R|0RW3;Q@?QC=ReppdPb9~U^Q|{$23IF;N%17SUU#09*=YT%tY`kQ3xg;uL;z-A>oVCHt`Ch322EKJ= zs_1+HX9Ab;HFF2-+?zwpWg5?4YsS_H68jO;z=ispRNuP1kxHrZtMfI4Z@&65#jq$< zyI$U2*WB&X>*w!#?Xy0Oym8V!(74}``9aHBcBz3vfYTgfNsP0#@BiK zdPlmAr9_zo0zq^hvk##G^Mn`JE?&nVZ$W}tV`#ftsYdF(V4&6Ey00dsw- z=oRxRy{^m3D+1FkWrZ0zSL(mYG))j}I?u!J4yEO~R=1lC2m0-2O|93{B^#BUr5E zhYyX#D^cI{Hde5h)%uxcc_lwkFbhNB)dP@Zpr?T@%$3*gpx0z2yzN2Dx1FK2#Lnw|m5VG$a;eVrRsxDZjz&I6HW{Q!l07O2pT zBXBCOsYjDiS~xwzP5}-amUp1;W9KmWfzxug@UKcIczA(c3Acy6KXLoh^7`k4?hRmR zh6%jg4~%c`ihVFWb$qp;Z_+6GpUJ@g{v`ha;6^YtW3a#4_Fok2S9j(7f>}X(<&Eu^ z{xSW7j|Xg+W?d~$>8|eUOjCth(#VQcq7QwSXNNCppDnb@7PE_(?8z~}*5O!1!Bp=# znCa)|Gxbbyj)v~pQ}x_=H5{inRf9R)+zJ2zDlBO@m;lt*Y!aSD5$DDu*^m22fixww zbz{H~S&6vrbD|EnfvT$d0RKaEHd(nRwY0{aw9M*jSP504HIMacW(O)Qv#XW@)C@+G zbPt;O|A6u*3;ySW{^bY76PaIECarIGFVeMoN$d3m}~j%X2onH(IT?5c&4EPjX0KhIWRWz{rJ8ZM@x29Fb}#d)R3vx2D8`w#JG*rmAw z72yd$C~+yzx3_d4C}=-*9CJRPcCx=`-h868$tFbAdRx^S*Jcie# zohn_;zl4sD2BKkC)=6_3$N{mP&cI3yNY4gJ9OG5-gAIB547w7HyqLAmm8#nne$-bp zGGZlz7j%PKpNd1*1Di&R-@so1Y0e9IMJ-HmTZLr>qy@OY4ArH{Xt9yo@$4_rVsrDfU40#L{+|TK5swBW} zlCTiGMOui$-YYE;;dhgdFFUnSY0Lw5C3*$=`m~Levd?p6IOZrLFC$+;hKZ}!ZFBf} zO4)9`FSo@Exd#!97G@;&Hx%(zoxCWbo95ZPeOcST2+B$!F*C(K&?A1j7`5OTpT|k(q#QEB~zIC4fJ4Gn^Nni7FPXert3VG(G@Q zejDz{49%%oZ%apr2qt*Vr+msC5isAUU|v!`%T~7^+c>H!8Rh68U`i=Wgx`)oE#FaX zX~faY|M1g`XG1N#ou`Gw?aRENvk^M7!UkxSon%PcTcFLNC8MvbkIuL$RZ+pbGDpO| zl#fey<{@j>ZQW7g5Vmj@g>eQ8Ly z8752U%I9b?bDQOsTyd1T!N84brPxf_?dviAmwcK|O?5n#xCF$|Cp4;*Cx@fYeN@v8DM|;6PDzKkNJ|*S#(|bU-o@0}L#4T`$~1`mm9J)1KN-ccn1ZEi+_FYS_TSnJoBI2weR0600(dcP zq#4k48I+mnu=rs=yneFX%&eqa`xU?g+&Su{VCT-GrWsVo#T7z87VQLAThjehHG7F0XCVj_PM$n}2<-BG$v-ruWH?Rqt>UQ96&NTNASM}-6v3?Dm5toL=UVFI|1 zH59?BD5U630l=1b_5BuoN55+93TqBCMv7*$Il%~f6>emfr%E^t$o$xuw}4A+~e?$K7Kqu>oi7Rl~7z^zD5ghMGlXbZji;5>x4q26)1vdKu3 znr|L=R7K7eK<%p9#TKc5l8K2S%*I}lQunpG7p6UCNKSL?tZ6JkrP2>P`x^TEmf-KxEZj%FL<+l1G z9S}{r>U2zXC_Uyn;m9uhXw6U7X9||q*%Y-!;uEToazJ@%5iL|o=}g;dk=18ridNwU zYl4yRQ7YWAYCWiUb};F9u!m{E7=xyc4*JR7IZ(I~o-aEQo{Z=R0Rh^}G@0@{JMeM( zRlnOJ@PO&?)?dW$ety+#0uaO+YeqZ0U-<8^hpL>N0S@VZIn(_-`T1#H)-EsJZXap2 z@&^>I{Rj1k9ZXXI8}}6ei~ZV^^(4bi=MA_>M}e{ZPg1rmYyCGwiLZfv8qJQ*Hs&4h z0=Op9tiagfy-HfU7X8Rzeoju8i+J-qXKuXLO#&X2e1~!y>%BoWjtF2!+{aFx?OPh z+*ZvrRNQhG9Luy-+FsX482L31hmOmq2dL10YM;8G>tYL_i*}g#@owq=?&l* zigB$myMUk$Xbv+gSgO3a{d7OhJ(04d5*+dOc81`CDNx;6pu1+YbAQbFHYjimb7d}9 zc81_V0bs=cKO$S#<1nD)dMPrKB?_=;rV=BC|7x+@UO5~%98Q0_evH9v`5FI4hnSJM z(O0=cQd6Ilgn=sEV-9*QJEC9ub__EYoE?S#EiwyUo!TfWNGT<$eg)l2h_U;gcr$&r25JmBDl zw1PDuSzI^pmg-}^`{~2}7^>`@=&ZgMFOaj}p!f8S6cY-svs~n`wyU|Jf{V}sWYeE% z2;*#IKhWQ2Zo^?-e4O{4!VZuCjbsOawjCiRME#ighLz_g(9JbzcHi?|N1<4pT)IbI zf>#p|W$BR7X*;qLJd^#vfvpN%LDWwup{FY|gMe*8m!?u|4pY+uI3%Y{fy6(5T{s_m zcJ_`4ve{=nD;fMTtzdO}`dVj|Pz`XkyV$~;`z#7wp({ypU0s@hp{)W`V$6syDDHf0 zMsnwaE7A(cLI-h`I*eGbB1iQH_{pFU-RqAw<(o1yGbcO6%NYuDQOd=@P*G3?(PK+(6C zdU$^P`iIc@Pf|V1vaeKL3JG+ zR>;P3fF3_*eXx&IN7bDjBW~w!IQj{dCe-(J{)D>5^&WKC(j)$&un}IH85b{d;ECp4 zobiy^Qux$b)g7RhEeHznF>jae4$L#lz4u?R{)4`+-8-*5x77Zbi{HM#n5PnO?&5<> zbaX`T5!4?V<-D8+WSczv-SLOvv8vV<>d2U4z$ta$ck5r?q*LazCKyX50lj8wIx}iK z$#U=XcA(qI1N*G?qcZd&;SCz86V*96IROchNc`v=fWLkRu`9UW9PHn@s}+B| zJ$Ajy4iI|{CdlQJsZj-`r6SWaGiHrlQ)>)y#{J=INx-cadN#4K^-NYOm%6u4`&J z#hJXg60ZC1Mr(pY@(t3kwS&zGH%*|C|EUV!g9}7C&y}s` z0yy$|Sa)lDqlhA-?v4gbymX>L0UUeWJF+23j8&Y#RHE1Mr%u!jejj{&%ap>R)plSD zfLNsK04pNBPcCW4h$~>BMQ)i=v!hpckW0W$6HS4knzKpm{JQ|52{5JfNlxKAAr5fb z3xJ{5NbQqncL9PqU`k!zxValoO&1t?X2Z%gXlD@G3IU5Y67ugv?zX!Bux2(eGyozb zwp~N--}u%_5AXn*z#+S^sjq1RL+ijS>=%D6cD7Jd%mWa)IY@37HuV{9V5li5ION3k zJNiMx`dhvGh4AgKiTHUuegS~-t5yJ)MgMKH+b!__jBJ@MEtsh^DU0ud7S6Z%?Q^NK z;gJVyHIkCXZn(|!e>@Df^Q)#AFls zMUrZPLh@nA>bDnpZudlYb1BRDHqFG8z971m)f0}@D?Lrhd{%Kgv$8aB`HiBfpn4Cy zurSdNG%=j=rFZ5kaK+^;Co=ebe(Ccp1aT3(w5S z_4JTJb7fMe%XC);ZMHoLz3}N(hu zx9v7{*UnlH z3vEXE8U81wY|=>c=RX$;Nbb*<95U*3zsGnd{7In9Ay$^V@>YkdotTgRdBEehKlK8x z%KUag{`ZDq>%zlFkdL|iW3(=vC}7r0KXB>er30sBa$W|?=D%t0m`cc)fA2DEup*Kc zLe?D0Oo*9mCe~Kt)=)$mesCk(r1igIg`yW8k2glF07MBEGW?x!V%2W-(w^5kHIFVG za8B2$eqE<(n8{Az!@{p)i7>;E%Y{GZzm{4${IA0cS{zE$2fxcP;0| z>Y%E3Dy2b=Ln!Y&IzrWJlE{%R)iLO#TG_}qu1CI~(8dvH` zo*2xTnVEq(k%v5BjyFaA2VVYxJBtJ0)vx}{^A-36V(nB_$K?y~E{Qp_^?)d&y+DTR zxf?h9l59O<@DkCm9Dc4sjLk$6&|YLd<0{XGyIUdh@FfE$;D$vg>}JUqgCiwtfsgoK zH&}_7z3n~QKcG6P%a*;+-D1@5yK7UW^dd3hRcaI#=edT>JZ+2h`5;WB>h5aem*B zQVuETW*ni4*ot5zB(1%9XZa7L|MOs=_H@HA8U=JL&tq*MODB(NCoTUa=^JnszGg0~ zkH$*)&P|zLI{dmITC})eNS`Vh#TJv>N>?NzEOiym(Ex8^JPXBlw*bXr2-we%EP@l@_q zO2t|^;;Y<5PKCvU=f19}g3v(aukFlD)u=Dj|ZhVWc! z;G{uT^K_o8KIJ$H6SWam2#4Auv?Kcv)k*qLYfJs;;=-@4;q4q5p4A~D!ImaB73SmUwii)lN4f?1i zQtW`#yeA23Nn34>5rhdHkcPTUp8Feqw}HnmulmFB)z!@*4;-a^6fQ+2-&q)a^Nn#_ z0eB6rlFfdQSBKn0#^?vRedo{JiYnki8C58v~3ad9W4`0XWcG;2o>;yQ04u_kB@(CR9Qh4!VKh@R==$8up&a z!914=U64(fyy!yv z9(jKb^z&!f0nR5pPeJ0uStFl`7|T|P&4pUVVW5iu!ia152NN}4<+xqdAQj2-RIw_rOkbqcKSK_}tfGXWaEYZ#k0G~S z4@VS*M0hLaHZ2aQZ5)d+gUixndlDy{?bho(uXbS&XzLj_1X{eX>oC2S@Ae1pLRt zvESP^KV|y7cKi|z-kV(Ua)M3Yh2k6gZjz$!lCu~+!`CCE$v^zK2wnZqpE*8lq7#&` z;i_!weJ(yNgtj^|)~rKbc10ZTsMv+O+NYZ67o2N5|Bh%qx89YjY(M;t(lZarv*#pF9amP8Nd5NulR8IRY!dz^BB)$vosV2EA+xDv9 zT_{?;ZnXE%GYP8&Glzn&xWKH6$Lb&UTv>Jsl@t{b--R*Md^kJyuCCiqv;DDNu=h** z4M{Dt`AwOP7pvB#yO03T+ Date: Wed, 10 Sep 2025 18:58:48 -0700 Subject: [PATCH 28/73] feat: Validate LiteLLM Virtual Key format (#14428) Co-authored-by: Cursor Agent Co-authored-by: ishaan --- .../management_endpoints/key_management_endpoints.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index 0bf957f2656..bd8faf34be8 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -551,6 +551,15 @@ async def _common_key_generation_helper( # noqa: PLR0915 prisma_client=prisma_client, ) + # Validate user-provided key format + if data.key is not None and not data.key.startswith("sk-"): + raise HTTPException( + status_code=400, + detail={ + "error": f"Invalid key format. LiteLLM Virtual Key must start with 'sk-'. Received: {data.key}" + } + ) + response = await generate_key_helper_fn( request_type="key", **data_json, table_name="key" ) From a13aa4740ab450207599491b5ba710fccf9cff10 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 10 Sep 2025 19:12:11 -0700 Subject: [PATCH 29/73] [Fixes] Bug fixes to using LiteLLM MCP Gateway (#14392) * fix: use _get_mcp_servers_in_path * fix checks for using litellm_proxy as MCP tool provider * fix: fix mcp_tools_with_litellm_proxy * fix: fix aresponses_api_with_mcp * aresponses_api_with_mcp * test_mcp_allowed_tools_filtering * fix: _filter_mcp_tools_by_allowed_tools * fix: _filter_mcp_tools_by_allowed_tools * test_streaming_responses_api_with_mcp_tools * fixes: test tools transfrom MCP->OpenaI spec * test_streaming_responses_api_with_mcp_tools * fix: chat ui allow multi select with allowed tools * fix: use correct MCP events with litellm proxy response API * fix get_event_model_class * fix litellm proxy MCP handler * fix MCPEnhancedStreamingIterator * chat ui show list tools result * UI: show MCP events * fix stream iterator * fixes: litellm proxy mcp handler * test responses + mcp * fix: update responses api with mcp handling * ruff check fix * central: _process_mcp_tools_to_openai_format * fix: refactor code * test_mcp_allowed_tools_filtering * test mcp with litellm proxy * fix mcp call * demo: video using MCP ui * fixes for using stream iterator * test_no_duplicate_mcp_tools_in_streaming_e2e * docs fix * fix code snippet --- .../mcp/mcp_with_litellm_proxy.py | 36 + docs/my-website/docs/mcp.md | 20 +- litellm/experimental_mcp_client/tools.py | 42 +- .../llms/openai/responses/transformation.py | 8 + .../proxy/_experimental/mcp_server/server.py | 81 +- .../mcp_management_endpoints.py | 45 +- litellm/proxy/proxy_config.yaml | 11 + litellm/responses/main.py | 177 +++- .../mcp/litellm_proxy_mcp_handler.py | 346 ++++++- .../responses/mcp/mcp_streaming_iterator.py | 604 ++++++++++++ litellm/types/llms/openai.py | 78 ++ .../mcp_tests/test_aresponses_api_with_mcp.py | 887 +++++++++++++++++- .../experimental_mcp_client/test_tools.py | 95 ++ .../src/components/chat_ui.tsx | 301 +++--- .../src/components/chat_ui/CodeSnippets.tsx | 2 + .../components/chat_ui/MCPEventsDisplay.tsx | 263 ++++++ .../chat_ui/llm_calls/anthropic_messages.tsx | 7 +- .../chat_ui/llm_calls/chat_completion.tsx | 7 +- .../chat_ui/llm_calls/responses_api.tsx | 39 +- .../src/components/public_model_hub.tsx | 2 + 20 files changed, 2814 insertions(+), 237 deletions(-) create mode 100644 cookbook/litellm_proxy_server/mcp/mcp_with_litellm_proxy.py create mode 100644 litellm/responses/mcp/mcp_streaming_iterator.py create mode 100644 ui/litellm-dashboard/src/components/chat_ui/MCPEventsDisplay.tsx diff --git a/cookbook/litellm_proxy_server/mcp/mcp_with_litellm_proxy.py b/cookbook/litellm_proxy_server/mcp/mcp_with_litellm_proxy.py new file mode 100644 index 00000000000..351b0920eb8 --- /dev/null +++ b/cookbook/litellm_proxy_server/mcp/mcp_with_litellm_proxy.py @@ -0,0 +1,36 @@ +""" +Use LiteLLM Proxy MCP Gateway to call MCP tools. + +When using LiteLLM Proxy, you can use the same MCP tools across all your LLM providers. +""" +import openai + +client = openai.OpenAI( + api_key="sk-1234", # paste your litellm proxy api key here + base_url="http://localhost:4000" # paste your litellm proxy base url here +) +print("Making API request to Responses API with MCP tools") + +response = client.responses.create( + model="gpt-5", + input=[ + { + "role": "user", + "content": "give me TLDR of what BerriAI/litellm repo is about", + "type": "message" + } + ], + tools=[ + { + "type": "mcp", + "server_label": "litellm", + "server_url": "litellm_proxy", + "require_approval": "never" + } + ], + stream=True, + tool_choice="required" +) + +for chunk in response: + print("response chunk: ", chunk) diff --git a/docs/my-website/docs/mcp.md b/docs/my-website/docs/mcp.md index 1e523600a86..18c99051709 100644 --- a/docs/my-website/docs/mcp.md +++ b/docs/my-website/docs/mcp.md @@ -197,10 +197,17 @@ litellm_settings: ### Use on LiteLLM UI +Follow this walkthrough to use your MCP on LiteLLM UI + + + ### Use with Responses API Replace `http://localhost:4000` with your LiteLLM Proxy base URL. +Demo Video Using Responses API with LiteLLM Proxy: [Demo video here](https://www.loom.com/share/34587e618c5c47c0b0d67b4e4d02718f?sid=2caf3d45-ead4-4490-bcc1-8d6dd6041c02) + + @@ -234,12 +241,18 @@ curl --location 'http://localhost:4000/v1/responses' \ ```python title="Python SDK Example" showLineNumbers +""" +Use LiteLLM Proxy MCP Gateway to call MCP tools. + +When using LiteLLM Proxy, you can use the same MCP tools across all your LLM providers. +""" import openai client = openai.OpenAI( - api_key="sk-1234", - base_url="http://localhost:4000" + api_key="sk-1234", # paste your litellm proxy api key here + base_url="http://localhost:4000" # paste your litellm proxy base url here ) +print("Making API request to Responses API with MCP tools") response = client.responses.create( model="gpt-5", @@ -262,7 +275,8 @@ response = client.responses.create( tool_choice="required" ) -print(response) +for chunk in response: + print("response chunk: ", chunk) ``` diff --git a/litellm/experimental_mcp_client/tools.py b/litellm/experimental_mcp_client/tools.py index bfbd3f96a5c..b716e3171e7 100644 --- a/litellm/experimental_mcp_client/tools.py +++ b/litellm/experimental_mcp_client/tools.py @@ -17,22 +17,60 @@ from litellm.types.utils import ChatCompletionMessageToolCall ######################################################## def transform_mcp_tool_to_openai_tool(mcp_tool: MCPTool) -> ChatCompletionToolParam: """Convert an MCP tool to an OpenAI tool.""" + normalized_parameters = _normalize_mcp_input_schema(mcp_tool.inputSchema) + return ChatCompletionToolParam( type="function", function=FunctionDefinition( name=mcp_tool.name, description=mcp_tool.description or "", - parameters=mcp_tool.inputSchema, + parameters=normalized_parameters, strict=False, ), ) +def _normalize_mcp_input_schema(input_schema: dict) -> dict: + """ + Normalize MCP input schema to ensure it's valid for OpenAI function calling. + + OpenAI requires that function parameters have: + - type: 'object' + - properties: dict (can be empty) + - additionalProperties: false (recommended) + """ + if not input_schema: + return { + "type": "object", + "properties": {}, + "additionalProperties": False + } + + # Make a copy to avoid modifying the original + normalized_schema = dict(input_schema) + + # Ensure type is 'object' + if "type" not in normalized_schema: + normalized_schema["type"] = "object" + + # Ensure properties exists (can be empty) + if "properties" not in normalized_schema: + normalized_schema["properties"] = {} + + # Add additionalProperties if not present (recommended by OpenAI) + if "additionalProperties" not in normalized_schema: + normalized_schema["additionalProperties"] = False + + return normalized_schema + + def transform_mcp_tool_to_openai_responses_api_tool(mcp_tool: MCPTool) -> FunctionToolParam: """Convert an MCP tool to an OpenAI Responses API tool.""" + normalized_parameters = _normalize_mcp_input_schema(mcp_tool.inputSchema) + return FunctionToolParam( name=mcp_tool.name, - parameters=mcp_tool.inputSchema, + parameters=normalized_parameters, strict=False, type="function", description=mcp_tool.description or "", diff --git a/litellm/llms/openai/responses/transformation.py b/litellm/llms/openai/responses/transformation.py index 392d47f9822..1d52f74b7b9 100644 --- a/litellm/llms/openai/responses/transformation.py +++ b/litellm/llms/openai/responses/transformation.py @@ -272,6 +272,14 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): ResponsesAPIStreamEvents.WEB_SEARCH_CALL_IN_PROGRESS: WebSearchCallInProgressEvent, ResponsesAPIStreamEvents.WEB_SEARCH_CALL_SEARCHING: WebSearchCallSearchingEvent, ResponsesAPIStreamEvents.WEB_SEARCH_CALL_COMPLETED: WebSearchCallCompletedEvent, + ResponsesAPIStreamEvents.MCP_LIST_TOOLS_IN_PROGRESS: MCPListToolsInProgressEvent, + ResponsesAPIStreamEvents.MCP_LIST_TOOLS_COMPLETED: MCPListToolsCompletedEvent, + ResponsesAPIStreamEvents.MCP_LIST_TOOLS_FAILED: MCPListToolsFailedEvent, + ResponsesAPIStreamEvents.MCP_CALL_IN_PROGRESS: MCPCallInProgressEvent, + ResponsesAPIStreamEvents.MCP_CALL_ARGUMENTS_DELTA: MCPCallArgumentsDeltaEvent, + ResponsesAPIStreamEvents.MCP_CALL_ARGUMENTS_DONE: MCPCallArgumentsDoneEvent, + ResponsesAPIStreamEvents.MCP_CALL_COMPLETED: MCPCallCompletedEvent, + ResponsesAPIStreamEvents.MCP_CALL_FAILED: MCPCallFailedEvent, ResponsesAPIStreamEvents.ERROR: ErrorEvent, } diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index 38619112ccc..d0461f91e9e 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -215,9 +215,9 @@ if MCP_AVAILABLE: """ from fastapi import Request + from litellm.exceptions import BlockedPiiEntityError, GuardrailRaisedException from litellm.proxy.litellm_pre_call_utils import add_litellm_data_to_request from litellm.proxy.proxy_server import proxy_config - from litellm.exceptions import BlockedPiiEntityError, GuardrailRaisedException # Validate arguments user_api_key_auth, mcp_auth_header, _, mcp_server_auth_headers, mcp_protocol_version = get_auth_context() @@ -279,33 +279,15 @@ if MCP_AVAILABLE: ############ Helper Functions ########################## ######################################################## - async def _get_tools_from_mcp_servers( - user_api_key_auth: Optional[UserAPIKeyAuth], - mcp_auth_header: Optional[str], + async def _get_allowed_mcp_servers_from_mcp_server_names( mcp_servers: Optional[List[str]], - mcp_server_auth_headers: Optional[Dict[str, str]] = None, - mcp_protocol_version: Optional[str] = None, - ) -> List[MCPTool]: + allowed_mcp_servers: List[str], + ) -> List[str]: """ - Helper method to fetch tools from MCP servers based on server filtering criteria. - - Args: - user_api_key_auth: User authentication info for access control - mcp_auth_header: Optional auth header for MCP server (deprecated) - mcp_servers: Optional list of server names/aliases to filter by - mcp_server_auth_headers: Optional dict of server-specific auth headers {server_alias: auth_value} - - Returns: - List[MCPTool]: Combined list of tools from filtered servers + Get the filtered MCP servers from the MCP server names """ - if not MCP_AVAILABLE: - return [] - - # Get allowed MCP servers based on user permissions - allowed_mcp_servers = await global_mcp_server_manager.get_allowed_mcp_servers(user_api_key_auth) - - filtered_server_ids = set() - + from typing import Set + filtered_server_ids: Set[str] = set() # Filter servers based on mcp_servers parameter if provided if mcp_servers is not None: for server_or_group in mcp_servers: @@ -336,6 +318,40 @@ if MCP_AVAILABLE: if filtered_server_ids: allowed_mcp_servers = list(filtered_server_ids) + + return allowed_mcp_servers + + async def _get_tools_from_mcp_servers( + user_api_key_auth: Optional[UserAPIKeyAuth], + mcp_auth_header: Optional[str], + mcp_servers: Optional[List[str]], + mcp_server_auth_headers: Optional[Dict[str, str]] = None, + mcp_protocol_version: Optional[str] = None, + ) -> List[MCPTool]: + """ + Helper method to fetch tools from MCP servers based on server filtering criteria. + + Args: + user_api_key_auth: User authentication info for access control + mcp_auth_header: Optional auth header for MCP server (deprecated) + mcp_servers: Optional list of server names/aliases to filter by + mcp_server_auth_headers: Optional dict of server-specific auth headers {server_alias: auth_value} + + Returns: + List[MCPTool]: Combined list of tools from filtered servers + """ + if not MCP_AVAILABLE: + return [] + + # Get allowed MCP servers based on user permissions + allowed_mcp_servers = await global_mcp_server_manager.get_allowed_mcp_servers(user_api_key_auth) + + if mcp_servers is not None: + allowed_mcp_servers = await _get_allowed_mcp_servers_from_mcp_server_names( + mcp_servers=mcp_servers, + allowed_mcp_servers=allowed_mcp_servers, + ) + # Get tools from each allowed server all_tools = [] @@ -556,20 +572,25 @@ if MCP_AVAILABLE: except Exception as e: return [TextContent(text=f"Error: {str(e)}", type="text")] - async def extract_mcp_auth_context(scope, path): + def _get_mcp_servers_in_path(path: str) -> Optional[List[str]]: """ - Extracts mcp_servers from the path and processes the MCP request for auth context. - Returns: (user_api_key_auth, mcp_auth_header, mcp_servers, mcp_server_auth_headers) + Get the MCP servers from the path """ import re - - mcp_servers_from_path = None + mcp_servers_from_path: Optional[List[str]] = None mcp_path_match = re.match(r"^/mcp/([^/]+)(/.*)?$", path) if mcp_path_match: mcp_servers_str = mcp_path_match.group(1) if mcp_servers_str: mcp_servers_from_path = [s.strip() for s in mcp_servers_str.split(",") if s.strip()] + return mcp_servers_from_path + async def extract_mcp_auth_context(scope, path): + """ + Extracts mcp_servers from the path and processes the MCP request for auth context. + Returns: (user_api_key_auth, mcp_auth_header, mcp_servers, mcp_server_auth_headers) + """ + mcp_servers_from_path = _get_mcp_servers_in_path(path) if mcp_servers_from_path is not None: ( user_api_key_auth, diff --git a/litellm/proxy/management_endpoints/mcp_management_endpoints.py b/litellm/proxy/management_endpoints/mcp_management_endpoints.py index be265c73bfc..a26ae32229a 100644 --- a/litellm/proxy/management_endpoints/mcp_management_endpoints.py +++ b/litellm/proxy/management_endpoints/mcp_management_endpoints.py @@ -17,8 +17,8 @@ Endpoints here: """ import importlib -from typing import Iterable, List, Optional from datetime import datetime +from typing import Iterable, List, Optional from fastapi import APIRouter, Depends, Header, HTTPException, Response, status from fastapi.responses import JSONResponse @@ -26,7 +26,9 @@ from fastapi.responses import JSONResponse import litellm from litellm._logging import verbose_logger, verbose_proxy_logger from litellm.constants import LITELLM_PROXY_ADMIN_NAME -from litellm.proxy._experimental.mcp_server.utils import validate_and_normalize_mcp_server_payload +from litellm.proxy._experimental.mcp_server.utils import ( + validate_and_normalize_mcp_server_payload, +) router = APIRouter(prefix="/v1/mcp", tags=["mcp"]) MCP_AVAILABLE: bool = True @@ -94,34 +96,17 @@ if MCP_AVAILABLE: """ Get all MCP tools available for the current key, including those from access groups """ - from litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp import ( - MCPRequestHandler, - ) - from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( - global_mcp_server_manager, + from litellm.proxy._experimental.mcp_server.server import _list_mcp_tools + tools = await _list_mcp_tools( + user_api_key_auth=user_api_key_dict, + mcp_auth_header=None, + mcp_servers=None, + mcp_server_auth_headers=None, + mcp_protocol_version=None, ) + dumped_tools = [dict(tool) for tool in tools] - # This now includes both direct and access group servers - server_ids = await MCPRequestHandler._get_allowed_mcp_servers_for_key(user_api_key_dict) - - tools = [] - errors = [] - for server_id in server_ids: - try: - server_tools = await global_mcp_server_manager.get_tools_for_server(server_id) - tools.extend(server_tools) - verbose_proxy_logger.debug(f"Successfully fetched {len(server_tools)} tools from server {server_id}") - except Exception as e: - error_msg = f"Failed to get tools from server {server_id}: {str(e)}" - verbose_proxy_logger.warning(error_msg) - errors.append(error_msg) - # Continue with other servers instead of failing completely - - verbose_proxy_logger.debug(f"Available tools: {tools}") - if errors: - verbose_proxy_logger.warning(f"Some servers failed to respond: {errors}") - - return {"tools": tools} + return {"tools": dumped_tools} @router.get( "/access_groups", @@ -134,8 +119,10 @@ if MCP_AVAILABLE: """ Get all available MCP access groups from the database AND config """ + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + global_mcp_server_manager, + ) from litellm.proxy.proxy_server import prisma_client - from litellm.proxy._experimental.mcp_server.mcp_server_manager import global_mcp_server_manager access_groups = set() diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index 7ee09105254..54bfdac55f9 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -3,5 +3,16 @@ model_list: litellm_params: model: openai/* api_base: https://exampleopenaiendpoint-production-0ee2.up.railway.app/ + - model_name: bedrock/* + litellm_params: + model: bedrock/* + - model_name: openai/* + litellm_params: + model: openai/* + - model_name: gemini/* + litellm_params: + model: gemini/* + + litellm_settings: callbacks: ["cloudzero"] \ No newline at end of file diff --git a/litellm/responses/main.py b/litellm/responses/main.py index 47ecbcf02c0..04ee2b343f5 100644 --- a/litellm/responses/main.py +++ b/litellm/responses/main.py @@ -1,12 +1,24 @@ import asyncio import contextvars from functools import partial -from typing import Any, Coroutine, Dict, Iterable, List, Literal, Optional, Type, Union +from typing import ( + TYPE_CHECKING, + Any, + Coroutine, + Dict, + Iterable, + List, + Literal, + Optional, + Type, + Union, +) import httpx from pydantic import BaseModel import litellm +from litellm._logging import verbose_logger from litellm.constants import request_timeout from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj from litellm.llms.base_llm.responses.transformation import BaseResponsesAPIConfig @@ -22,15 +34,27 @@ from litellm.types.llms.openai import ( ResponseInputParam, ResponsesAPIOptionalRequestParams, ResponsesAPIResponse, - ResponseText, ToolChoice, ToolParam, ) + +# Handle ResponseText import with fallback +if TYPE_CHECKING: + from litellm.types.llms.openai import ResponseText +else: + ResponseText = str # Fallback for ResponseText import from litellm.types.responses.main import * from litellm.types.router import GenericLiteLLMParams from litellm.utils import ProviderConfigManager, client -from .streaming_iterator import BaseResponsesAPIStreamingIterator +if TYPE_CHECKING: + from mcp.types import Tool as MCPTool +else: + MCPTool = Any + +from .streaming_iterator import ( + BaseResponsesAPIStreamingIterator, +) ####### ENVIRONMENT VARIABLES ################### # Initialize any necessary instances or variables here @@ -141,17 +165,15 @@ async def aresponses_api_with_mcp( other_tools, ) = LiteLLM_Proxy_MCP_Handler._parse_mcp_tools(tools) - # Get available tools from MCP manager if we have MCP tools - openai_tools = [] - mcp_tools_fetched = [] - if mcp_tools_with_litellm_proxy: - user_api_key_auth = kwargs.get("user_api_key_auth") - mcp_tools_fetched = await LiteLLM_Proxy_MCP_Handler._get_mcp_tools_from_manager( - user_api_key_auth - ) - openai_tools = LiteLLM_Proxy_MCP_Handler._transform_mcp_tools_to_openai( - mcp_tools_fetched - ) + # Process MCP tools through the complete pipeline (fetch + filter + deduplicate + transform) + user_api_key_auth = kwargs.get("user_api_key_auth") + + # Get original MCP tools (for events) and OpenAI tools (for LLM) by reusing existing methods + original_mcp_tools = await LiteLLM_Proxy_MCP_Handler._process_mcp_tools_without_openai_transform( + user_api_key_auth=user_api_key_auth, + mcp_tools_with_litellm_proxy=mcp_tools_with_litellm_proxy + ) + openai_tools = LiteLLM_Proxy_MCP_Handler._transform_mcp_tools_to_openai(original_mcp_tools) # Combine with other tools all_tools = openai_tools + other_tools if (openai_tools or other_tools) else None @@ -182,23 +204,68 @@ async def aresponses_api_with_mcp( **kwargs, } + # Handle MCP streaming if requested + if stream and mcp_tools_with_litellm_proxy: + # Generate MCP discovery events using the already processed tools + import uuid + + from litellm.responses.mcp.mcp_streaming_iterator import ( + create_mcp_list_tools_events, + ) + + base_item_id = f"mcp_{uuid.uuid4().hex[:8]}" + mcp_discovery_events = await create_mcp_list_tools_events( + mcp_tools_with_litellm_proxy=mcp_tools_with_litellm_proxy, + user_api_key_auth=user_api_key_auth, + base_item_id=base_item_id, + pre_processed_mcp_tools=original_mcp_tools + ) + + return LiteLLM_Proxy_MCP_Handler._create_mcp_streaming_response( + input=input, + model=model, + all_tools=all_tools, + mcp_tools_with_litellm_proxy=mcp_tools_with_litellm_proxy, + mcp_discovery_events=mcp_discovery_events, + call_params=call_params, + previous_response_id=previous_response_id, + **kwargs + ) + + # Determine if we should auto-execute tools + should_auto_execute = ( + bool(mcp_tools_with_litellm_proxy) + and LiteLLM_Proxy_MCP_Handler._should_auto_execute_tools( + mcp_tools_with_litellm_proxy=mcp_tools_with_litellm_proxy + ) + ) + + # Prepare parameters for the initial call + initial_call_params = LiteLLM_Proxy_MCP_Handler._prepare_initial_call_params( + call_params=call_params, + should_auto_execute=should_auto_execute + ) + + ######################################################### # Make initial response API call - # TODO: if should auto-execute is True, then this first response should not be streamed + ######################################################### response = await aresponses( input=input, model=model, tools=all_tools, previous_response_id=previous_response_id, - **call_params, + **initial_call_params, ) - # Check if we need to auto-execute tool calls (only for non-streaming responses) + verbose_logger.debug("Initial response %s", response) + + ######################################################### + # Auto-Execute Tools Handling + # If auto-execute tools is True, then we need to execute the tool calls + ######################################################### if ( - mcp_tools_with_litellm_proxy + should_auto_execute and isinstance(response, ResponsesAPIResponse) - and LiteLLM_Proxy_MCP_Handler._should_auto_execute_tools( - mcp_tools_with_litellm_proxy=mcp_tools_with_litellm_proxy - ) ): # type: ignore tool_calls = LiteLLM_Proxy_MCP_Handler._extract_tool_calls_from_response( response=response @@ -217,20 +284,49 @@ async def aresponses_api_with_mcp( response=response, tool_results=tool_results, original_input=input ) + # Prepare parameters for follow-up call (restores original stream setting) + follow_up_call_params = LiteLLM_Proxy_MCP_Handler._prepare_follow_up_call_params( + call_params=call_params, + original_stream_setting=stream or False + ) + + # Create tool execution events for streaming if needed + tool_execution_events = [] + if stream: + tool_execution_events = LiteLLM_Proxy_MCP_Handler._create_tool_execution_events( + tool_calls=tool_calls, + tool_results=tool_results + ) + final_response = await LiteLLM_Proxy_MCP_Handler._make_follow_up_call( follow_up_input=follow_up_input, model=model, all_tools=all_tools, response_id=response.id, - **call_params, + **follow_up_call_params, ) - # Add custom output elements to the final response - if isinstance(final_response, ResponsesAPIResponse): + # If streaming and we have tool execution events, wrap the response + if stream and tool_execution_events and (hasattr(final_response, '__aiter__') or hasattr(final_response, '__iter__')): + from litellm.responses.mcp.mcp_streaming_iterator import ( + MCPEnhancedStreamingIterator, + ) + final_response = MCPEnhancedStreamingIterator( + base_iterator=final_response, + mcp_events=tool_execution_events + ) + + # Add custom output elements to the final response (for non-streaming) + elif isinstance(final_response, ResponsesAPIResponse): + # Fetch MCP tools again for output elements (without OpenAI transformation) + mcp_tools_for_output = await LiteLLM_Proxy_MCP_Handler._process_mcp_tools_without_openai_transform( + user_api_key_auth=user_api_key_auth, + mcp_tools_with_litellm_proxy=mcp_tools_with_litellm_proxy + ) final_response = ( LiteLLM_Proxy_MCP_Handler._add_mcp_output_elements_to_response( response=final_response, - mcp_tools_fetched=mcp_tools_fetched, + mcp_tools_fetched=mcp_tools_for_output, tool_results=tool_results, ) ) @@ -401,13 +497,13 @@ def responses( Synchronous version of the Responses API. Uses the synchronous HTTP handler to make requests. """ + local_vars = locals() from litellm.responses.mcp.litellm_proxy_mcp_handler import ( LiteLLM_Proxy_MCP_Handler, ) - local_vars = locals() try: - litellm_logging_obj: LiteLLMLoggingObj = kwargs.get("litellm_logging_obj") # type: ignore + litellm_logging_obj: LiteLLMLoggingObj = kwargs.pop("litellm_logging_obj") # type: ignore litellm_call_id: Optional[str] = kwargs.get("litellm_call_id", None) _is_async = kwargs.pop("aresponses", False) is True @@ -448,7 +544,32 @@ def responses( ######################################################### if LiteLLM_Proxy_MCP_Handler._should_use_litellm_mcp_gateway(tools=tools): return aresponses_api_with_mcp( - **local_vars, + input=input, + model=model, + include=include, + instructions=instructions, + max_output_tokens=max_output_tokens, + prompt=prompt, + metadata=metadata, + parallel_tool_calls=parallel_tool_calls, + previous_response_id=previous_response_id, + reasoning=reasoning, + store=store, + background=background, + stream=stream, + temperature=temperature, + text=text, + tool_choice=tool_choice, + tools=tools, + top_p=top_p, + truncation=truncation, + user=user, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + custom_llm_provider=custom_llm_provider, + **kwargs, ) # get provider config diff --git a/litellm/responses/mcp/litellm_proxy_mcp_handler.py b/litellm/responses/mcp/litellm_proxy_mcp_handler.py index 5c72b9b6521..7a9a21a9690 100644 --- a/litellm/responses/mcp/litellm_proxy_mcp_handler.py +++ b/litellm/responses/mcp/litellm_proxy_mcp_handler.py @@ -1,10 +1,17 @@ -from typing import Any, Dict, Iterable, List, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Tuple, Union from litellm._logging import verbose_logger from litellm.responses.main import aresponses from litellm.responses.streaming_iterator import BaseResponsesAPIStreamingIterator from litellm.types.llms.openai import ResponsesAPIResponse, ToolParam +if TYPE_CHECKING: + from mcp.types import Tool as MCPTool +else: + MCPTool = Any + +LITELLM_PROXY_MCP_SERVER_URL = "litellm_proxy" +LITELLM_PROXY_MCP_SERVER_URL_PREFIX = f"{LITELLM_PROXY_MCP_SERVER_URL}/mcp/" class LiteLLM_Proxy_MCP_Handler: """ @@ -22,7 +29,7 @@ class LiteLLM_Proxy_MCP_Handler: for tool in tools: if (isinstance(tool, dict) and tool.get("type") == "mcp" and - tool.get("server_url") == "litellm_proxy"): + tool.get("server_url", "").startswith(LITELLM_PROXY_MCP_SERVER_URL)): return True return False @@ -41,7 +48,7 @@ class LiteLLM_Proxy_MCP_Handler: for tool in tools: if (isinstance(tool, dict) and tool.get("type") == "mcp" and - tool.get("server_url") == "litellm_proxy"): + tool.get("server_url", "").startswith(LITELLM_PROXY_MCP_SERVER_URL)): mcp_tools_with_litellm_proxy.append(tool) else: other_tools.append(tool) @@ -49,13 +56,167 @@ class LiteLLM_Proxy_MCP_Handler: return mcp_tools_with_litellm_proxy, other_tools @staticmethod - async def _get_mcp_tools_from_manager(user_api_key_auth: Any) -> List[Any]: - """Get available tools from the MCP server manager.""" - from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( - global_mcp_server_manager, + async def _get_mcp_tools_from_manager( + user_api_key_auth: Any, + mcp_tools_with_litellm_proxy: Optional[Iterable[ToolParam]], + ) -> List[MCPTool]: + """ + Get available tools from the MCP server manager. + + Args: + user_api_key_auth: User authentication info for access control + mcp_tools_with_litellm_proxy: ToolParam objects with server_url starting with "litellm_proxy" + """ + from litellm.proxy._experimental.mcp_server.server import ( + _get_tools_from_mcp_servers, + ) + mcp_servers: List[str] = [] + if mcp_tools_with_litellm_proxy: + for _tool in mcp_tools_with_litellm_proxy: + # if user specifies servers as server_url: litellm_proxy/mcp/zapier,github then return zapier,github + if _tool.get("server_url", "").startswith(LITELLM_PROXY_MCP_SERVER_URL_PREFIX): + mcp_servers.append(_tool.get("server_url", "").split("/")[-1]) + + return await _get_tools_from_mcp_servers( + user_api_key_auth=user_api_key_auth, + mcp_auth_header=None, + mcp_servers=mcp_servers, + mcp_server_auth_headers=None, + mcp_protocol_version=None, + ) + + @staticmethod + def _deduplicate_mcp_tools(mcp_tools: List[Any]) -> List[Any]: + """ + Deduplicate MCP tools by name, keeping the first occurrence of each tool. + + Args: + mcp_tools: List of MCP tools that may contain duplicates + + Returns: + List of deduplicated MCP tools + """ + seen_names = set() + deduplicated_tools = [] + + for tool in mcp_tools: + tool_name = getattr(tool, 'name', None) if hasattr(tool, 'name') else tool.get('name') if isinstance(tool, dict) else None + if tool_name and tool_name not in seen_names: + seen_names.add(tool_name) + deduplicated_tools.append(tool) + + return deduplicated_tools + + @staticmethod + def _filter_mcp_tools_by_allowed_tools( + mcp_tools: List[Any], + mcp_tools_with_litellm_proxy: List[ToolParam] + ) -> List[Any]: + """Filter MCP tools based on allowed_tools parameter from the original tool configs.""" + # Collect all allowed tool names from all MCP tool configs + allowed_tool_names = set() + for tool_config in mcp_tools_with_litellm_proxy: + if isinstance(tool_config, dict) and "allowed_tools" in tool_config: + allowed_tools = tool_config.get("allowed_tools", []) + if isinstance(allowed_tools, list): + allowed_tool_names.update(allowed_tools) + + # If no allowed_tools specified, return all tools + if not allowed_tool_names: + return mcp_tools + + # Filter tools based on allowed names + filtered_tools = [] + for mcp_tool in mcp_tools: + tool_name = getattr(mcp_tool, 'name', None) if hasattr(mcp_tool, 'name') else mcp_tool.get('name') if isinstance(mcp_tool, dict) else None + if tool_name and tool_name in allowed_tool_names: + filtered_tools.append(mcp_tool) + + return filtered_tools + + @staticmethod + async def _process_mcp_tools_to_openai_format( + user_api_key_auth: Any, + mcp_tools_with_litellm_proxy: List[ToolParam] + ) -> List[Any]: + """ + Centralized method to process MCP tools through the complete pipeline: + 1. Fetch tools from MCP manager + 2. Filter based on allowed_tools parameter + 3. Deduplicate tools by name + 4. Transform to OpenAI format + + Args: + user_api_key_auth: User authentication info for access control + mcp_tools_with_litellm_proxy: ToolParam objects with server_url starting with "litellm_proxy" + + Returns: + List of tools in OpenAI format ready to be sent to the LLM + """ + if not mcp_tools_with_litellm_proxy: + return [] + + # Step 1: Fetch MCP tools from manager + mcp_tools_fetched = await LiteLLM_Proxy_MCP_Handler._get_mcp_tools_from_manager( + user_api_key_auth=user_api_key_auth, + mcp_tools_with_litellm_proxy=mcp_tools_with_litellm_proxy, ) - return await global_mcp_server_manager.list_tools(user_api_key_auth=user_api_key_auth) + # Step 2: Filter tools based on allowed_tools parameter + filtered_mcp_tools = LiteLLM_Proxy_MCP_Handler._filter_mcp_tools_by_allowed_tools( + mcp_tools=mcp_tools_fetched, + mcp_tools_with_litellm_proxy=mcp_tools_with_litellm_proxy, + ) + + # Step 3: Deduplicate tools after filtering + deduplicated_mcp_tools = LiteLLM_Proxy_MCP_Handler._deduplicate_mcp_tools( + filtered_mcp_tools + ) + + # Step 4: Transform to OpenAI format + openai_tools = LiteLLM_Proxy_MCP_Handler._transform_mcp_tools_to_openai( + deduplicated_mcp_tools + ) + + return openai_tools + + @staticmethod + async def _process_mcp_tools_without_openai_transform( + user_api_key_auth: Any, + mcp_tools_with_litellm_proxy: List[ToolParam] + ) -> List[Any]: + """ + Process MCP tools through filtering and deduplication pipeline without OpenAI transformation. + This is useful for cases where we need the original MCP tool objects (e.g., for events). + + Args: + user_api_key_auth: User authentication info for access control + mcp_tools_with_litellm_proxy: ToolParam objects with server_url starting with "litellm_proxy" + + Returns: + List of filtered and deduplicated MCP tools in their original format + """ + if not mcp_tools_with_litellm_proxy: + return [] + + # Step 1: Fetch MCP tools from manager + mcp_tools_fetched = await LiteLLM_Proxy_MCP_Handler._get_mcp_tools_from_manager( + user_api_key_auth=user_api_key_auth, + mcp_tools_with_litellm_proxy=mcp_tools_with_litellm_proxy, + ) + + # Step 2: Filter tools based on allowed_tools parameter + filtered_mcp_tools = LiteLLM_Proxy_MCP_Handler._filter_mcp_tools_by_allowed_tools( + mcp_tools=mcp_tools_fetched, + mcp_tools_with_litellm_proxy=mcp_tools_with_litellm_proxy, + ) + + # Step 3: Deduplicate tools after filtering + deduplicated_mcp_tools = LiteLLM_Proxy_MCP_Handler._deduplicate_mcp_tools( + filtered_mcp_tools + ) + + return deduplicated_mcp_tools @staticmethod def _transform_mcp_tools_to_openai(mcp_tools: List[Any]) -> List[Any]: @@ -178,11 +339,12 @@ class LiteLLM_Proxy_MCP_Handler: user_api_key_auth: Any ) -> List[Dict[str, Any]]: """Execute tool calls and return results.""" + from fastapi import HTTPException + + from litellm.exceptions import BlockedPiiEntityError, GuardrailRaisedException from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( global_mcp_server_manager, ) - from litellm.exceptions import BlockedPiiEntityError, GuardrailRaisedException - from fastapi import HTTPException tool_results = [] tool_call_id: Optional[str] = None @@ -331,6 +493,170 @@ class LiteLLM_Proxy_MCP_Handler: **call_params ) + @staticmethod + def _create_mcp_streaming_response( + input: Union[str, Any], + model: str, + all_tools: Optional[List[Any]], + mcp_tools_with_litellm_proxy: List[Any], + mcp_discovery_events: List[Any], + call_params: Dict[str, Any], + previous_response_id: Optional[str], + **kwargs + ) -> Any: + """ + Create MCP enhanced streaming response that handles the full MCP workflow. + + This creates a streaming iterator that: + 1. Immediately emits MCP discovery events + 2. Makes the LLM call and streams the response + 3. Handles tool execution and follow-up calls + """ + from litellm.responses.mcp.mcp_streaming_iterator import ( + MCPEnhancedStreamingIterator, + ) + + # Build the complete request parameters by merging all sources + request_params = LiteLLM_Proxy_MCP_Handler._build_request_params( + input=input, + model=model, + all_tools=all_tools, + call_params=call_params, + previous_response_id=previous_response_id, + **kwargs + ) + + # Create the enhanced streaming iterator that will handle everything + return MCPEnhancedStreamingIterator( + base_iterator=None, # Will be created internally + mcp_events=mcp_discovery_events, # Pre-generated MCP discovery events + mcp_tools_with_litellm_proxy=mcp_tools_with_litellm_proxy, + user_api_key_auth=kwargs.get("user_api_key_auth"), + original_request_params=request_params + ) + + @staticmethod + def _build_request_params( + input: Union[str, Any], + model: str, + all_tools: Optional[List[Any]], + call_params: Dict[str, Any], + previous_response_id: Optional[str], + **kwargs + ) -> Dict[str, Any]: + """ + Build a clean request parameters dictionary for MCP streaming. + + Combines input, model, tools with call_params and additional kwargs + in a clean, maintainable way. + """ + # Start with the core required parameters + request_params = { + 'input': input, + 'model': model, + 'tools': all_tools, + } + + # Add previous_response_id if provided + if previous_response_id is not None: + request_params['previous_response_id'] = previous_response_id + + # Merge in all call_params (which contains most of the API parameters) + request_params.update(call_params) + + # Merge in any additional kwargs + request_params.update(kwargs) + + return request_params + + @staticmethod + def _create_tool_execution_events( + tool_calls: List[Any], + tool_results: List[Dict[str, Any]] + ) -> List[Any]: + """ + Create MCP tool execution events for streaming. + + Args: + tool_calls: List of tool calls from the LLM response + tool_results: List of tool execution results + + Returns: + List of MCP tool execution events for streaming + """ + import uuid + + from litellm.responses.mcp.mcp_streaming_iterator import create_mcp_call_events + + tool_execution_events = [] + + # Create events for each tool execution + for tool_result in tool_results: + tool_call_id = tool_result.get("tool_call_id", "unknown") + result_text = tool_result.get("result", "") + + # Extract tool name and arguments from tool calls + tool_name = "unknown" + tool_arguments = "{}" + for tool_call in tool_calls: + name, args, call_id = LiteLLM_Proxy_MCP_Handler._extract_tool_call_details(tool_call) + if call_id == tool_call_id: + tool_name = name or "unknown" + tool_arguments = args or "{}" + break + + execution_events = create_mcp_call_events( + tool_name=tool_name, + tool_call_id=tool_call_id, + arguments=tool_arguments, # Use actual arguments + result=result_text, + base_item_id=f"mcp_{uuid.uuid4().hex[:8]}", # Unique ID for each tool call + sequence_start=len(tool_execution_events) + 1 + ) + tool_execution_events.extend(execution_events) + + return tool_execution_events + + @staticmethod + def _prepare_initial_call_params( + call_params: Dict[str, Any], + should_auto_execute: bool + ) -> Dict[str, Any]: + """ + Prepare call parameters for the initial LLM call. + + For auto-execute scenarios, we need to disable streaming for the initial call + so we can process the tool calls before streaming the final response. + """ + initial_params = call_params.copy() + + if should_auto_execute: + # Disable streaming for initial call when auto-executing tools + initial_params["stream"] = False + + return initial_params + + @staticmethod + def _prepare_follow_up_call_params( + call_params: Dict[str, Any], + original_stream_setting: bool + ) -> Dict[str, Any]: + """ + Prepare call parameters for the follow-up LLM call after tool execution. + + Restores the original streaming setting and removes tool_choice since + we're now providing tool results, not requesting tool calls. + """ + follow_up_params = call_params.copy() + + # Restore original streaming setting for follow-up call + follow_up_params["stream"] = original_stream_setting + + # Remove tool_choice since we're providing results, not requesting tool calls + follow_up_params.pop("tool_choice", None) + + return follow_up_params + @staticmethod def _add_mcp_output_elements_to_response( response: ResponsesAPIResponse, diff --git a/litellm/responses/mcp/mcp_streaming_iterator.py b/litellm/responses/mcp/mcp_streaming_iterator.py new file mode 100644 index 00000000000..bf6a9182522 --- /dev/null +++ b/litellm/responses/mcp/mcp_streaming_iterator.py @@ -0,0 +1,604 @@ +import uuid +from typing import ( + TYPE_CHECKING, + Any, + Dict, + List, + Optional, + Union, + cast, +) + +from litellm._logging import verbose_logger +from litellm.responses.streaming_iterator import ( + BaseResponsesAPIStreamingIterator, +) +from litellm.types.llms.openai import ( + MCPCallArgumentsDeltaEvent, + MCPCallArgumentsDoneEvent, + MCPCallCompletedEvent, + MCPCallFailedEvent, + MCPCallInProgressEvent, + MCPListToolsCompletedEvent, + MCPListToolsFailedEvent, + MCPListToolsInProgressEvent, + ResponsesAPIResponse, + ResponsesAPIStreamEvents, + ResponsesAPIStreamingResponse, + ToolParam, +) + +if TYPE_CHECKING: + from mcp.types import Tool as MCPTool +else: + MCPTool = Any + + +async def create_mcp_list_tools_events( + mcp_tools_with_litellm_proxy: List[ToolParam], + user_api_key_auth: Any, + base_item_id: str, + pre_processed_mcp_tools: List[Any] +) -> List[ResponsesAPIStreamingResponse]: + """Create MCP discovery events using pre-processed tools from the parent""" + from litellm.responses.mcp.litellm_proxy_mcp_handler import ( + LiteLLM_Proxy_MCP_Handler, + ) + + events = [] + + try: + # Extract MCP server names + mcp_servers = [] + for tool in mcp_tools_with_litellm_proxy: + if isinstance(tool, dict) and "server_url" in tool: + server_url = tool["server_url"] + if server_url.startswith("litellm_proxy/mcp/"): + server_name = server_url.split("/")[-1] + mcp_servers.append(server_name) + + # Emit list tools in progress event + in_progress_event = MCPListToolsInProgressEvent( + type=ResponsesAPIStreamEvents.MCP_LIST_TOOLS_IN_PROGRESS, + sequence_number=1, + output_index=0, + item_id=base_item_id, + ) + events.append(in_progress_event) + + # Use the pre-processed MCP tools that were already fetched, filtered, and deduplicated by the parent + filtered_mcp_tools = pre_processed_mcp_tools + + # Convert tools to dict format for the event + mcp_tools_dict = [] + for tool in filtered_mcp_tools: + if hasattr(tool, 'model_dump'): + mcp_tools_dict.append(tool.model_dump()) + elif hasattr(tool, '__dict__'): + mcp_tools_dict.append(tool.__dict__) + else: + mcp_tools_dict.append({"name": getattr(tool, 'name', str(tool))}) + + # Emit list tools completed event + completed_event = MCPListToolsCompletedEvent( + type=ResponsesAPIStreamEvents.MCP_LIST_TOOLS_COMPLETED, + sequence_number=2, + output_index=0, + item_id=base_item_id, + ) + events.append(completed_event) + + # Add output_item.done event with the actual tools list (matching OpenAI format) + from litellm.types.llms.openai import OutputItemDoneEvent + + # Extract server label from the first MCP tool config + server_label = "" + if mcp_tools_with_litellm_proxy: + first_tool = mcp_tools_with_litellm_proxy[0] + if isinstance(first_tool, dict): + server_label = first_tool.get("server_label", "") + + # Format tools for OpenAI output_item.done format + formatted_tools = [] + for tool in filtered_mcp_tools: + tool_dict = { + "name": getattr(tool, 'name', 'unknown'), + "description": getattr(tool, 'description', ''), + "annotations": {"read_only": False}, + } + + # Add input_schema if available + if hasattr(tool, 'inputSchema'): + tool_dict["input_schema"] = tool.inputSchema + elif hasattr(tool, 'input_schema'): + tool_dict["input_schema"] = tool.input_schema + + formatted_tools.append(tool_dict) + + # Create the output_item.done event with MCP tools list + output_item_done_event = OutputItemDoneEvent( + type=ResponsesAPIStreamEvents.OUTPUT_ITEM_DONE, + output_index=0, + item={ + "id": base_item_id, + "type": "mcp_list_tools", + "server_label": server_label, + "tools": formatted_tools + } + ) + events.append(output_item_done_event) + + verbose_logger.debug(f"Created {len(events)} MCP discovery events") + + except Exception as e: + verbose_logger.error(f"Error creating MCP list tools events: {e}") + import traceback + traceback.print_exc() + + # Emit failed event on error + failed_event = MCPListToolsFailedEvent( + type=ResponsesAPIStreamEvents.MCP_LIST_TOOLS_FAILED, + sequence_number=2, + output_index=0, + item_id=base_item_id, + ) + events.append(failed_event) + + # Still emit output_item.done event even on failure (with empty tools list) + from litellm.types.llms.openai import OutputItemDoneEvent + + output_item_done_event = OutputItemDoneEvent( + type=ResponsesAPIStreamEvents.OUTPUT_ITEM_DONE, + output_index=0, + item={ + "id": base_item_id, + "type": "mcp_list_tools", + "server_label": "", + "tools": [] + } + ) + events.append(output_item_done_event) + + return events + + +def create_mcp_call_events( + tool_name: str, + tool_call_id: str, + arguments: str, + result: Optional[str] = None, + base_item_id: Optional[str] = None, + sequence_start: int = 1 +) -> List[ResponsesAPIStreamingResponse]: + """Create MCP call events following OpenAI's specification""" + events = [] + item_id = base_item_id or f"mcp_{uuid.uuid4().hex[:8]}" + + # MCP call in progress event + in_progress_event = MCPCallInProgressEvent( + type=ResponsesAPIStreamEvents.MCP_CALL_IN_PROGRESS, + sequence_number=sequence_start, + output_index=0, + item_id=item_id, + ) + events.append(in_progress_event) + + # MCP call arguments delta event (streaming the arguments) + arguments_delta_event = MCPCallArgumentsDeltaEvent( + type=ResponsesAPIStreamEvents.MCP_CALL_ARGUMENTS_DELTA, + output_index=0, + item_id=item_id, + delta=arguments, # JSON string with arguments + sequence_number=sequence_start + 1, + ) + events.append(arguments_delta_event) + + # MCP call arguments done event + arguments_done_event = MCPCallArgumentsDoneEvent( + type=ResponsesAPIStreamEvents.MCP_CALL_ARGUMENTS_DONE, + output_index=0, + item_id=item_id, + arguments=arguments, # Complete JSON string with finalized arguments + sequence_number=sequence_start + 2, + ) + events.append(arguments_done_event) + + # MCP call completed event (or failed if result indicates failure) + if result is not None: + completed_event = MCPCallCompletedEvent( + type=ResponsesAPIStreamEvents.MCP_CALL_COMPLETED, + sequence_number=sequence_start + 3, + item_id=item_id, + output_index=0, + ) + events.append(completed_event) + + # Add output_item.done event with the tool call result + from litellm.types.llms.openai import OutputItemDoneEvent + + output_item_done_event = OutputItemDoneEvent( + type=ResponsesAPIStreamEvents.OUTPUT_ITEM_DONE, + output_index=0, + item={ + "id": item_id, + "type": "mcp_call", + "approval_request_id": f"mcpr_{uuid.uuid4().hex[:8]}", + "arguments": arguments, + "error": None, + "name": tool_name, + "output": result, + "server_label": "litellm" + }, + ) + events.append(output_item_done_event) + else: + failed_event = MCPCallFailedEvent( + type=ResponsesAPIStreamEvents.MCP_CALL_FAILED, + sequence_number=sequence_start + 3, + item_id=item_id, + output_index=0, + ) + events.append(failed_event) + + return events + + +class MCPEnhancedStreamingIterator(BaseResponsesAPIStreamingIterator): + """ + A complete MCP streaming iterator that handles the entire flow: + 1. Immediately emits MCP discovery events + 2. Makes the first LLM call and streams its response + 3. Handles tool execution and follow-up calls for auto-execute tools + 4. Emits tool execution events in the stream + """ + + def __init__( + self, + base_iterator: Any, # Can be None - will be created internally + mcp_events: List[ResponsesAPIStreamingResponse], + mcp_tools_with_litellm_proxy: Optional[List[Any]] = None, + user_api_key_auth: Any = None, + original_request_params: Optional[Dict[str, Any]] = None + ): + # MCP setup + self.mcp_tools_with_litellm_proxy = mcp_tools_with_litellm_proxy or [] + self.user_api_key_auth = user_api_key_auth + self.original_request_params = original_request_params or {} + self.should_auto_execute = self._should_auto_execute_tools() + + # Streaming state management + self.phase = "mcp_discovery" # mcp_discovery -> initial_response -> tool_execution -> follow_up_response -> finished + self.finished = False + + # Event queues and generation flags + self.mcp_discovery_events: List[ResponsesAPIStreamingResponse] = mcp_events # Pre-generated MCP discovery events + self.tool_execution_events: List[ResponsesAPIStreamingResponse] = [] + self.mcp_discovery_generated = True # Events are already generated + self.mcp_events = mcp_events # Store the initial MCP events for backward compatibility + + # Iterator references + self.base_iterator: Optional[Union[Any, ResponsesAPIResponse]] = base_iterator # Will be created when needed + self.follow_up_iterator: Optional[Any] = None + + # Response collection for tool execution + self.collected_response: Optional[ResponsesAPIResponse] = None + + # Set up model metadata (will be updated when we get the real iterator) + self.model = self.original_request_params.get('model', 'unknown') + self.litellm_metadata = {} + self.custom_llm_provider = self.original_request_params.get('custom_llm_provider', None) + + # Mark as async iterator + self.is_async = True + + def _should_auto_execute_tools(self) -> bool: + """Check if tools should be auto-executed""" + from litellm.responses.mcp.litellm_proxy_mcp_handler import ( + LiteLLM_Proxy_MCP_Handler, + ) + return LiteLLM_Proxy_MCP_Handler._should_auto_execute_tools( + self.mcp_tools_with_litellm_proxy + ) + + def __aiter__(self): + return self + + async def __anext__(self) -> ResponsesAPIStreamingResponse: + """ + Phase-based streaming: + 1. mcp_discovery - Emit MCP discovery events + 2. initial_response - Stream the first LLM response + 3. tool_execution - Emit tool execution events + 4. follow_up_response - Stream the follow-up response + 5. finished - End iteration + """ + + # Phase 1: MCP Discovery Events + if self.phase == "mcp_discovery": + # Generate MCP discovery events if not already done + # MCP discovery events are already generated and available + + # Emit MCP discovery events + if self.mcp_discovery_events: + return self.mcp_discovery_events.pop(0) + + # All MCP discovery events emitted, move to next phase + verbose_logger.debug("MCP discovery phase complete, transitioning to initial_response") + self.phase = "initial_response" + await self._create_initial_response_iterator() + # Fall through to process the initial response immediately + + # Phase 2: Initial Response Stream + if self.phase == "initial_response": + if self.base_iterator: + # Check if base_iterator is actually iterable + if hasattr(self.base_iterator, '__anext__'): + try: + chunk = await cast(Any, self.base_iterator).__anext__() # type: ignore[attr-defined] + + # If auto-execution is enabled, check for completed responses + if self.should_auto_execute and self._is_response_completed(chunk): + # Collect the response for tool execution + response_obj = getattr(chunk, 'response', None) + if isinstance(response_obj, ResponsesAPIResponse): + self.collected_response = response_obj + # Move to tool execution phase after emitting this chunk + self.phase = "tool_execution" + await self._generate_tool_execution_events() + + return chunk + except StopAsyncIteration: + # Initial response ended, move to next phase + if self.should_auto_execute and self.collected_response: + self.phase = "tool_execution" + await self._generate_tool_execution_events() + else: + self.phase = "finished" + raise + else: + # base_iterator is not async iterable (likely a ResponsesAPIResponse) + # Collect it for tool execution if needed + if self.should_auto_execute and isinstance(self.base_iterator, ResponsesAPIResponse): + self.collected_response = self.base_iterator + self.phase = "tool_execution" + await self._generate_tool_execution_events() + else: + self.phase = "finished" + raise StopAsyncIteration + + # Phase 3: Tool Execution Events + if self.phase == "tool_execution": + # Emit any queued tool execution events + if self.tool_execution_events: + return self.tool_execution_events.pop(0) + + # Move to follow-up response phase + self.phase = "follow_up_response" + await self._create_follow_up_iterator() + + # Phase 4: Follow-up Response Stream + if self.phase == "follow_up_response": + if self.follow_up_iterator: + try: + return await cast(Any, self.follow_up_iterator).__anext__() # type: ignore[attr-defined] + except StopAsyncIteration: + self.phase = "finished" + raise + else: + self.phase = "finished" + raise StopAsyncIteration + + # Phase 5: Finished + if self.phase == "finished": + raise StopAsyncIteration + + # Should not reach here + raise StopAsyncIteration + + def _is_response_completed(self, chunk: ResponsesAPIStreamingResponse) -> bool: + """Check if this chunk indicates the response is completed""" + from litellm.types.llms.openai import ResponsesAPIStreamEvents + return getattr(chunk, 'type', None) == ResponsesAPIStreamEvents.RESPONSE_COMPLETED + + + async def _create_initial_response_iterator(self) -> None: + """Create the initial response iterator by making the first LLM call""" + try: + # Import the core aresponses function that doesn't have MCP logic + from litellm.responses.main import aresponses + + # Make the initial response API call - but avoid the MCP wrapper + params = self.original_request_params.copy() + params['stream'] = True # Ensure streaming + + # Use the pre-fetched all_tools from original_request_params (no re-processing needed) + params_for_llm = {} + for key, value in params.items(): + params_for_llm[key] = value # Copy all params as-is since tools are already processed + + tools_count = len(params_for_llm.get('tools', [])) + verbose_logger.debug(f"Making LLM call with {tools_count} tools") + response = await aresponses(**params_for_llm) + + # Set the base iterator + if hasattr(response, '__aiter__') or hasattr(response, '__iter__'): + self.base_iterator = response + # Copy metadata from the real iterator + self.model = getattr(response, 'model', self.model) + self.litellm_metadata = getattr(response, 'litellm_metadata', {}) + self.custom_llm_provider = getattr(response, 'custom_llm_provider', self.custom_llm_provider) + verbose_logger.debug(f"Created base iterator: {type(self.base_iterator)}") + else: + # Non-streaming response - this shouldn't happen but handle it + verbose_logger.warning(f"Got non-streaming response: {type(response)}") + self.base_iterator = None + self.phase = "finished" + + except Exception as e: + verbose_logger.error(f"Error creating initial response iterator: {e}") + import traceback + traceback.print_exc() + self.base_iterator = None + self.phase = "finished" + + async def _generate_tool_execution_events(self) -> None: + """Generate tool execution events and execute tools""" + if not self.collected_response: + return + + import uuid + + from litellm.responses.mcp.litellm_proxy_mcp_handler import ( + LiteLLM_Proxy_MCP_Handler, + ) + + try: + # Extract tool calls from the response + if self.collected_response is not None: + tool_calls = LiteLLM_Proxy_MCP_Handler._extract_tool_calls_from_response(self.collected_response) # type: ignore[arg-type] + else: + tool_calls = [] + if not tool_calls: + return + + # Create tool execution events + base_item_id = f"mcp_{uuid.uuid4().hex[:8]}" + for tool_call in tool_calls: + tool_name, tool_arguments, tool_call_id = LiteLLM_Proxy_MCP_Handler._extract_tool_call_details(tool_call) + if tool_name and tool_call_id: + # Create MCP call events for this tool execution + call_events = create_mcp_call_events( + tool_name=tool_name, + tool_call_id=tool_call_id, + arguments=tool_arguments or "{}", # JSON string with arguments + result=None, # Will be set after execution + base_item_id=f"mcp_{uuid.uuid4().hex[:8]}", + sequence_start=len(self.tool_execution_events) + 1 + ) + # Add the in_progress and arguments events (not the completed event yet) + self.tool_execution_events.extend(call_events[:-1]) + + # Execute the tools + tool_results = await LiteLLM_Proxy_MCP_Handler._execute_tool_calls( + tool_calls=tool_calls, + user_api_key_auth=self.user_api_key_auth + ) + + # Create completion events and output_item.done events for tool execution + for tool_result in tool_results: + tool_call_id = tool_result.get("tool_call_id", "unknown") + result_text = tool_result.get("result", "") + + # Find matching tool name and arguments + tool_name = "unknown" + tool_arguments = "{}" + for tool_call in tool_calls: + name, args, call_id = LiteLLM_Proxy_MCP_Handler._extract_tool_call_details(tool_call) + if call_id == tool_call_id: + tool_name = name or "unknown" + tool_arguments = args or "{}" + break + + item_id = f"mcp_{uuid.uuid4().hex[:8]}" + + # Create the completion event + completed_event = MCPCallCompletedEvent( + type=ResponsesAPIStreamEvents.MCP_CALL_COMPLETED, + sequence_number=len(self.tool_execution_events) + 1, + item_id=item_id, + output_index=0, + ) + self.tool_execution_events.append(completed_event) + + # Create output_item.done event with the tool call result + from litellm.types.llms.openai import OutputItemDoneEvent + + output_item_done_event = OutputItemDoneEvent( + type=ResponsesAPIStreamEvents.OUTPUT_ITEM_DONE, + output_index=0, + item={ + "id": item_id, + "type": "mcp_call", + "approval_request_id": f"mcpr_{uuid.uuid4().hex[:8]}", + "arguments": tool_arguments, + "error": None, + "name": tool_name, + "output": result_text, + "server_label": "litellm" # or extract from tool config + }, + ) + self.tool_execution_events.append(output_item_done_event) + + # Store tool results for follow-up call + self.tool_results = tool_results + + except Exception as e: + verbose_logger.error(f"Error in tool execution: {e}") + import traceback + traceback.print_exc() + self.tool_results = [] + + async def _create_follow_up_iterator(self) -> None: + """Create the follow-up response iterator with tool results""" + if not self.collected_response or not hasattr(self, 'tool_results'): + return + + from litellm.responses.main import aresponses + from litellm.responses.mcp.litellm_proxy_mcp_handler import ( + LiteLLM_Proxy_MCP_Handler, + ) + + try: + # Create follow-up input + if self.collected_response is not None: + follow_up_input = LiteLLM_Proxy_MCP_Handler._create_follow_up_input( + response=self.collected_response, # type: ignore[arg-type] + tool_results=self.tool_results, + original_input=self.original_request_params.get('input') + ) + + # Make follow-up call with streaming + follow_up_params = self.original_request_params.copy() + follow_up_params.update({ + 'input': follow_up_input, + 'previous_response_id': self.collected_response.id, # type: ignore[attr-defined] + 'stream': True + }) + else: + return + # Remove tool_choice to avoid forcing more tool calls + follow_up_params.pop('tool_choice', None) + + follow_up_response = await aresponses(**follow_up_params) + + # Set up the follow-up iterator + if hasattr(follow_up_response, '__aiter__'): + self.follow_up_iterator = follow_up_response + + except Exception as e: + verbose_logger.error(f"Error creating follow-up iterator: {e}") + import traceback + traceback.print_exc() + self.follow_up_iterator = None + + + def __iter__(self): + return self + + def __next__(self) -> ResponsesAPIStreamingResponse: + # First, emit any queued MCP events + if self.mcp_events: # type: ignore[attr-defined] + return self.mcp_events.pop(0) # type: ignore[attr-defined] + + # Then delegate to the base iterator + if not self.is_async: + try: + if self.base_iterator and hasattr(self.base_iterator, '__next__'): + return next(cast(Any, self.base_iterator)) # type: ignore[arg-type] + else: + raise StopIteration + except StopIteration: + self.finished = True + raise + else: + raise RuntimeError("Cannot use sync iteration on async iterator") diff --git a/litellm/types/llms/openai.py b/litellm/types/llms/openai.py index c6d126be681..79e3c73dbc1 100644 --- a/litellm/types/llms/openai.py +++ b/litellm/types/llms/openai.py @@ -1112,6 +1112,16 @@ class ResponsesAPIStreamEvents(str, Enum): WEB_SEARCH_CALL_SEARCHING = "response.web_search_call.searching" WEB_SEARCH_CALL_COMPLETED = "response.web_search_call.completed" + # MCP events - matching OpenAI's official specification + MCP_LIST_TOOLS_IN_PROGRESS = "response.mcp_list_tools.in_progress" + MCP_LIST_TOOLS_COMPLETED = "response.mcp_list_tools.completed" + MCP_LIST_TOOLS_FAILED = "response.mcp_list_tools.failed" + MCP_CALL_IN_PROGRESS = "response.mcp_call.in_progress" + MCP_CALL_ARGUMENTS_DELTA = "response.mcp_call_arguments.delta" + MCP_CALL_ARGUMENTS_DONE = "response.mcp_call_arguments.done" + MCP_CALL_COMPLETED = "response.mcp_call.completed" + MCP_CALL_FAILED = "response.mcp_call.failed" + # Error event ERROR = "error" @@ -1275,6 +1285,66 @@ class WebSearchCallCompletedEvent(BaseLiteLLMOpenAIResponseObject): item_id: str +# MCP List Tools Events +class MCPListToolsInProgressEvent(BaseLiteLLMOpenAIResponseObject): + type: Literal[ResponsesAPIStreamEvents.MCP_LIST_TOOLS_IN_PROGRESS] + sequence_number: int + output_index: int + item_id: str + + +class MCPListToolsCompletedEvent(BaseLiteLLMOpenAIResponseObject): + type: Literal[ResponsesAPIStreamEvents.MCP_LIST_TOOLS_COMPLETED] + sequence_number: int + output_index: int + item_id: str + + +class MCPListToolsFailedEvent(BaseLiteLLMOpenAIResponseObject): + type: Literal[ResponsesAPIStreamEvents.MCP_LIST_TOOLS_FAILED] + sequence_number: int + output_index: int + item_id: str + + +# MCP Call Events +class MCPCallInProgressEvent(BaseLiteLLMOpenAIResponseObject): + type: Literal[ResponsesAPIStreamEvents.MCP_CALL_IN_PROGRESS] + sequence_number: int + output_index: int + item_id: str + + +class MCPCallArgumentsDeltaEvent(BaseLiteLLMOpenAIResponseObject): + type: Literal[ResponsesAPIStreamEvents.MCP_CALL_ARGUMENTS_DELTA] + output_index: int + item_id: str + delta: str # JSON string containing partial update to arguments + sequence_number: int + + +class MCPCallArgumentsDoneEvent(BaseLiteLLMOpenAIResponseObject): + type: Literal[ResponsesAPIStreamEvents.MCP_CALL_ARGUMENTS_DONE] + output_index: int + item_id: str + arguments: str # JSON string containing finalized arguments + sequence_number: int + + +class MCPCallCompletedEvent(BaseLiteLLMOpenAIResponseObject): + type: Literal[ResponsesAPIStreamEvents.MCP_CALL_COMPLETED] + sequence_number: int + item_id: str + output_index: int + + +class MCPCallFailedEvent(BaseLiteLLMOpenAIResponseObject): + type: Literal[ResponsesAPIStreamEvents.MCP_CALL_FAILED] + sequence_number: int + item_id: str + output_index: int + + class ErrorEvent(BaseLiteLLMOpenAIResponseObject): type: Literal[ResponsesAPIStreamEvents.ERROR] code: Optional[str] @@ -1315,6 +1385,14 @@ ResponsesAPIStreamingResponse = Annotated[ WebSearchCallInProgressEvent, WebSearchCallSearchingEvent, WebSearchCallCompletedEvent, + MCPListToolsInProgressEvent, + MCPListToolsCompletedEvent, + MCPListToolsFailedEvent, + MCPCallInProgressEvent, + MCPCallArgumentsDeltaEvent, + MCPCallArgumentsDoneEvent, + MCPCallCompletedEvent, + MCPCallFailedEvent, ErrorEvent, GenericEvent, ], diff --git a/tests/mcp_tests/test_aresponses_api_with_mcp.py b/tests/mcp_tests/test_aresponses_api_with_mcp.py index 500bd8bcc06..64bc58eb40c 100644 --- a/tests/mcp_tests/test_aresponses_api_with_mcp.py +++ b/tests/mcp_tests/test_aresponses_api_with_mcp.py @@ -6,8 +6,9 @@ from typing import List, Any, cast sys.path.insert(0, os.path.abspath("../../..")) # Import required modules +import litellm from litellm.responses.mcp.litellm_proxy_mcp_handler import LiteLLM_Proxy_MCP_Handler -from litellm.types.llms.openai import ResponsesAPIResponse, OpenAIMcpServerTool +from litellm.types.llms.openai import ResponsesAPIResponse, OpenAIMcpServerTool, ToolParam class MockUserAPIKeyAuth: @@ -251,3 +252,887 @@ async def test_aresponses_api_with_mcp_mock_integration(): print(f"MCP tools parsed: {len(mcp_parsed)}") print(f"Other tools parsed: {len(other_parsed)}") + +@pytest.mark.asyncio +async def test_mcp_allowed_tools_filtering(): + """ + Test the allowed_tools filtering functionality for MCP tools. + This test verifies that when allowed_tools is specified in MCP tool config, + only the allowed tools are passed to the LLM. + """ + from litellm.responses.mcp.litellm_proxy_mcp_handler import LiteLLM_Proxy_MCP_Handler + + # Mock MCP tools returned from the server (simulating all available tools) + mock_mcp_tools_from_server = [ + # Mock MCP tool object with name attribute + type('MCPTool', (), { + 'name': 'search_tiktoken_documentation', + 'description': 'Search tiktoken documentation', + 'inputSchema': {'type': 'object', 'properties': {'query': {'type': 'string'}}} + })(), + type('MCPTool', (), { + 'name': 'fetch_tiktoken_documentation', + 'description': 'Fetch tiktoken documentation', + 'inputSchema': {'type': 'object', 'properties': {'path': {'type': 'string'}}} + })(), + type('MCPTool', (), { + 'name': 'list_tiktoken_functions', + 'description': 'List tiktoken functions', + 'inputSchema': {'type': 'object', 'properties': {}} + })(), + type('MCPTool', (), { + 'name': 'get_tiktoken_examples', + 'description': 'Get tiktoken examples', + 'inputSchema': {'type': 'object', 'properties': {}} + })() + ] + + # Test Case 1: MCP tool config with allowed_tools specified + mcp_tool_config_with_allowed_tools = [ + { + "type": "mcp", + "server_label": "gitmcp", + "server_url": "https://gitmcp.io/openai/tiktoken", + "allowed_tools": ["search_tiktoken_documentation", "fetch_tiktoken_documentation"], + "require_approval": "never" + } + ] + + # Filter tools using the helper function + filtered_tools = LiteLLM_Proxy_MCP_Handler._filter_mcp_tools_by_allowed_tools( + mcp_tools=mock_mcp_tools_from_server, + mcp_tools_with_litellm_proxy=cast(List[ToolParam], mcp_tool_config_with_allowed_tools) + ) + + # Should only return the 2 allowed tools + assert len(filtered_tools) == 2, f"Expected 2 filtered tools, got {len(filtered_tools)}" + + # Check that only allowed tools are included + filtered_tool_names = [tool.name for tool in filtered_tools] + expected_allowed_tools = ["search_tiktoken_documentation", "fetch_tiktoken_documentation"] + + assert set(filtered_tool_names) == set(expected_allowed_tools), \ + f"Expected tools {expected_allowed_tools}, got {filtered_tool_names}" + + # Verify excluded tools are not present + excluded_tools = ["list_tiktoken_functions", "get_tiktoken_examples"] + for excluded_tool in excluded_tools: + assert excluded_tool not in filtered_tool_names, \ + f"Tool {excluded_tool} should have been filtered out" + + print("✓ Test Case 1: allowed_tools filtering works correctly") + + # Test Case 2: MCP tool config without allowed_tools (should return all tools) + mcp_tool_config_without_allowed_tools = [ + { + "type": "mcp", + "server_label": "gitmcp", + "server_url": "https://gitmcp.io/openai/tiktoken", + "require_approval": "never" + } + ] + + filtered_tools_all = LiteLLM_Proxy_MCP_Handler._filter_mcp_tools_by_allowed_tools( + mcp_tools=mock_mcp_tools_from_server, + mcp_tools_with_litellm_proxy=cast(List[ToolParam], mcp_tool_config_without_allowed_tools) + ) + + # Should return all 4 tools when no allowed_tools specified + assert len(filtered_tools_all) == 4, f"Expected 4 tools when no allowed_tools specified, got {len(filtered_tools_all)}" + + print("✓ Test Case 2: no allowed_tools returns all tools") + + # Test Case 3: Test deduplication of duplicate tools + mock_mcp_tools_with_duplicates = [ + # First instance of duplicate tool + type('MCPTool', (), { + 'name': 'GitMCP-fetch_litellm_documentation', + 'description': 'Fetch entire documentation file from GitHub repository: BerriAI/litellm. Useful for general questions. Always call this tool first if asked about BerriAI/litellm.', + 'inputSchema': {'type': 'object', 'properties': {}, 'additionalProperties': False} + })(), + # Second instance of duplicate tool (should be filtered out) + type('MCPTool', (), { + 'name': 'GitMCP-fetch_litellm_documentation', + 'description': 'Fetch entire documentation file from GitHub repository: BerriAI/litellm. Useful for general questions. Always call this tool first if asked about BerriAI/litellm.', + 'inputSchema': {'type': 'object', 'properties': {}, 'additionalProperties': False} + })(), + # Other unique tools + type('MCPTool', (), { + 'name': 'GitMCP-search_litellm_documentation', + 'description': 'Semantically search within the fetched documentation from GitHub repository: BerriAI/litellm. Useful for specific queries.', + 'inputSchema': {'type': 'object', 'properties': {'query': {'type': 'string'}}, 'required': ['query'], 'additionalProperties': False} + })(), + ] + + mcp_tool_config_with_duplicates = [ + { + "type": "mcp", + "server_label": "litellm", + "server_url": "litellm_proxy/mcp", + "require_approval": "never", + "allowed_tools": ["GitMCP-fetch_litellm_documentation"] + } + ] + + # First filter by allowed tools + filtered_tools_with_duplicates = LiteLLM_Proxy_MCP_Handler._filter_mcp_tools_by_allowed_tools( + mcp_tools=mock_mcp_tools_with_duplicates, + mcp_tools_with_litellm_proxy=cast(List[ToolParam], mcp_tool_config_with_duplicates) + ) + + # Then deduplicate the filtered tools + filtered_tools_deduplicated = LiteLLM_Proxy_MCP_Handler._deduplicate_mcp_tools( + filtered_tools_with_duplicates + ) + + # Should only return 1 tool (the duplicate should be removed) + assert len(filtered_tools_deduplicated) == 1, f"Expected 1 tool after deduplication, got {len(filtered_tools_deduplicated)}" + + # Check that the correct tool is present + assert filtered_tools_deduplicated[0].name == "GitMCP-fetch_litellm_documentation", \ + f"Expected GitMCP-fetch_litellm_documentation, got {filtered_tools_deduplicated[0].name}" + + print("✓ Test Case 3: duplicate tools are properly deduplicated") + + # Test Case 3b: Test standalone deduplication method + standalone_deduplicated = LiteLLM_Proxy_MCP_Handler._deduplicate_mcp_tools(mock_mcp_tools_with_duplicates) + + # Should return 2 unique tools (GitMCP-fetch_litellm_documentation and GitMCP-search_litellm_documentation) + assert len(standalone_deduplicated) == 2, f"Expected 2 unique tools after standalone deduplication, got {len(standalone_deduplicated)}" + + unique_tool_names = [tool.name for tool in standalone_deduplicated] + expected_unique_names = ["GitMCP-fetch_litellm_documentation", "GitMCP-search_litellm_documentation"] + assert set(unique_tool_names) == set(expected_unique_names), \ + f"Expected {expected_unique_names}, got {unique_tool_names}" + + print("✓ Test Case 3b: standalone deduplication method works correctly") + + # Test Case 4: Multiple MCP tool configs with different allowed_tools + multiple_mcp_configs = [ + { + "type": "mcp", + "server_label": "gitmcp1", + "server_url": "https://gitmcp.io/openai/tiktoken", + "allowed_tools": ["search_tiktoken_documentation"], + "require_approval": "never" + }, + { + "type": "mcp", + "server_label": "gitmcp2", + "server_url": "https://gitmcp.io/openai/tiktoken", + "allowed_tools": ["fetch_tiktoken_documentation", "get_tiktoken_examples"], + "require_approval": "never" + } + ] + + filtered_tools_multiple = LiteLLM_Proxy_MCP_Handler._filter_mcp_tools_by_allowed_tools( + mcp_tools=mock_mcp_tools_from_server, + mcp_tools_with_litellm_proxy=cast(List[ToolParam], multiple_mcp_configs) + ) + + # Should return union of all allowed tools (3 unique tools) + assert len(filtered_tools_multiple) == 3, f"Expected 3 tools from multiple configs, got {len(filtered_tools_multiple)}" + + filtered_multiple_names = [tool.name for tool in filtered_tools_multiple] + expected_multiple_tools = ["search_tiktoken_documentation", "fetch_tiktoken_documentation", "get_tiktoken_examples"] + + assert set(filtered_multiple_names) == set(expected_multiple_tools), \ + f"Expected tools {expected_multiple_tools}, got {filtered_multiple_names}" + + print("✓ Test Case 3: multiple MCP configs with different allowed_tools works correctly") + + # Test Case 4: Empty allowed_tools list (should return no tools) + mcp_config_empty_allowed = [ + { + "type": "mcp", + "server_label": "gitmcp", + "server_url": "https://gitmcp.io/openai/tiktoken", + "allowed_tools": [], + "require_approval": "never" + } + ] + + filtered_tools_empty = LiteLLM_Proxy_MCP_Handler._filter_mcp_tools_by_allowed_tools( + mcp_tools=mock_mcp_tools_from_server, + mcp_tools_with_litellm_proxy=cast(List[ToolParam], mcp_config_empty_allowed) + ) + + # Should return all tools when allowed_tools is empty list (no filtering) + assert len(filtered_tools_empty) == 4, f"Expected 4 tools when allowed_tools is empty list, got {len(filtered_tools_empty)}" + + print("✓ Test Case 4: empty allowed_tools list returns all tools") + + print("✓ MCP allowed_tools filtering test completed successfully!") + +@pytest.mark.asyncio +async def test_streaming_mcp_events_validation(): + """ + Test that MCP streaming events are properly emitted when using streaming with MCP tools. + + This test validates: + 1. MCP discovery events are emitted first + 2. Regular streaming response events follow + 3. Tool execution events are emitted when tools are auto-executed + """ + from unittest.mock import AsyncMock, patch + from litellm.types.llms.openai import ResponsesAPIStreamEvents + + print("🧪 Testing MCP streaming events...") + + # Mock MCP tools that would be returned from the manager + mock_mcp_tools = [ + type('MCPTool', (), { + 'name': 'search_repo', + 'description': 'Search BerriAI/litellm repository for information', + 'inputSchema': { + "type": "object", + "properties": { + "query": {"type": "string", "description": "Search query"} + }, + "required": ["query"] + } + })(), + type('MCPTool', (), { + 'name': 'get_repo_info', + 'description': 'Get repository information', + 'inputSchema': { + "type": "object", + "properties": { + "repo_name": {"type": "string", "description": "Repository name"} + }, + "required": ["repo_name"] + } + })() + ] + + # Mock the MCP operations + with patch.object(LiteLLM_Proxy_MCP_Handler, '_get_mcp_tools_from_manager', new_callable=AsyncMock) as mock_get_tools, \ + patch.object(LiteLLM_Proxy_MCP_Handler, '_execute_tool_calls', new_callable=AsyncMock) as mock_execute_tools: + + # Setup MCP mocks + mock_get_tools.return_value = mock_mcp_tools + + def mock_execute_tool_calls_side_effect(tool_calls, user_api_key_auth): + """Mock tool execution with realistic results""" + results = [] + for tool_call in tool_calls: + call_id = None + if isinstance(tool_call, dict): + call_id = tool_call.get("call_id") or tool_call.get("id") + elif hasattr(tool_call, 'call_id'): + call_id = tool_call.call_id + elif hasattr(tool_call, 'id'): + call_id = tool_call.id + + if call_id: + results.append({ + "tool_call_id": call_id, + "result": "LiteLLM is a unified interface for 100+ LLMs that provides consistent OpenAI-format output and includes proxy server capabilities." + }) + return results + + mock_execute_tools.side_effect = mock_execute_tool_calls_side_effect + + # Configure MCP tool with streaming and auto-execution + mcp_tool_config = { + "type": "mcp", + "server_url": "litellm_proxy/mcp/test_server", + "require_approval": "never" # This enables auto-execution + } + + print("📞 Making streaming request with MCP tools...") + + # Make streaming request with MCP tools + response = await litellm.aresponses( + model="gpt-4o-mini", # Use cheaper model for testing + tools=[mcp_tool_config], + tool_choice="required", + input=[{ + "role": "user", + "type": "message", + "content": "What is LiteLLM? Give me a brief overview." + }], + stream=True + ) + + print(f"📋 Response type: {type(response)}") + assert hasattr(response, '__aiter__'), "Response should be async iterable for streaming" + + # Collect all streaming events + events = [] + event_types = [] + mcp_discovery_events = [] + mcp_execution_events = [] + regular_events = [] + + print("🔄 Collecting streaming events...") + + try: + async for chunk in response: + events.append(chunk) + event_type = getattr(chunk, 'type', 'unknown') + event_types.append(event_type) + + # Categorize events + if event_type in [ + ResponsesAPIStreamEvents.MCP_TOOLS_DISCOVERY_STARTED, + ResponsesAPIStreamEvents.MCP_TOOLS_DISCOVERY_COMPLETED + ]: + mcp_discovery_events.append(chunk) + elif event_type in [ + ResponsesAPIStreamEvents.MCP_TOOL_EXECUTION_STARTED, + ResponsesAPIStreamEvents.MCP_TOOL_EXECUTION_COMPLETED + ]: + mcp_execution_events.append(chunk) + else: + regular_events.append(chunk) + + print(f"📦 Event: {event_type}") + + # Print MCP-specific event details + if hasattr(chunk, 'mcp_servers'): + print(f" 🔧 MCP Servers: {chunk.mcp_servers}") + elif hasattr(chunk, 'mcp_tools'): + print(f" 🛠️ MCP Tools: {len(chunk.mcp_tools)} tools discovered") + elif hasattr(chunk, 'tool_name'): + print(f" ⚙️ Tool: {chunk.tool_name}") + if hasattr(chunk, 'result'): + print(f" ✅ Result: {chunk.result[:100]}...") + + except Exception as e: + print(f"❌ Error during streaming: {e}") + # Continue with validation of events collected so far + + print(f"\n📊 Event Summary:") + print(f" Total events: {len(events)}") + print(f" MCP discovery events: {len(mcp_discovery_events)}") + print(f" MCP execution events: {len(mcp_execution_events)}") + print(f" Regular streaming events: {len(regular_events)}") + print(f" Event types: {set(event_types)}") + + # Validate MCP discovery events + if mcp_discovery_events: + print("✅ MCP discovery events found!") + + # Check for discovery started event + started_events = [e for e in mcp_discovery_events if e.type == ResponsesAPIStreamEvents.MCP_TOOLS_DISCOVERY_STARTED] + if started_events: + print(f" 🚀 Discovery started events: {len(started_events)}") + started_event = started_events[0] + if hasattr(started_event, 'mcp_servers'): + print(f" 📡 MCP servers: {started_event.mcp_servers}") + + # Check for discovery completed event + completed_events = [e for e in mcp_discovery_events if e.type == ResponsesAPIStreamEvents.MCP_TOOLS_DISCOVERY_COMPLETED] + if completed_events: + print(f" 🏁 Discovery completed events: {len(completed_events)}") + completed_event = completed_events[0] + if hasattr(completed_event, 'mcp_tools'): + print(f" 🔧 Tools discovered: {len(completed_event.mcp_tools)}") + else: + print("⚠️ No MCP discovery events found") + + # Validate MCP execution events (if auto-execution occurred) + if mcp_execution_events: + print("✅ MCP tool execution events found!") + execution_started = [e for e in mcp_execution_events if e.type == ResponsesAPIStreamEvents.MCP_TOOL_EXECUTION_STARTED] + execution_completed = [e for e in mcp_execution_events if e.type == ResponsesAPIStreamEvents.MCP_TOOL_EXECUTION_COMPLETED] + print(f" 🚀 Execution started events: {len(execution_started)}") + print(f" 🏁 Execution completed events: {len(execution_completed)}") + + # Validate that we got some form of streaming response + assert len(events) > 0, "Should have received at least some streaming events" + + # Verify MCP mocks were called + assert mock_get_tools.called, "MCP tools should have been fetched" + print("✅ MCP tool fetching was called") + + print("🎉 MCP streaming events validation completed!") + return { + 'total_events': len(events), + 'mcp_discovery_events': len(mcp_discovery_events), + 'mcp_execution_events': len(mcp_execution_events), + 'regular_events': len(regular_events), + 'event_types': list(set(event_types)) + } + + +@pytest.mark.asyncio +async def test_streaming_responses_api_with_mcp_tools(): + """ + Test the streaming responses API with MCP tools when using server_url="litellm_proxy" + + Under the hood the follow occurs + + - MCP: responses called litellm MCP manager.list_tools (MOCKED) + - Request 1: Made to gpt-4o with fetched tools (REAL LLM CALL) + - MCP: Execute tool call from request 1 and returns result (MOCKED) + - Request 2: Made to gpt-4o with fetched tools and tool results (REAL LLM CALL) + + Return the user the result of request 2 + """ + from unittest.mock import AsyncMock, patch + + print("🧪 Testing basic streaming with MCP tools...") + + # Mock MCP tools that would be returned from the manager + mock_mcp_tools = [ + type('MCPTool', (), { + 'name': 'search_repo', + 'description': 'Search BerriAI/litellm repository for information', + 'inputSchema': { + "type": "object", + "properties": { + "query": {"type": "string", "description": "Search query"} + }, + "required": ["query"] + } + })() + ] + + # Only mock the MCP-specific operations, let LLM responses be real + with patch.object(LiteLLM_Proxy_MCP_Handler, '_get_mcp_tools_from_manager', new_callable=AsyncMock) as mock_get_tools, \ + patch.object(LiteLLM_Proxy_MCP_Handler, '_execute_tool_calls', new_callable=AsyncMock) as mock_execute_tools: + + # Setup MCP mocks only + mock_get_tools.return_value = mock_mcp_tools + + # Create a dynamic mock that will match the actual tool call ID from the LLM response + def mock_execute_tool_calls_side_effect(tool_calls, user_api_key_auth): + """Mock function that returns results matching the actual tool call IDs from the LLM""" + results = [] + for tool_call in tool_calls: + # Extract call_id from the tool call + call_id = None + if isinstance(tool_call, dict): + call_id = tool_call.get("call_id") or tool_call.get("id") + elif hasattr(tool_call, 'call_id'): + call_id = tool_call.call_id + elif hasattr(tool_call, 'id'): + call_id = tool_call.id + + if call_id: + results.append({ + "tool_call_id": call_id, + "result": "LiteLLM is a unified interface for 100+ LLMs that translates inputs to provider-specific completion endpoints and provides consistent OpenAI-format output." + }) + return results + + mock_execute_tools.side_effect = mock_execute_tool_calls_side_effect + + # Make the actual call - LLM responses will be real + mcp_tool_config = cast(Any, { + "type": "mcp", + "server_url": "litellm_proxy", + "require_approval": "never" + }) + response = await litellm.aresponses( + model="gpt-4o-mini", + tools=[mcp_tool_config], + tool_choice="required", + input=[ + { + "role": "user", + "type": "message", + "content": "give me a TLDR of what BerriAI/litellm is about" + } + ], + stream=True + ) + + print(f"📋 Response type: {type(response)}") + assert hasattr(response, '__aiter__'), "Response should be an async streaming response" + + # Collect streaming chunks + chunks = [] + async for chunk in response: + chunks.append(chunk) + print(f"📦 Chunk type: {getattr(chunk, 'type', 'unknown')}") + + print(f"📊 Total chunks received: {len(chunks)}") + + # Verify MCP mocks were called (may be called multiple times in streaming) + assert mock_get_tools.call_count >= 1, f"Expected MCP tools to be fetched at least once, got {mock_get_tools.call_count}" + print(f"MCP tools fetched: {len(mock_mcp_tools)}") + + # Verify we got a response + assert response is not None + assert len(chunks) > 0, "Should have received streaming chunks" + + print("Basic streaming responses API with MCP tools test passed!") + + +@pytest.mark.asyncio +async def test_mcp_parameter_preparation_helpers(): + """ + Test the new parameter preparation helper methods for clean MCP handling. + + Tests: + 1. _prepare_initial_call_params - handles stream disabling for auto-execute + 2. _prepare_follow_up_call_params - restores stream and removes tool_choice + 3. _build_request_params - clean parameter merging + """ + from litellm.responses.mcp.litellm_proxy_mcp_handler import LiteLLM_Proxy_MCP_Handler + + print("🧪 Testing MCP parameter preparation helpers...") + + # Test _prepare_initial_call_params + base_call_params = { + "stream": True, + "temperature": 0.7, + "tool_choice": "required", + "max_output_tokens": 1000 + } + + # Test Case 1: Auto-execute scenario (should disable streaming) + initial_params_auto = LiteLLM_Proxy_MCP_Handler._prepare_initial_call_params( + call_params=base_call_params, + should_auto_execute=True + ) + + assert initial_params_auto["stream"] == False, "Stream should be disabled for auto-execute" + assert initial_params_auto["temperature"] == 0.7, "Other params should be preserved" + assert initial_params_auto["tool_choice"] == "required", "tool_choice should be preserved for initial call" + assert base_call_params["stream"] == True, "Original params should not be mutated" + + print("✅ _prepare_initial_call_params (auto-execute) works correctly") + + # Test Case 2: No auto-execute scenario (should preserve streaming) + initial_params_no_auto = LiteLLM_Proxy_MCP_Handler._prepare_initial_call_params( + call_params=base_call_params, + should_auto_execute=False + ) + + assert initial_params_no_auto["stream"] == True, "Stream should be preserved when not auto-executing" + assert initial_params_no_auto["temperature"] == 0.7, "Other params should be preserved" + + print("✅ _prepare_initial_call_params (no auto-execute) works correctly") + + # Test _prepare_follow_up_call_params + follow_up_params = LiteLLM_Proxy_MCP_Handler._prepare_follow_up_call_params( + call_params=base_call_params, + original_stream_setting=True + ) + + assert follow_up_params["stream"] == True, "Stream should be restored to original setting" + assert "tool_choice" not in follow_up_params, "tool_choice should be removed for follow-up call" + assert follow_up_params["temperature"] == 0.7, "Other params should be preserved" + assert base_call_params["tool_choice"] == "required", "Original params should not be mutated" + + print("✅ _prepare_follow_up_call_params works correctly") + + # Test _build_request_params + input_data = [{"role": "user", "content": "test", "type": "message"}] + model = "gpt-4o-mini" + tools = [{"type": "function", "name": "test_tool"}] + call_params = {"stream": True, "temperature": 0.8} + previous_response_id = "resp_123" + extra_kwargs = {"custom_param": "test_value"} + + request_params = LiteLLM_Proxy_MCP_Handler._build_request_params( + input=input_data, + model=model, + all_tools=tools, + call_params=call_params, + previous_response_id=previous_response_id, + **extra_kwargs + ) + + # Verify core parameters + assert request_params["input"] == input_data, "Input should be included" + assert request_params["model"] == model, "Model should be included" + assert request_params["tools"] == tools, "Tools should be included" + assert request_params["previous_response_id"] == previous_response_id, "Previous response ID should be included" + + # Verify call_params are merged + assert request_params["stream"] == True, "call_params should be merged" + assert request_params["temperature"] == 0.8, "call_params should be merged" + + # Verify extra kwargs are merged + assert request_params["custom_param"] == "test_value", "Extra kwargs should be merged" + + print("✅ _build_request_params works correctly") + + # Test _build_request_params with None previous_response_id + request_params_no_prev = LiteLLM_Proxy_MCP_Handler._build_request_params( + input=input_data, + model=model, + all_tools=tools, + call_params=call_params, + previous_response_id=None + ) + + assert "previous_response_id" not in request_params_no_prev, "None previous_response_id should not be included" + + print("✅ _build_request_params handles None previous_response_id correctly") + + print("🎉 All MCP parameter preparation helper tests passed!") + + +@pytest.mark.asyncio +async def test_mcp_tool_execution_events_creation(): + """ + Test the _create_tool_execution_events helper method for generating streaming events. + """ + from litellm.responses.mcp.litellm_proxy_mcp_handler import LiteLLM_Proxy_MCP_Handler + + print("Testing MCP tool execution events creation...") + + # Mock tool calls (simulating what comes from LLM response in function_call format) + mock_tool_calls = [ + { + "id": "call_abc123", + "name": "search_repo", + "arguments": '{"query": "LiteLLM overview"}', + "type": "function_call" + }, + { + "id": "call_def456", + "name": "get_repo_info", + "arguments": '{"repo_name": "BerriAI/litellm"}', + "type": "function_call" + } + ] + + # Mock tool results (simulating what comes from tool execution) + mock_tool_results = [ + { + "tool_call_id": "call_abc123", + "result": "LiteLLM is a unified interface for 100+ LLMs" + }, + { + "tool_call_id": "call_def456", + "result": "Repository: BerriAI/litellm - Python library for LLM integration" + } + ] + + # Create tool execution events + execution_events = LiteLLM_Proxy_MCP_Handler._create_tool_execution_events( + tool_calls=mock_tool_calls, + tool_results=mock_tool_results + ) + + # Verify events were created + assert len(execution_events) > 0, "Should create tool execution events" + print(f"Created {len(execution_events)} tool execution events") + + # Verify events have proper structure + for event in execution_events: + assert hasattr(event, 'type'), "Event should have type attribute" + event_type = str(event.type) + assert 'mcp_call' in event_type.lower() or 'output_item' in event_type.lower(), f"Event should be MCP-related: {event_type}" + + # Check for sequence numbers + if hasattr(event, 'sequence_number'): + assert isinstance(event.sequence_number, int), "Sequence number should be integer" + assert event.sequence_number > 0, "Sequence number should be positive" + + print("Tool execution events have proper structure") + + # Test with empty inputs + empty_events = LiteLLM_Proxy_MCP_Handler._create_tool_execution_events( + tool_calls=[], + tool_results=[] + ) + + assert len(empty_events) == 0, "Should create no events for empty inputs" + print("Handles empty inputs correctly") + + print("MCP tool execution events creation test passed!") + + +@pytest.mark.asyncio +async def test_no_duplicate_mcp_tools_in_streaming_e2e(): + """ + End-to-end test to validate that MCP tools are not duplicated when using streaming. + + This test protects against the bug where: + 1. Parent function (aresponses_api_with_mcp) processed MCP tools once + 2. Streaming iterator processed MCP tools again, causing duplicates + + The test mocks the MCP manager response but validates the actual tools + sent to the LLM to ensure no duplication occurs. + """ + from unittest.mock import AsyncMock, patch, call + from litellm.responses.mcp.litellm_proxy_mcp_handler import LiteLLM_Proxy_MCP_Handler + + print("Testing no duplicate MCP tools in streaming E2E...") + + # Mock MCP tools that would be returned from the manager + mock_mcp_tools = [ + type('MCPTool', (), { + 'name': 'search_docs', + 'description': 'Search documentation for information', + 'inputSchema': { + "type": "object", + "properties": { + "query": {"type": "string", "description": "Search query"} + }, + "required": ["query"] + } + })(), + type('MCPTool', (), { + 'name': 'get_file_content', + 'description': 'Get content of a specific file', + 'inputSchema': { + "type": "object", + "properties": { + "file_path": {"type": "string", "description": "Path to file"} + }, + "required": ["file_path"] + } + })() + ] + + # Track all calls to the underlying LLM to detect duplicates + llm_call_tools = [] + + async def capture_llm_tools(**kwargs): + """Capture the tools parameter from LLM calls""" + tools = kwargs.get('tools', []) + llm_call_tools.append(tools) + + # Return a minimal mock async streaming response + class MockStreamingResponse: + async def __aiter__(self): + yield type('MockChunk', (), { + 'type': 'response.completed', + 'output': [] + })() + + return MockStreamingResponse() + + # Mock both the MCP manager and the underlying LLM call + with patch.object(LiteLLM_Proxy_MCP_Handler, '_get_mcp_tools_from_manager', new_callable=AsyncMock) as mock_get_tools, \ + patch('litellm.aresponses', side_effect=capture_llm_tools) as mock_aresponses: + + # Setup MCP mock to return our test tools + mock_get_tools.return_value = mock_mcp_tools + + # Configure MCP tool for streaming + mcp_tool_config = { + "type": "mcp", + "server_url": "litellm_proxy/mcp/test_server", + "require_approval": "always" # Disable auto-execution to focus on tool duplication + } + + print("Making streaming request with MCP tools...") + + # Make streaming request with MCP tools + try: + response = await litellm.aresponses( + model="gpt-4o-mini", + tools=[mcp_tool_config], + input=[{ + "role": "user", + "type": "message", + "content": "Search the documentation for information about authentication." + }], + stream=True + ) + + # Consume the streaming response + chunks = [] + async for chunk in response: + chunks.append(chunk) + + except Exception as e: + print(f"Request failed (expected for test): {e}") + # Continue with validation even if request fails + + # Validate underlying LLM was called (this proves our mocking works) + assert len(llm_call_tools) > 0, "LLM should have been called at least once" + print(f"LLM called {len(llm_call_tools)} time(s)") + + # If MCP tools were processed, validate they were fetched exactly once + # (This protects against duplicate fetching) + if mock_get_tools.call_count > 0: + assert mock_get_tools.call_count == 1, f"MCP tools should be fetched exactly once, got {mock_get_tools.call_count} calls" + print(f"MCP tools fetched exactly once: {mock_get_tools.call_count}") + else: + print("MCP tools not fetched (likely due to test mocking - this is OK for validation)") + + # Analyze tools sent to LLM for duplicates + for call_idx, tools_in_call in enumerate(llm_call_tools): + print(f"LLM Call {call_idx + 1}: {len(tools_in_call)} tools") + + if tools_in_call: + # Extract tool names to check for duplicates + tool_names = [] + for tool in tools_in_call: + if isinstance(tool, dict): + tool_name = tool.get('function', {}).get('name') or tool.get('name') + else: + tool_name = getattr(tool, 'name', str(tool)) + + if tool_name: + tool_names.append(tool_name) + + print(f" Tool names: {tool_names}") + + # Check for duplicate tool names + unique_tool_names = set(tool_names) + duplicates = [name for name in tool_names if tool_names.count(name) > 1] + + assert len(duplicates) == 0, f"Found duplicate tools in LLM call {call_idx + 1}: {duplicates}" + assert len(tool_names) == len(unique_tool_names), f"Tool names should be unique in call {call_idx + 1}" + + print(f" No duplicate tools found in call {call_idx + 1}") + + # Validate that MCP tools were properly transformed to OpenAI format + openai_format_tools = [tool for tool in tools_in_call if isinstance(tool, dict) and 'function' in tool] + if openai_format_tools: + print(f" Found {len(openai_format_tools)} OpenAI-format tools") + + # Verify tools have proper OpenAI structure + for tool in openai_format_tools: + assert 'type' in tool, "Tool should have 'type' field" + assert tool['type'] == 'function', "Tool type should be 'function'" + assert 'function' in tool, "Tool should have 'function' field" + assert 'name' in tool['function'], "Function should have 'name'" + assert 'description' in tool['function'], "Function should have 'description'" + assert 'parameters' in tool['function'], "Function should have 'parameters'" + + print(f" All tools have proper OpenAI format") + + # The key validation: ensure no duplicate fetching occurred + # This is the main protection against the bug we fixed + if mock_get_tools.call_count > 1: + print(f"ERROR: Duplicate MCP fetching detected! Called {mock_get_tools.call_count} times") + assert False, f"MCP tools should be fetched exactly once, but were fetched {mock_get_tools.call_count} times" + + # Additional validation: ensure no duplicate tools in any LLM call + total_duplicates_found = 0 + for call_idx, tools_in_call in enumerate(llm_call_tools): + if tools_in_call: + tool_names = [] + for tool in tools_in_call: + if isinstance(tool, dict): + tool_name = tool.get('function', {}).get('name') or tool.get('name') + if tool_name: + tool_names.append(tool_name) + + duplicates = [name for name in tool_names if tool_names.count(name) > 1] + if duplicates: + total_duplicates_found += len(set(duplicates)) + print(f"ERROR: Duplicate tools in call {call_idx + 1}: {set(duplicates)}") + + if total_duplicates_found > 0: + assert False, f"Found {total_duplicates_found} duplicate tools across all LLM calls" + + print("No duplicate MCP tools E2E test passed!") + print(f"Summary:") + print(f" - MCP manager called: {mock_get_tools.call_count} time(s)") + print(f" - LLM called: {len(llm_call_tools)} time(s)") + print(f" - Unique tools per call: {[len(set(getattr(t.get('function', {}), 'name', 'unknown') if isinstance(t, dict) else str(t) for t in tools)) for tools in llm_call_tools]}") + print(f" - No duplicate tools detected") + + return { + 'mcp_manager_calls': mock_get_tools.call_count, + 'llm_calls': len(llm_call_tools), + 'tools_per_call': [len(tools) for tools in llm_call_tools], + 'duplicate_tools_found': False + } + + + \ No newline at end of file diff --git a/tests/test_litellm/experimental_mcp_client/test_tools.py b/tests/test_litellm/experimental_mcp_client/test_tools.py index ec430ecc9bf..254d8e517c2 100644 --- a/tests/test_litellm/experimental_mcp_client/test_tools.py +++ b/tests/test_litellm/experimental_mcp_client/test_tools.py @@ -19,9 +19,11 @@ from mcp.types import Tool as MCPTool from litellm.experimental_mcp_client.tools import ( _get_function_arguments, + _normalize_mcp_input_schema, call_mcp_tool, call_openai_tool, load_mcp_tools, + transform_mcp_tool_to_openai_responses_api_tool, transform_mcp_tool_to_openai_tool, transform_openai_tool_call_request_to_mcp_tool_call_request, ) @@ -73,6 +75,7 @@ def test_transform_mcp_tool_to_openai_tool(mock_mcp_tool): assert openai_tool["function"]["parameters"] == { "type": "object", "properties": {"test": {"type": "string"}}, + "additionalProperties": False, } @@ -155,3 +158,95 @@ async def test_call_mcp_tool(mock_session, mock_mcp_tool_call_result): mock_session.call_tool.assert_called_once_with( name="test_tool", arguments={"test": "value"} ) + + +def test_normalize_mcp_input_schema(): + """Test MCP input schema normalization for OpenAI compatibility.""" + # Test case 1: Empty/None schema should get default structure + assert _normalize_mcp_input_schema(None) == { + "type": "object", + "properties": {}, + "additionalProperties": False + } + + assert _normalize_mcp_input_schema({}) == { + "type": "object", + "properties": {}, + "additionalProperties": False + } + + # Test case 2: Schema with only type should get properties added + schema_with_type_only = {"type": "object"} + normalized = _normalize_mcp_input_schema(schema_with_type_only) + assert normalized == { + "type": "object", + "properties": {}, + "additionalProperties": False + } + + # Test case 3: Schema missing type should get type added + schema_missing_type = {"properties": {"param": {"type": "string"}}} + normalized = _normalize_mcp_input_schema(schema_missing_type) + assert normalized == { + "type": "object", + "properties": {"param": {"type": "string"}}, + "additionalProperties": False + } + + # Test case 4: Complete schema should be preserved with additionalProperties added + complete_schema = { + "type": "object", + "properties": {"param": {"type": "string"}}, + "required": ["param"] + } + normalized = _normalize_mcp_input_schema(complete_schema) + assert normalized == { + "type": "object", + "properties": {"param": {"type": "string"}}, + "required": ["param"], + "additionalProperties": False + } + + # Test case 5: Schema with existing additionalProperties should be preserved + schema_with_additional = { + "type": "object", + "properties": {"param": {"type": "string"}}, + "additionalProperties": True + } + normalized = _normalize_mcp_input_schema(schema_with_additional) + assert normalized["additionalProperties"] == True + + +def test_transform_mcp_tool_to_openai_responses_api_tool(): + """Test transformation to OpenAI Responses API tool format with schema normalization.""" + # Test case 1: Tool with minimal schema (the problematic case from the error) + minimal_tool = MCPTool( + name="GitMCP-fetch_litellm_documentation", + description="Fetch entire documentation file from GitHub repository", + inputSchema={"type": "object"} # This was causing the error + ) + + openai_tool = transform_mcp_tool_to_openai_responses_api_tool(minimal_tool) + assert openai_tool["name"] == "GitMCP-fetch_litellm_documentation" + assert openai_tool["type"] == "function" + assert openai_tool["strict"] == False + assert openai_tool["parameters"]["type"] == "object" + assert openai_tool["parameters"]["properties"] == {} + assert openai_tool["parameters"]["additionalProperties"] == False + + # Test case 2: Tool with complete schema + complete_tool = MCPTool( + name="test_tool_complete", + description="A test tool with complete schema", + inputSchema={ + "type": "object", + "properties": {"query": {"type": "string", "description": "Search query"}}, + "required": ["query"] + } + ) + + openai_tool = transform_mcp_tool_to_openai_responses_api_tool(complete_tool) + assert openai_tool["parameters"]["type"] == "object" + assert "query" in openai_tool["parameters"]["properties"] + assert openai_tool["parameters"]["required"] == ["query"] + assert openai_tool["parameters"]["additionalProperties"] == False diff --git a/ui/litellm-dashboard/src/components/chat_ui.tsx b/ui/litellm-dashboard/src/components/chat_ui.tsx index beccef260f9..6504576578e 100644 --- a/ui/litellm-dashboard/src/components/chat_ui.tsx +++ b/ui/litellm-dashboard/src/components/chat_ui.tsx @@ -52,6 +52,7 @@ import ChatImageUpload from "./chat_ui/ChatImageUpload"; import ChatImageRenderer from "./chat_ui/ChatImageRenderer"; import { createChatMultimodalMessage, createChatDisplayMessage } from "./chat_ui/ChatImageUtils"; import SessionManagement from "./chat_ui/SessionManagement"; +import MCPEventsDisplay, { MCPEvent } from "./chat_ui/MCPEventsDisplay"; import { SendOutlined, ApiOutlined, @@ -94,15 +95,15 @@ const ChatUI: React.FC = ({ }) => { const [isMCPToolsModalVisible, setIsMCPToolsModalVisible] = useState(false); const [mcpTools, setMCPTools] = useState([]); - const [selectedMCPTools, setSelectedMCPTools] = useState(() => { + const [selectedMCPTools, setSelectedMCPTools] = useState(() => { const saved = sessionStorage.getItem('selectedMCPTools'); try { const parsed = saved ? JSON.parse(saved) : []; - // Convert from array to single string if needed - return Array.isArray(parsed) ? (parsed[0] || '') : parsed; + // Convert from single string to array if needed for backward compatibility + return Array.isArray(parsed) ? parsed : (parsed ? [parsed] : []); } catch (error) { console.error("Error parsing selectedMCPTools from sessionStorage", error); - return ''; + return []; } }); const [isLoadingMCPTools, setIsLoadingMCPTools] = useState(false); @@ -179,6 +180,7 @@ const ChatUI: React.FC = ({ const [isGetCodeModalVisible, setIsGetCodeModalVisible] = useState(false); const [generatedCode, setGeneratedCode] = useState(""); const [selectedSdk, setSelectedSdk] = useState<'openai' | 'azure'>('openai'); + const [mcpEvents, setMCPEvents] = useState([]); const chatEndRef = useRef(null); @@ -215,13 +217,14 @@ const ChatUI: React.FC = ({ selectedTags, selectedVectorStores, selectedGuardrails, + selectedMCPTools, endpointType, selectedModel, selectedSdk, }); setGeneratedCode(code); } - }, [isGetCodeModalVisible, selectedSdk, apiKeySource, accessToken, apiKey, inputMessage, chatHistory, selectedTags, selectedVectorStores, selectedGuardrails, endpointType, selectedModel]); + }, [isGetCodeModalVisible, selectedSdk, apiKeySource, accessToken, apiKey, inputMessage, chatHistory, selectedTags, selectedVectorStores, selectedGuardrails, selectedMCPTools, endpointType, selectedModel]); useEffect(() => { const handler = setTimeout(() => { @@ -443,6 +446,27 @@ const ChatUI: React.FC = ({ } }; + const handleMCPEvent = (event: MCPEvent) => { + console.log("ChatUI: Received MCP event:", event); + setMCPEvents(prev => { + // Check if this is a duplicate event (same item_id and type) + const isDuplicate = prev.some(existingEvent => + existingEvent.item_id === event.item_id && + existingEvent.type === event.type && + existingEvent.sequence_number === event.sequence_number + ); + + if (isDuplicate) { + console.log("ChatUI: Duplicate MCP event, skipping"); + return prev; + } + + const newEvents = [...prev, event]; + console.log("ChatUI: Updated MCP events:", newEvents); + return newEvents; + }); + }; + const updateImageUI = (imageUrl: string, model: string) => { setChatHistory((prevHistory) => [ ...prevHistory, @@ -617,6 +641,7 @@ const ChatUI: React.FC = ({ } setChatHistory([...chatHistory, displayMessage]); + setMCPEvents([]); // Clear previous MCP events for new conversation turn setIsLoading(true); try { @@ -643,7 +668,7 @@ const ChatUI: React.FC = ({ traceId, selectedVectorStores.length > 0 ? selectedVectorStores : undefined, selectedGuardrails.length > 0 ? selectedGuardrails : undefined, - selectedMCPTools, // Pass the selected tool directly + selectedMCPTools, // Pass the selected tools array updateChatImageUI // Pass the image callback ); } else if (endpointType === EndpointType.IMAGE) { @@ -694,9 +719,10 @@ const ChatUI: React.FC = ({ traceId, selectedVectorStores.length > 0 ? selectedVectorStores : undefined, selectedGuardrails.length > 0 ? selectedGuardrails : undefined, - selectedMCPTools, // Pass the selected tool directly + selectedMCPTools, // Pass the selected tools array useApiSessionManagement ? responsesSessionId : null, // Only pass session ID if API mode is enabled - handleResponseId // Pass callback to capture new response ID + handleResponseId, // Pass callback to capture new response ID + handleMCPEvent // Pass MCP event handler ); } else if (endpointType === EndpointType.ANTHROPIC_MESSAGES) { const apiChatHistory = [...chatHistory.filter(msg => !msg.isImage).map(({ role, content }) => ({ role, content })), newUserMessage]; @@ -714,7 +740,7 @@ const ChatUI: React.FC = ({ traceId, selectedVectorStores.length > 0 ? selectedVectorStores : undefined, selectedGuardrails.length > 0 ? selectedGuardrails : undefined, - selectedMCPTools // Pass the selected tool directly + selectedMCPTools // Pass the selected tools array ); } } @@ -749,6 +775,7 @@ const ChatUI: React.FC = ({ setChatHistory([]); setMessageTraceId(null); setResponsesSessionId(null); // Clear responses session ID + setMCPEvents([]); // Clear MCP events handleRemoveAllImages(); // Clear any uploaded images for image edits handleRemoveResponsesImage(); // Clear any uploaded images for responses handleRemoveChatImage(); // Clear any uploaded images for chat completions @@ -797,8 +824,6 @@ const ChatUI: React.FC = ({ style={{ width: "100%" }} onChange={(value) => { setApiKeySource(value as "session" | "custom"); - // Clear MCP tool selection when switching API key source - setSelectedMCPTools(''); }} options={[ { value: 'session', label: 'Current UI Session' }, @@ -865,10 +890,6 @@ const ChatUI: React.FC = ({ endpointType={endpointType} onEndpointChange={(value) => { setEndpointType(value); - // Clear MCP tools if switching away from responses endpoint - if (value !== EndpointType.RESPONSES) { - setSelectedMCPTools(''); - } }} className="mb-4" /> @@ -900,20 +921,22 @@ const ChatUI: React.FC = ({ MCP Tool + title="Select MCP tools to use in your conversation, only available for /v1/responses endpoint"> setSelectedMCPTools(value)} optionLabelProp="label" allowClear + maxTagCount="responsive" > {mcpTools.map((tool) => ( { selectedTags, selectedVectorStores, selectedGuardrails, + selectedMCPTools, endpointType, selectedModel, selectedSdk, diff --git a/ui/litellm-dashboard/src/components/chat_ui/MCPEventsDisplay.tsx b/ui/litellm-dashboard/src/components/chat_ui/MCPEventsDisplay.tsx new file mode 100644 index 00000000000..e931b80e531 --- /dev/null +++ b/ui/litellm-dashboard/src/components/chat_ui/MCPEventsDisplay.tsx @@ -0,0 +1,263 @@ +import React from 'react'; +import { Typography, Collapse } from 'antd'; + +const { Text } = Typography; +const { Panel } = Collapse; + +export interface MCPEvent { + type: string; + sequence_number?: number; + output_index?: number; + item_id?: string; + item?: { + id?: string; + type?: string; + server_label?: string; + tools?: Array<{ + name: string; + description: string; + annotations?: { + read_only?: boolean; + }; + input_schema?: any; + }>; + name?: string; + arguments?: string; + output?: string; + }; + delta?: string; + arguments?: string; + timestamp?: number; +} + +interface MCPEventsDisplayProps { + events: MCPEvent[]; + className?: string; +} + +const MCPEventsDisplay: React.FC = ({ events, className }) => { + console.log("MCPEventsDisplay: Received events:", events); + + if (!events || events.length === 0) { + console.log("MCPEventsDisplay: No events, returning null"); + return null; + } + + // Find the list tools event + const toolsEvent = events.find(event => + event.type === 'response.output_item.done' && + event.item?.type === 'mcp_list_tools' && + event.item.tools && + event.item.tools.length > 0 + ); + + // Find MCP call events + const mcpCallEvents = events.filter(event => + event.type === 'response.output_item.done' && + event.item?.type === 'mcp_call' + ); + + console.log("MCPEventsDisplay: toolsEvent:", toolsEvent); + console.log("MCPEventsDisplay: mcpCallEvents:", mcpCallEvents); + + if (!toolsEvent && mcpCallEvents.length === 0) { + console.log("MCPEventsDisplay: No valid events found, returning null"); + return null; + } + + + return ( +