From 987a8fcf48b0a8de787b4405290fb8f4cd3c108b Mon Sep 17 00:00:00 2001 From: mgeorgaklis Date: Wed, 29 Jul 2026 03:31:54 +0000 Subject: [PATCH 01/92] fix(gemini): do not send duplicate thoughtSignature copies to Gemini Gemini returns each thoughtSignature on exactly one part. LiteLLM stores a function-call signature both message-level (thought_signatures) and on the tool call itself, then re-attached it to BOTH the text part and the function-call part when serializing history. gemini-3 and newer models bill every replayed copy as the previous turn's full reasoning token count, so long agentic sessions doubled their context growth and hit the 1,048,576-token limit Only attach a message-level signature to the text part when the same signature is not already carried by a tool-call part: - compare signature values instead of boolean presence so a distinct text-part signature is never dropped - ignore the gemini-3 dummy-signature fallback during detection so replaying gemini-2.5 history to a newer model keeps the real text signature - count signatures carried by server-side tool invocations so they are not re-attached to the text part gemini-2.5 responses (signature on the text part, function call unsigned) are unaffected: the text signature is preserved as before --- .../llms/vertex_ai/gemini/transformation.py | 61 +++- .../test_vertex_ai_gemini_transformation.py | 316 ++++++++++++++++++ 2 files changed, 375 insertions(+), 2 deletions(-) diff --git a/litellm/llms/vertex_ai/gemini/transformation.py b/litellm/llms/vertex_ai/gemini/transformation.py index 0db1118a7b4..f465a54b265 100644 --- a/litellm/llms/vertex_ai/gemini/transformation.py +++ b/litellm/llms/vertex_ai/gemini/transformation.py @@ -20,6 +20,7 @@ from litellm.litellm_core_utils.prompt_templates.common_utils import ( _get_image_mime_type_from_url, ) from litellm.litellm_core_utils.prompt_templates.factory import ( + _get_thought_signature_from_tool, convert_generic_image_chunk_to_openai_image_obj, convert_to_anthropic_image_obj, convert_to_gemini_tool_call_invoke, @@ -635,6 +636,52 @@ def check_if_part_exists_in_parts(parts: List[PartType], part: PartType, exclude return False +def _collect_tool_call_thought_signatures( + assistant_msg: ChatCompletionAssistantMessage, +) -> frozenset[str]: + """Thought signatures already carried by this message's tool-call parts. + + Gemini returns each thoughtSignature on exactly one part. When the signed + part is a function call, the signature is replayed on that tool-call part + by convert_to_gemini_tool_call_invoke, so attaching the same signature to + the text part as well would send two copies and double-bill the previous + turn's reasoning tokens on gemini-3 and newer models. + + Detection deliberately calls _get_thought_signature_from_tool without the + model argument: with a gemini-3 model that helper synthesizes a dummy + signature for unsigned tool calls, which must not suppress a real + text-part signature (e.g. replaying gemini-2.5 history to a newer model). + """ + signatures: tuple[str, ...] = () + + tool_calls = assistant_msg.get("tool_calls") + if isinstance(tool_calls, list): + for tool in tool_calls: + if isinstance(tool, dict): + signature = _get_thought_signature_from_tool(tool) + if signature: + signatures += (signature,) + + function_call = assistant_msg.get("function_call") + if isinstance(function_call, dict): + signature = _get_thought_signature_from_tool({"function": function_call}) + if signature: + signatures += (signature,) + + provider_specific_fields = assistant_msg.get("provider_specific_fields") + if isinstance(provider_specific_fields, dict): + invocations = provider_specific_fields.get("server_side_tool_invocations") + if isinstance(invocations, list): + for invocation in invocations: + if isinstance(invocation, dict): + for key in ("thought_signature", "response_thought_signature"): + invocation_signature = invocation.get(key) + if isinstance(invocation_signature, str) and invocation_signature: + signatures += (invocation_signature,) + + return frozenset(signatures) + + def _gemini_convert_messages_with_history( messages: List[AllMessageValues], model: Optional[str] = None, @@ -864,8 +911,18 @@ def _gemini_convert_messages_with_history( if provider_specific_fields and isinstance(provider_specific_fields, dict): thought_signatures = provider_specific_fields.get("thought_signatures") - # If we have thought signatures, add them to the part - if thought_signatures and isinstance(thought_signatures, list) and len(thought_signatures) > 0: + # A signature that is already carried by one of this message's + # tool-call parts must not be attached to the text part too: + # Gemini bills every replayed copy as the previous turn's full + # reasoning token count on gemini-3 and newer models + tool_call_signatures = _collect_tool_call_thought_signatures(assistant_msg) + + if ( + thought_signatures + and isinstance(thought_signatures, list) + and len(thought_signatures) > 0 + and thought_signatures[0] not in tool_call_signatures + ): # Use the first signature for the text part (Gemini expects one signature per part) assistant_content.append( PartType( diff --git a/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_ai_gemini_transformation.py b/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_ai_gemini_transformation.py index d99c190c6e5..8ee8186f6bb 100644 --- a/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_ai_gemini_transformation.py +++ b/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_ai_gemini_transformation.py @@ -2097,3 +2097,319 @@ def test_multi_turn_function_calling_roles(): assert ( content["role"] == "user" ), f"Content block {i} with function_response has role='{content['role']}', expected 'user'" + + +def test_gemini_thought_signature_preservation_real_response(): + """Test that thought signatures are preserved on the text part if originally there, without dropping or duplicating (real response case).""" + from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import ( + VertexGeminiConfig, + ) + from litellm.llms.vertex_ai.gemini.transformation import ( + _gemini_convert_messages_with_history, + ) + + real_candidate = { + "content": { + "parts": [ + { + "text": "I will explain and then list files.", + "thoughtSignature": "mock_signature_from_text_part", + }, + { + "functionCall": { + "name": "list_files", + "args": {}, + } + }, + ] + } + } + + parts = real_candidate["content"]["parts"] + + content, reasoning_content = ( + VertexGeminiConfig().get_assistant_content_message(parts=parts) + ) + thought_signatures = ( + VertexGeminiConfig()._extract_thought_signatures_from_parts( + parts=parts + ) + ) + functions, tools, _ = VertexGeminiConfig._transform_parts( + parts=parts, + cumulative_tool_call_idx=0, + is_function_call=False, + ) + + msg: dict = {"role": "assistant"} + if content is not None: + msg["content"] = content + if tools: + msg["tool_calls"] = tools + if functions is not None: + msg["function_call"] = functions + if thought_signatures is not None: + msg["provider_specific_fields"] = { + "thought_signatures": thought_signatures + } + + converted_real = _gemini_convert_messages_with_history( + messages=[msg], + model="gemini-2.5-pro", + ) + + assert len(converted_real) == 1 + assert "parts" in converted_real[0] + parts_out = converted_real[0]["parts"] + assert len(parts_out) == 2 + assert "text" in parts_out[0] + assert ( + parts_out[0]["thoughtSignature"] == "mock_signature_from_text_part" + ) + assert "function_call" in parts_out[1] + assert "thoughtSignature" not in parts_out[1] + + +def test_gemini_thought_signature_deduplication_assumed_response(): + """Test that thought signatures are deduplicated and not attached to the text part if already present in the tool call (assumed response case).""" + from litellm.llms.vertex_ai.gemini.transformation import ( + _gemini_convert_messages_with_history, + ) + + pr_assumed_msg = { + "role": "assistant", + "content": "I will list the directory.", + "provider_specific_fields": { + "thought_signatures": ["mock_signature_63k"] + }, + "tool_calls": [ + { + "id": "call_1", + "type": "function", + "function": {"name": "list_files", "arguments": "{}"}, + "provider_specific_fields": { + "thought_signature": "mock_signature_63k" + }, + } + ], + } + + converted_pr = _gemini_convert_messages_with_history( + messages=[pr_assumed_msg], + model="gemini-2.5-pro", + ) + + assert len(converted_pr) == 1 + assert "parts" in converted_pr[0] + parts_out = converted_pr[0]["parts"] + assert len(parts_out) == 2 + assert "text" in parts_out[0] + assert "thoughtSignature" not in parts_out[0] + assert "function_call" in parts_out[1] + assert parts_out[1]["thoughtSignature"] == "mock_signature_63k" + + +def test_gemini_thought_signature_pure_text(): + """Test that thought signatures are preserved on the text part for responses with no tool calls.""" + from litellm.llms.vertex_ai.gemini.transformation import ( + _gemini_convert_messages_with_history, + ) + + msg = { + "role": "assistant", + "content": "Hello, I am a model.", + "provider_specific_fields": { + "thought_signatures": ["pure_text_signature"] + }, + } + + converted = _gemini_convert_messages_with_history( + messages=[msg], + model="gemini-2.5-pro", + ) + + assert len(converted) == 1 + assert "parts" in converted[0] + parts_out = converted[0]["parts"] + assert len(parts_out) == 1 + assert "text" in parts_out[0] + assert parts_out[0]["thoughtSignature"] == "pure_text_signature" + + +def test_gemini_thought_signature_pure_tool_call(): + """Test that thought signatures are preserved on the tool call for responses with no intermediate text.""" + from litellm.llms.vertex_ai.gemini.transformation import ( + _gemini_convert_messages_with_history, + ) + + msg = { + "role": "assistant", + "content": None, + "provider_specific_fields": { + "thought_signatures": ["pure_tool_signature"] + }, + "tool_calls": [ + { + "id": "call_1", + "type": "function", + "function": {"name": "list_files", "arguments": "{}"}, + "provider_specific_fields": { + "thought_signature": "pure_tool_signature" + }, + } + ], + } + + converted = _gemini_convert_messages_with_history( + messages=[msg], + model="gemini-2.5-pro", + ) + + assert len(converted) == 1 + assert "parts" in converted[0] + parts_out = converted[0]["parts"] + assert len(parts_out) == 1 + assert "function_call" in parts_out[0] + assert parts_out[0]["thoughtSignature"] == "pure_tool_signature" + + +def test_gemini_distinct_text_and_tool_signatures_are_both_preserved(): + """A text-part signature that differs from the tool-call signature must stay on the text part.""" + from litellm.llms.vertex_ai.gemini.transformation import ( + _gemini_convert_messages_with_history, + ) + + msg = { + "role": "assistant", + "content": "Some analysis.", + "provider_specific_fields": { + "thought_signatures": ["text_signature", "tool_signature"] + }, + "tool_calls": [ + { + "id": "call_1", + "type": "function", + "function": {"name": "list_files", "arguments": "{}"}, + "provider_specific_fields": {"thought_signature": "tool_signature"}, + } + ], + } + + parts = _gemini_convert_messages_with_history( + messages=[msg], model="gemini-2.5-pro" + )[0]["parts"] + + assert parts[0]["text"] == "Some analysis." + assert parts[0]["thoughtSignature"] == "text_signature" + assert "function_call" in parts[1] + assert parts[1]["thoughtSignature"] == "tool_signature" + + +def test_gemini_25_text_signature_survives_replay_to_gemini_3(): + """gemini-2.5 history (signed text, unsigned tool call) replayed to gemini-3 keeps the real + text signature; the dummy signature synthesized for the unsigned tool call must not suppress it.""" + from litellm.litellm_core_utils.prompt_templates.factory import ( + _get_dummy_thought_signature, + ) + from litellm.llms.vertex_ai.gemini.transformation import ( + _gemini_convert_messages_with_history, + ) + + msg = { + "role": "assistant", + "content": "I will list the directory.", + "provider_specific_fields": {"thought_signatures": ["real_25_signature"]}, + "tool_calls": [ + { + "id": "call_1", + "type": "function", + "function": {"name": "list_files", "arguments": "{}"}, + } + ], + } + + parts = _gemini_convert_messages_with_history(messages=[msg], model="gemini-3-pro")[ + 0 + ]["parts"] + + assert parts[0]["text"] == "I will list the directory." + assert parts[0]["thoughtSignature"] == "real_25_signature" + assert "function_call" in parts[1] + assert parts[1]["thoughtSignature"] == _get_dummy_thought_signature() + + +def test_gemini_function_call_signature_round_trip_no_duplicate(): + """End to end: a gemini-3-style response (unsigned text + signed functionCall) parsed and + re-serialized sends the signature exactly once, on the function-call part.""" + from litellm.llms.vertex_ai.gemini.transformation import ( + _gemini_convert_messages_with_history, + ) + from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import ( + VertexGeminiConfig, + ) + + response_parts = [ + {"text": "I will calculate the result for you."}, + { + "functionCall": {"name": "add_numbers", "args": {"a": 17, "b": 25}}, + "thoughtSignature": "signature_from_function_call", + }, + ] + + config = VertexGeminiConfig() + content, _ = config.get_assistant_content_message(parts=response_parts) + thought_signatures = config._extract_thought_signatures_from_parts( + parts=response_parts + ) + _, tools, _ = VertexGeminiConfig._transform_parts( + parts=response_parts, cumulative_tool_call_idx=0, is_function_call=False + ) + + msg = { + "role": "assistant", + "content": content, + "tool_calls": tools, + "provider_specific_fields": {"thought_signatures": thought_signatures}, + } + + parts = _gemini_convert_messages_with_history(messages=[msg], model="gemini-3-pro")[ + 0 + ]["parts"] + + signatures = [p["thoughtSignature"] for p in parts if "thoughtSignature" in p] + assert signatures == ["signature_from_function_call"] + assert "thoughtSignature" not in parts[0] + assert "function_call" in parts[1] + + +def test_gemini_server_side_tool_signature_not_duplicated_on_text(): + """A signature already re-injected on a server-side toolCall part is not attached to the text part again.""" + from litellm.llms.vertex_ai.gemini.transformation import ( + _gemini_convert_messages_with_history, + ) + + msg = { + "role": "assistant", + "content": "The weather in Buenos Aires is sunny.", + "provider_specific_fields": { + "thought_signatures": ["server_side_signature"], + "server_side_tool_invocations": [ + { + "tool_type": "GOOGLE_SEARCH_WEB", + "id": "abc123", + "args": {"queries": ["weather Buenos Aires"]}, + "response": {"weather": "Sunny"}, + "thought_signature": "server_side_signature", + } + ], + }, + } + + parts = _gemini_convert_messages_with_history( + messages=[msg], model="gemini-2.5-pro" + )[0]["parts"] + + text_part = next(p for p in parts if "text" in p) + assert "thoughtSignature" not in text_part + tool_call_part = next(p for p in parts if "toolCall" in p) + assert tool_call_part["thoughtSignature"] == "server_side_signature" From 35592323e24a3740ed2531a26d71145903ac9f2d Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Thu, 30 Jul 2026 16:12:08 -0700 Subject: [PATCH 02/92] fix(tag-management): drop unsupported prisma select kwarg from key lookup /tag/list returned HTTP 500 for every internal user with "LiteLLM_VerificationTokenActions.find_many() got an unexpected keyword argument 'select'". The non-admin branch scopes the tag list to keys owned by the caller, and that lookup passed select={"token": True}; prisma-client-py 0.11.0 has no select kwarg on find_many, so the call raised TypeError and the handler's except block turned it into a 500. Since the Admin UI calls /tag/list on load, Tags was broken for every non-admin user. /tag/daily/activity shares the same helper and was failing the same way The kwarg is dropped rather than replaced; the generated client has no projection API, and a user's key set is small enough that selecting all columns is not worth working around The reason this shipped green is that the existing test asserted the call was made with select={"token": True} against an AsyncMock, which accepts any keyword. The verification-token table double now binds each call against the real find_many signature, so an unsupported kwarg raises the same TypeError production does --- .../tag_management_endpoints.py | 2 - .../test_tag_management_endpoints.py | 109 +++++++++++++++--- 2 files changed, 91 insertions(+), 20 deletions(-) diff --git a/litellm/proxy/management_endpoints/tag_management_endpoints.py b/litellm/proxy/management_endpoints/tag_management_endpoints.py index 47a8670e26f..ac53f254981 100644 --- a/litellm/proxy/management_endpoints/tag_management_endpoints.py +++ b/litellm/proxy/management_endpoints/tag_management_endpoints.py @@ -96,7 +96,6 @@ class _VerificationTokenTableClient(Protocol): async def find_many( self, where: Mapping[str, object] | None = None, - select: Mapping[str, object] | None = None, ) -> "Sequence[PrismaVerificationToken]": ... @@ -157,7 +156,6 @@ async def _get_internal_user_api_keys( key_records = await _table(VerificationTokenRepository(prisma_client)).find_many( where={"user_id": user_id}, - select={"token": True}, ) user_api_keys.update(key_record.token for key_record in key_records if getattr(key_record, "token", None)) diff --git a/tests/test_litellm/proxy/management_endpoints/test_tag_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_tag_management_endpoints.py index 76ba0e3dc67..9927c56b847 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_tag_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_tag_management_endpoints.py @@ -1,11 +1,13 @@ +import inspect import json import os import sys -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional import pytest from fastapi import HTTPException from fastapi.testclient import TestClient +from prisma.actions import LiteLLM_VerificationTokenActions sys.path.insert( 0, os.path.abspath("../../../..") @@ -21,6 +23,28 @@ from litellm.types.tag_management import TagDeleteRequest, TagInfoRequest, TagNe client = TestClient(app) +class FakeVerificationTokenTable: + """Stand-in for ``prisma_client.db.litellm_verificationtoken``. + + ``AsyncMock`` swallows any keyword argument, so a plain mock cannot catch a + call that the generated prisma client would reject at runtime. This double + binds every call against the real ``find_many`` signature, so passing an + unsupported kwarg (e.g. ``select``) raises the same ``TypeError`` the proxy + surfaces as an HTTP 500. + """ + + def __init__(self, records: List[Any]): + self._records = records + self.calls: List[Dict[str, Any]] = [] + + async def find_many(self, **kwargs: Any) -> List[Any]: + inspect.signature(LiteLLM_VerificationTokenActions.find_many).bind( + self, **kwargs + ) + self.calls.append(kwargs) + return self._records + + @pytest.mark.asyncio async def test_create_and_get_tag(): """ @@ -380,6 +404,7 @@ async def test_list_tags_no_dynamic_tags(): app.dependency_overrides.clear() +@pytest.mark.asyncio async def test_internal_user_list_tags_only_returns_tags_used_by_their_keys(): """ Internal users can view tag usage, but the tag list must be scoped to tags @@ -404,9 +429,8 @@ async def test_internal_user_list_tags_only_returns_tags_used_by_their_keys(): owned_key_record = Mock() owned_key_record.token = "owned-key" - mock_db.litellm_verificationtoken.find_many = AsyncMock( - return_value=[owned_key_record] - ) + fake_token_table = FakeVerificationTokenTable([owned_key_record]) + mock_db.litellm_verificationtoken = fake_token_table mock_db.litellm_dailytagspend.group_by = AsyncMock( return_value=[ @@ -446,10 +470,9 @@ async def test_internal_user_list_tags_only_returns_tags_used_by_their_keys(): "stored-owned-tag", "dynamic-owned-tag", ] - mock_db.litellm_verificationtoken.find_many.assert_awaited_once_with( - where={"user_id": "internal-user-123"}, - select={"token": True}, - ) + assert fake_token_table.calls == [ + {"where": {"user_id": "internal-user-123"}} + ] mock_db.litellm_dailytagspend.group_by.assert_awaited_once_with( by=["tag"], where={ @@ -468,6 +491,54 @@ async def test_internal_user_list_tags_only_returns_tags_used_by_their_keys(): app.dependency_overrides.clear() +@pytest.mark.asyncio +async def test_internal_user_list_tags_does_not_500_on_unsupported_prisma_kwarg(): + """ + Regression: /tag/list returned 500 for every internal user because the + non-admin branch looked up the caller's keys with + ``find_many(select={"token": True})``, and the generated prisma client has no + ``select`` kwarg. This reproduces the reported case exactly: a freshly created + internal user with no tag spend yet, which must get an empty 200 rather than + "LiteLLM_VerificationTokenActions.find_many() got an unexpected keyword + argument 'select'". + """ + from unittest.mock import AsyncMock, Mock + + from litellm.proxy.auth.user_api_key_auth import user_api_key_auth + + mock_user_auth = UserAPIKeyAuth( + api_key="new-user-key", + user_id="brand-new-internal-user", + user_role=LitellmUserRoles.INTERNAL_USER, + ) + app.dependency_overrides[user_api_key_auth] = lambda: mock_user_auth + + try: + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma: + mock_db = Mock() + mock_prisma.db = mock_db + + key_record = Mock() + key_record.token = "new-user-key" + fake_token_table = FakeVerificationTokenTable([key_record]) + mock_db.litellm_verificationtoken = fake_token_table + + mock_db.litellm_dailytagspend.group_by = AsyncMock(return_value=[]) + mock_db.litellm_tagtable.find_many = AsyncMock(return_value=[]) + + response = client.get( + "/tag/list", headers={"Authorization": "Bearer new-user-key"} + ) + + assert response.status_code == 200, response.text + assert response.json() == [] + assert fake_token_table.calls == [ + {"where": {"user_id": "brand-new-internal-user"}} + ] + finally: + app.dependency_overrides.clear() + + @pytest.mark.asyncio async def test_list_tags_with_date_range_filters_dynamic_tags(): """ @@ -537,9 +608,8 @@ async def test_internal_user_tag_daily_activity_is_scoped_to_their_keys(): owned_key_record = Mock() owned_key_record.token = "owned-key" - mock_db.litellm_verificationtoken.find_many = AsyncMock( - return_value=[owned_key_record] - ) + fake_token_table = FakeVerificationTokenTable([owned_key_record]) + mock_db.litellm_verificationtoken = fake_token_table mock_get_daily_activity.return_value = "daily-activity-response" result = await get_tag_daily_activity( @@ -549,6 +619,7 @@ async def test_internal_user_tag_daily_activity_is_scoped_to_their_keys(): ) assert result == "daily-activity-response" + assert fake_token_table.calls == [{"where": {"user_id": "internal-user-123"}}] mock_get_daily_activity.assert_awaited_once() assert mock_get_daily_activity.await_args.kwargs["api_key"] == ["owned-key"] @@ -583,9 +654,8 @@ async def test_internal_user_tag_daily_activity_rejects_unowned_api_key_filter() owned_key_record = Mock() owned_key_record.token = "owned-key" - mock_db.litellm_verificationtoken.find_many = AsyncMock( - return_value=[owned_key_record] - ) + fake_token_table = FakeVerificationTokenTable([owned_key_record]) + mock_db.litellm_verificationtoken = fake_token_table result = await get_tag_daily_activity( start_date="2025-01-01", end_date="2025-01-31", @@ -593,6 +663,7 @@ async def test_internal_user_tag_daily_activity_rejects_unowned_api_key_filter() user_api_key_dict=mock_user_auth, ) + assert fake_token_table.calls == [{"where": {"user_id": "internal-user-123"}}] assert result.results == [] assert result.metadata.total_spend == 0 assert result.metadata.total_api_requests == 0 @@ -626,7 +697,8 @@ async def test_internal_user_tag_daily_activity_scopes_to_current_key_without_us ): mock_db = Mock() mock_prisma.db = mock_db - mock_db.litellm_verificationtoken.find_many = AsyncMock(return_value=[]) + fake_token_table = FakeVerificationTokenTable([]) + mock_db.litellm_verificationtoken = fake_token_table mock_get_daily_activity.return_value = "daily-activity-response" result = await get_tag_daily_activity( @@ -636,7 +708,7 @@ async def test_internal_user_tag_daily_activity_scopes_to_current_key_without_us ) assert result == "daily-activity-response" - mock_db.litellm_verificationtoken.find_many.assert_not_awaited() + assert fake_token_table.calls == [] mock_get_daily_activity.assert_awaited_once() assert mock_get_daily_activity.await_args.kwargs["api_key"] == [ "current-owned-key" @@ -669,7 +741,8 @@ async def test_internal_user_tag_daily_activity_without_any_scoped_keys_returns_ ): mock_db = Mock() mock_prisma.db = mock_db - mock_db.litellm_verificationtoken.find_many = AsyncMock(return_value=[]) + fake_token_table = FakeVerificationTokenTable([]) + mock_db.litellm_verificationtoken = fake_token_table result = await get_tag_daily_activity( start_date="2025-01-01", @@ -680,7 +753,7 @@ async def test_internal_user_tag_daily_activity_without_any_scoped_keys_returns_ assert result.results == [] assert result.metadata.total_spend == 0 assert result.metadata.total_api_requests == 0 - mock_db.litellm_verificationtoken.find_many.assert_not_awaited() + assert fake_token_table.calls == [] mock_get_daily_activity.assert_not_awaited() From 1d40f2a707a96be7b4d91587bb7ba0e834cd805d Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Thu, 30 Jul 2026 19:22:22 -0700 Subject: [PATCH 03/92] feat(ui): add sorting, filtering and search to the budgets page Move the budgets table onto the paged management list route so sorting, filtering and search happen server-side instead of over whichever rows happened to be in memory. Adds useResourceList, a generic hook that owns page, page_size, sort, q and filters for a server-driven table, folds them into one JSON:API query and returns exactly the props DataTable's server modes want. Budgets is its first consumer. The budget id column now renders in full with a copy button instead of a fixed-width cell, and the table gains Reset and Created columns. --- ui/litellm-dashboard/eslint-suppressions.json | 5 - .../budgets/_components/BudgetTable.test.tsx | 160 +++++-- .../budgets/_components/BudgetTable.tsx | 246 ++++++++++- .../_components/BudgetTableColumns.tsx | 62 ++- .../budgets/_components/budget_panel.test.tsx | 413 ++++++++++-------- .../budgets/_components/budget_panel.tsx | 31 +- .../hooks/budgets/budgetFilters.test.ts | 59 +++ .../hooks/budgets/budgetFilters.ts | 91 ++++ .../(dashboard)/hooks/budgets/useBudgets.ts | 52 ++- .../hooks/common/useResourceList.test.tsx | 161 +++++++ .../hooks/common/useResourceList.ts | 142 ++++++ 11 files changed, 1144 insertions(+), 278 deletions(-) create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/hooks/budgets/budgetFilters.test.ts create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/hooks/budgets/budgetFilters.ts create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/hooks/common/useResourceList.test.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/hooks/common/useResourceList.ts diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index 6819b2851f5..229d6c797c2 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -126,11 +126,6 @@ "count": 2 } }, - "src/app/(dashboard)/budgets/_components/budget_panel.test.tsx": { - "unused-imports/no-unused-imports": { - "count": 2 - } - }, "src/app/(dashboard)/budgets/_components/budget_panel.tsx": { "local/filename-pascal-case": { "count": 1 diff --git a/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/BudgetTable.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/BudgetTable.test.tsx index 2b97bcbc072..0c485adf4f2 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/BudgetTable.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/BudgetTable.test.tsx @@ -1,22 +1,58 @@ -import { screen, within } from "@testing-library/react"; +import { screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { renderWithProviders } from "@/../tests/test-utils"; +import { renderWithProviders, testQueryClient } from "@/../tests/test-utils"; import BudgetTable from "./BudgetTable"; -import { budgetItem } from "@/app/(dashboard)/hooks/budgets/useBudgets"; +import type { budgetItem } from "@/app/(dashboard)/hooks/budgets/useBudgets"; +import type { ResourceListResult } from "@/app/(dashboard)/hooks/common/useResourceList"; +import { ApiError } from "@/lib/http/client"; + +const { copyToClipboardMock } = vi.hoisted(() => ({ copyToClipboardMock: vi.fn() })); + +vi.mock("@/utils/dataUtils", async (importOriginal) => ({ + ...(await importOriginal()), + copyToClipboard: copyToClipboardMock, +})); const makeBudget = (overrides: Partial = {}): budgetItem => ({ budget_id: "budget-1", max_budget: 100, + soft_budget: null, tpm_limit: 1000, rpm_limit: 10, + budget_duration: "30d", + budget_reset_at: null, + created_at: "2024-01-01T00:00:00Z", updated_at: "2024-01-01T00:00:00Z", ...overrides, }); -const defaultProps = { - budgets: [makeBudget()], +const makeList = (overrides: Partial> = {}): ResourceListResult => ({ + rows: [makeBudget()], + rowCount: 1, isLoading: false, + isFetching: false, + error: null, + refetch: vi.fn(), + sorting: [{ id: "created_at", desc: true }], + onSortingChange: vi.fn(), + pagination: { pageIndex: 0, pageSize: 50 }, + onPaginationChange: vi.fn(), + columnFilters: [], + onColumnFiltersChange: vi.fn(), + searchValue: "", + onSearchChange: vi.fn(), + ...overrides, +}); + +const FORBIDDEN_PROBLEM = { + type: "about:blank", + title: "Forbidden", + status: 403, + detail: "Only proxy admins can view budgets", +}; + +const defaultProps = { canModify: true, onEditClick: vi.fn(), onDeleteClick: vi.fn(), @@ -25,72 +61,134 @@ const defaultProps = { describe("BudgetTable", () => { beforeEach(() => { vi.clearAllMocks(); + testQueryClient.clear(); }); it("should display budget information", () => { - renderWithProviders(); + renderWithProviders(); expect(screen.getByText("budget-1")).toBeInTheDocument(); expect(screen.getByText("$100.00")).toBeInTheDocument(); expect(screen.getByText("1000")).toBeInTheDocument(); expect(screen.getByText("10")).toBeInTheDocument(); }); - it("should render the budget id without a fixed character-count clamp", () => { + it("should render the reset column with the friendly duration label", () => { + renderWithProviders(); + expect(screen.getByText("monthly")).toBeInTheDocument(); + }); + + it("should render 'Not set' when a budget has no reset duration", () => { + const list = makeList({ rows: [makeBudget({ budget_duration: null })] }); + renderWithProviders(); + expect(screen.getByText("Not set")).toBeInTheDocument(); + }); + + it("should render the budget id in full, with no truncation", () => { const budgetId = "ecc1869c-6231-4380-a56d-1a0be457477d"; - renderWithProviders(); + const list = makeList({ rows: [makeBudget({ budget_id: budgetId })] }); + renderWithProviders(); const idCell = screen.getByText(budgetId); + expect(idCell.className).not.toContain("truncate"); expect(idCell.className).not.toMatch(/max-w-\[\d+(ch|rem|px)\]/); - expect(idCell.className).toContain("max-w-full"); - expect(idCell.className).toContain("truncate"); + }); + + it("should keep the budget id on a single line", () => { + const budgetId = "ecc1869c-6231-4380-a56d-1a0be457477d"; + const list = makeList({ rows: [makeBudget({ budget_id: budgetId })] }); + renderWithProviders(); + expect(screen.getByText(budgetId).className).toContain("whitespace-nowrap"); + }); + + it("should copy the budget id from the cell's copy button", async () => { + const user = userEvent.setup(); + const budgetId = "ecc1869c-6231-4380-a56d-1a0be457477d"; + const list = makeList({ rows: [makeBudget({ budget_id: budgetId })] }); + renderWithProviders(); + await user.click(screen.getByRole("button", { name: "Copy ID" })); + expect(copyToClipboardMock).toHaveBeenCalledWith(budgetId); + }); + + it("should offer sorting on every backend-sortable column", async () => { + renderWithProviders(); + for (const field of ["budget_id", "max_budget", "tpm_limit", "rpm_limit", "created_at"]) { + expect(screen.getByTestId(`sort-header-${field}`)).toBeInTheDocument(); + } + }); + + it("should not make the reset column sortable", () => { + renderWithProviders(); + expect(screen.queryByTestId("sort-header-budget_duration")).not.toBeInTheDocument(); + expect(screen.getByText("Reset")).toBeInTheDocument(); + }); + + it("should ask the list for a new sort when a sortable header is clicked", async () => { + const user = userEvent.setup(); + const onSortingChange = vi.fn(); + renderWithProviders(); + await user.click(screen.getByTestId("sort-header-max_budget")); + expect(onSortingChange).toHaveBeenCalled(); }); it("should show n/a for missing rate limits and Unlimited for a missing max budget", () => { - renderWithProviders( - , - ); + const list = makeList({ rows: [makeBudget({ max_budget: null, tpm_limit: null, rpm_limit: null })] }); + renderWithProviders(); expect(screen.getAllByText("n/a")).toHaveLength(2); expect(screen.getByText("Unlimited")).toBeInTheDocument(); }); - it("should sort budgets by updated_at descending", () => { - const budgets = [ - makeBudget({ budget_id: "budget-old", updated_at: "2024-01-01T00:00:00Z" }), - makeBudget({ budget_id: "budget-new", updated_at: "2024-06-01T00:00:00Z" }), - ]; - renderWithProviders(); - const rows = screen.getAllByRole("row").slice(1); - expect(within(rows[0]).getByText("budget-new")).toBeInTheDocument(); - expect(within(rows[1]).getByText("budget-old")).toBeInTheDocument(); - }); - it("should call onEditClick from the actions menu", async () => { const user = userEvent.setup(); - renderWithProviders(); + const list = makeList(); + renderWithProviders(); await user.click(screen.getByTestId("budget-actions-budget-1")); await user.click(await screen.findByTestId("budget-action-edit")); - expect(defaultProps.onEditClick).toHaveBeenCalledWith(defaultProps.budgets[0]); + expect(defaultProps.onEditClick).toHaveBeenCalledWith(list.rows[0]); }); it("should call onDeleteClick from the actions menu", async () => { const user = userEvent.setup(); - renderWithProviders(); + const list = makeList(); + renderWithProviders(); await user.click(screen.getByTestId("budget-actions-budget-1")); await user.click(await screen.findByTestId("budget-action-delete")); - expect(defaultProps.onDeleteClick).toHaveBeenCalledWith(defaultProps.budgets[0]); + expect(defaultProps.onDeleteClick).toHaveBeenCalledWith(list.rows[0]); }); it("should not render the actions menu when the user cannot modify budgets", () => { - renderWithProviders(); + renderWithProviders(); expect(screen.queryByTestId("budget-actions-budget-1")).not.toBeInTheDocument(); }); it("should show skeleton rows when loading", () => { - renderWithProviders(); + renderWithProviders(); expect(screen.getAllByTestId("skeleton-row").length).toBeGreaterThan(0); }); it("should show the empty state when there are no budgets", () => { - renderWithProviders(); + renderWithProviders(); expect(screen.getByText("No budgets yet")).toBeInTheDocument(); }); + + it("should tell the user their search matched nothing rather than that no budgets exist", () => { + const list = makeList({ rows: [], rowCount: 0, searchValue: "nope" }); + renderWithProviders(); + expect(screen.getByText("No matching budgets")).toBeInTheDocument(); + }); + + it("should render an access-denied state for a 403 instead of an empty table", () => { + const error = new ApiError("Only proxy admins can view budgets", 403, FORBIDDEN_PROBLEM); + const list = makeList({ rows: [], rowCount: 0, error }); + const { container } = renderWithProviders(); + expect(screen.getByText("You do not have access to budgets")).toBeInTheDocument(); + expect(screen.queryByText("No budgets yet")).not.toBeInTheDocument(); + expect(container.querySelector(".lucide-shield-alert")).not.toBeNull(); + }); + + it("should surface the problem detail for a non-403 failure", () => { + const error = new ApiError("budget store unavailable", 500, null); + const list = makeList({ rows: [], rowCount: 0, error }); + renderWithProviders(); + expect(screen.getByText("Could not load budgets")).toBeInTheDocument(); + expect(screen.getByText("budget store unavailable")).toBeInTheDocument(); + }); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/BudgetTable.tsx b/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/BudgetTable.tsx index 4bc06425f80..76355c874f8 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/BudgetTable.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/BudgetTable.tsx @@ -1,55 +1,269 @@ "use client"; -import { Inbox } from "lucide-react"; -import React, { useMemo } from "react"; +import { Inbox, ShieldAlert } from "lucide-react"; +import React, { useMemo, useState } from "react"; -import { DataTable } from "@/components/shared/DataTable"; -import { budgetItem } from "@/app/(dashboard)/hooks/budgets/useBudgets"; +import { + BUDGET_DURATION_FILTER_OPTIONS, + BUDGET_DURATION_UNSET, + type CreatedAtFilterValue, + type MaxBudgetFilterValue, +} from "@/app/(dashboard)/hooks/budgets/budgetFilters"; +import type { budgetItem } from "@/app/(dashboard)/hooks/budgets/useBudgets"; +import type { ResourceListResult } from "@/app/(dashboard)/hooks/common/useResourceList"; +import { + DataTable, + DataTableFilterDrawer, + DataTableFilterField, + DataTableToolbar, + type FilterDraft, +} from "@/components/shared/DataTable"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { ApiError } from "@/lib/http/client"; import { getBudgetTableColumns } from "./BudgetTableColumns"; interface BudgetTableProps { - budgets: budgetItem[]; - isLoading: boolean; + list: ResourceListResult; canModify: boolean; onEditClick: (budget: budgetItem) => void; onDeleteClick: (budget: budgetItem) => void; } -function EmptyState() { +const PAGE_SIZE_OPTIONS = [25, 50, 100]; + +const FILTER_LABELS: Record = { + budget_duration: "Reset", + max_budget: "Max Budget", + created_at: "Created", +}; + +const durationLabel = (value: string): string => + BUDGET_DURATION_FILTER_OPTIONS.find((option) => option.value === value)?.label ?? value; + +const formatFilterValue = (columnId: string, value: unknown): string => { + if (columnId === "budget_duration") { + return (Array.isArray(value) ? value : []).map((entry) => durationLabel(String(entry))).join(", "); + } + if (columnId === "max_budget") { + const { min, max, unlimitedOnly } = (value ?? {}) as MaxBudgetFilterValue; + return unlimitedOnly === true ? "Unlimited only" : `${min ? `$${min}` : "any"} to ${max ? `$${max}` : "any"}`; + } + if (columnId === "created_at") { + const { from, to } = (value ?? {}) as CreatedAtFilterValue; + return `${from || "any"} to ${to || "any"}`; + } + return String(value); +}; + +/** The drawer keeps any non-empty object as an active filter, so collapse a blank draft to nothing. */ +const normalizeMaxBudget = (draft: MaxBudgetFilterValue): MaxBudgetFilterValue | undefined => { + if (draft.unlimitedOnly === true) { + return { unlimitedOnly: true }; + } + const min = draft.min?.trim() ?? ""; + const max = draft.max?.trim() ?? ""; + if (min === "" && max === "") { + return undefined; + } + return { ...(min === "" ? {} : { min }), ...(max === "" ? {} : { max }) }; +}; + +const normalizeCreatedAt = (draft: CreatedAtFilterValue): CreatedAtFilterValue | undefined => { + const from = draft.from ?? ""; + const to = draft.to ?? ""; + if (from === "" && to === "") { + return undefined; + } + return { ...(from === "" ? {} : { from }), ...(to === "" ? {} : { to }) }; +}; + +function EmptyState({ hasQuery }: { hasQuery: boolean }) { return (
-
No budgets yet
+
{hasQuery ? "No matching budgets" : "No budgets yet"}
- Create a budget to set spend, TPM and RPM limits for customers. + {hasQuery + ? "No budget matches your search or filters." + : "Create a budget to set spend, TPM and RPM limits for customers."}
); } -const BudgetTable: React.FC = ({ budgets, isLoading, canModify, onEditClick, onDeleteClick }) => { - const rows = useMemo( - () => [...budgets].sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()), - [budgets], +function ErrorState({ error }: { error: Error }) { + const forbidden = error instanceof ApiError && error.status === 403; + return ( +
+
+ +
+
+ {forbidden ? "You do not have access to budgets" : "Could not load budgets"} +
+
+ {forbidden ? "Ask a proxy admin to grant you the admin viewer role." : error.message} +
+
); +} + +/** "Not set" and the concrete durations are exclusive; see serializeBudgetFilters for why. */ +function DurationFilter({ selected, onChange }: { selected: string[]; onChange: (selected: string[]) => void }) { + const toggle = (value: string, checked: boolean): void => { + if (!checked) { + onChange(selected.filter((entry) => entry !== value)); + return; + } + const kept = value === BUDGET_DURATION_UNSET ? [] : selected.filter((entry) => entry !== BUDGET_DURATION_UNSET); + onChange([...kept, value]); + }; + + return ( +
+ {BUDGET_DURATION_FILTER_OPTIONS.map((option) => ( + + ))} +
+ ); +} + +function BudgetFilterFields({ get, set }: FilterDraft) { + const maxBudget = (get("max_budget") as MaxBudgetFilterValue | undefined) ?? {}; + const created = (get("created_at") as CreatedAtFilterValue | undefined) ?? {}; + const unlimitedOnly = maxBudget.unlimitedOnly === true; + + return ( + <> + + set("budget_duration", selected)} + /> + + +
+ set("max_budget", normalizeMaxBudget({ ...maxBudget, min: event.target.value }))} + placeholder="Min" + aria-label="Minimum max budget" + data-testid="budget-filter-max-budget-min" + /> + set("max_budget", normalizeMaxBudget({ ...maxBudget, max: event.target.value }))} + placeholder="Max" + aria-label="Maximum max budget" + data-testid="budget-filter-max-budget-max" + /> +
+ +
+ +
+ set("created_at", normalizeCreatedAt({ ...created, from: event.target.value }))} + aria-label="Created from" + data-testid="budget-filter-created-from" + /> + set("created_at", normalizeCreatedAt({ ...created, to: event.target.value }))} + aria-label="Created to" + data-testid="budget-filter-created-to" + /> +
+
+ + ); +} + +const BudgetTable: React.FC = ({ list, canModify, onEditClick, onDeleteClick }) => { + const [filtersOpen, setFiltersOpen] = useState(false); const columns = useMemo( () => getBudgetTableColumns({ canModify, onEditClick, onDeleteClick }), [canModify, onEditClick, onDeleteClick], ); + const hasQuery = list.searchValue.trim() !== "" || list.columnFilters.length > 0; + const emptyMessage = list.error === null ? : ; + return ( budget.budget_id || String(index)} - isLoading={isLoading} + sortingMode="server" + sorting={list.sorting} + onSortingChange={list.onSortingChange} + paginationMode="server" + pagination={list.pagination} + onPaginationChange={list.onPaginationChange} + rowCount={list.rowCount} + pageSizeOptions={PAGE_SIZE_OPTIONS} + filterMode="server" + columnFilters={list.columnFilters} + onColumnFiltersChange={list.onColumnFiltersChange} + isLoading={list.isLoading} loadingMessage="Loading budgets…" - noDataMessage={} + noDataMessage={emptyMessage} size="compact" + toolbar={(table) => ( + <> + setFiltersOpen(true)} + onRefresh={list.refetch} + isRefreshing={list.isFetching} + filterLabels={FILTER_LABELS} + formatFilterValue={formatFilterValue} + /> + + {(draft) => } + + + )} /> ); }; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/BudgetTableColumns.tsx b/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/BudgetTableColumns.tsx index e3fbc9dba08..8cca2caf214 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/BudgetTableColumns.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/BudgetTableColumns.tsx @@ -1,11 +1,13 @@ "use client"; -import { ColumnDef } from "@tanstack/react-table"; +import { ColumnDef, FilterFn } from "@tanstack/react-table"; import { MoreHorizontal, Pencil, Trash2 } from "lucide-react"; -import { IdCell, MoneyCell } from "@/components/shared/table_cells"; -import { budgetItem } from "@/app/(dashboard)/hooks/budgets/useBudgets"; +import { DataTableSortHeader } from "@/components/shared/DataTable"; +import { DateCell, IdCell, MoneyCell } from "@/components/shared/table_cells"; +import type { budgetItem } from "@/app/(dashboard)/hooks/budgets/useBudgets"; import { buttonVariants } from "@/components/ui/button"; +import { getBudgetDurationLabel } from "@/components/common_components/budget_duration_dropdown"; import { DropdownMenu, DropdownMenuContent, @@ -15,6 +17,15 @@ import { } from "@/components/ui/dropdown-menu"; import { cn } from "@/lib/cva.config"; +/** + * Filtering happens on the server, so this never runs as a predicate. It exists to override + * TanStack's auto-remove heuristic, which infers a filter shape from the column's first cell + * and silently discards a filter whose value is not that shape (a range object on a numeric + * column, for instance). + */ +const serverFilter: FilterFn = () => true; +serverFilter.autoRemove = () => false; + function RateLimitCell({ value }: { value: number | null }) { if (value == null) { return n/a; @@ -22,6 +33,13 @@ function RateLimitCell({ value }: { value: number | null }) { return {value}; } +function BudgetDurationCell({ value }: { value: string | null }) { + if (!value) { + return Not set; + } + return {getBudgetDurationLabel(value)}; +} + interface BudgetRowActionsProps { budget: budgetItem; onEditClick: (budget: budgetItem) => void; @@ -72,38 +90,56 @@ export const getBudgetTableColumns = ({ id: "budget_id", accessorKey: "budget_id", meta: { title: "Budget ID" }, - header: "Budget ID", - size: 220, - enableSorting: false, - cell: ({ row }) => , + header: ({ column }) => , + cell: ({ row }) => ( + + ), }, { id: "max_budget", accessorKey: "max_budget", + filterFn: serverFilter, meta: { title: "Max Budget", numeric: true }, - header: "Max Budget", + header: ({ column }) => , size: 120, - enableSorting: false, cell: ({ row }) => , }, { id: "tpm_limit", accessorKey: "tpm_limit", meta: { title: "TPM", numeric: true }, - header: "TPM", + header: ({ column }) => , size: 100, - enableSorting: false, cell: ({ row }) => , }, { id: "rpm_limit", accessorKey: "rpm_limit", meta: { title: "RPM", numeric: true }, - header: "RPM", + header: ({ column }) => , size: 100, - enableSorting: false, cell: ({ row }) => , }, + { + id: "budget_duration", + accessorKey: "budget_duration", + filterFn: serverFilter, + meta: { title: "Reset" }, + // "7d"/"30d" sort lexicographically, not chronologically, so the route does not offer it. + enableSorting: false, + header: ({ column }) => , + size: 110, + cell: ({ row }) => , + }, + { + id: "created_at", + accessorKey: "created_at", + filterFn: serverFilter, + meta: { title: "Created" }, + header: ({ column }) => , + size: 160, + cell: ({ row }) => , + }, ...(canModify ? [ { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/budget_panel.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/budget_panel.test.tsx index 392616f1935..46f72cd8886 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/budget_panel.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/budget_panel.test.tsx @@ -1,217 +1,254 @@ -import { fireEvent, render, waitFor, screen } from "@testing-library/react"; -import { act } from "@testing-library/react"; -import userEvent from "@testing-library/user-event"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { afterEach, describe, expect, it, vi } from "vitest"; +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import React from "react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { ApiError } from "@/lib/http/client"; + import BudgetPanel from "./budget_panel"; -const mockBudgets = [ - { - budget_id: "budget-1", - max_budget: 100, - rpm_limit: 10, - tpm_limit: 1000, - updated_at: "2024-01-01T00:00:00Z", - }, -]; - -vi.mock("@/app/(dashboard)/hooks/budgets/useBudgets", () => ({ - useBudgets: vi.fn().mockReturnValue({ data: [], isLoading: false }), - useDeleteBudget: vi.fn().mockReturnValue({ mutateAsync: vi.fn(), isPending: false }), - useCreateBudget: vi.fn().mockReturnValue({ mutateAsync: vi.fn() }), - useUpdateBudget: vi.fn().mockReturnValue({ mutateAsync: vi.fn() }), +const { getMock, budgetDeleteMock } = vi.hoisted(() => ({ + getMock: vi.fn(), + budgetDeleteMock: vi.fn(), })); -import { - useBudgets, - useDeleteBudget, - useCreateBudget, - useUpdateBudget, -} from "@/app/(dashboard)/hooks/budgets/useBudgets"; +vi.mock("@/components/networking", () => ({ + apiClient: { get: getMock }, + budgetCreateCall: vi.fn(), + budgetUpdateCall: vi.fn(), + budgetDeleteCall: budgetDeleteMock, + getProxyBaseUrl: () => "", +})); -const createQueryClient = () => - new QueryClient({ - defaultOptions: { queries: { retry: false, gcTime: 0 } }, - }); +vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({ + default: () => ({ accessToken: "sk-test", userRole: "Admin", userId: "u1" }), +})); -function renderWithProviders(ui: React.ReactElement) { - const qc = createQueryClient(); - return render({ui}); +vi.mock("@/components/molecules/notifications_manager", () => ({ + default: { success: vi.fn(), info: vi.fn(), fromBackend: vi.fn() }, +})); + +interface BudgetSeed { + budget_id: string; + max_budget: number | null; + budget_duration: string | null; } +const budgetRow = (seed: BudgetSeed) => ({ + soft_budget: null, + tpm_limit: 1000, + rpm_limit: 10, + budget_reset_at: null, + created_at: "2026-01-01T00:00:00Z", + updated_at: "2026-01-01T00:00:00Z", + ...seed, +}); + +const FORBIDDEN_PROBLEM = { + type: "about:blank", + title: "Forbidden", + status: 403, + detail: "Only proxy admins can view budgets", +}; + +const DEFAULT_ROWS = [ + budgetRow({ budget_id: "ecc1869c-6231-4380-a56d-1a0be457477d", max_budget: 100, budget_duration: "30d" }), +]; + +const respondWith = (rows: ReturnType[], totalCount: number) => { + getMock.mockResolvedValue({ + data: rows, + meta: { total_count: totalCount, page: 1, page_size: 50, total_pages: Math.ceil(totalCount / 50) }, + }); +}; + +type QueryRecord = Record; + +const queries = (): QueryRecord[] => getMock.mock.calls.map((call) => (call[1] as { query: QueryRecord }).query); +const lastQuery = (): QueryRecord => queries()[queries().length - 1]; +const paths = (): string[] => getMock.mock.calls.map((call) => String(call[0])); + +const renderPanel = () => { + const client = new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 0 } } }); + return render( + + + , + ); +}; + +const openFilters = async (user: ReturnType) => { + await user.click(screen.getByTestId("datatable-filters-trigger")); + await screen.findByTestId("filter-drawer-body"); +}; + describe("Budget Panel", () => { - afterEach(() => { + beforeEach(() => { vi.clearAllMocks(); + respondWith(DEFAULT_ROWS, 1); }); - it("should render the budget panel and load budgets", async () => { - vi.mocked(useBudgets).mockReturnValue({ - data: mockBudgets, - isLoading: false, - } as any); - - renderWithProviders(); - - await waitFor(() => { - expect(screen.getByText("Create a budget to assign to customers.")).toBeInTheDocument(); - expect(screen.getByText("budget-1")).toBeInTheDocument(); - }); + it("loads the first page of budgets, newest first", async () => { + renderPanel(); + await waitFor(() => expect(getMock).toHaveBeenCalled()); + expect(paths()[0]).toBe("/management/v1/budgets"); + expect(queries()[0]).toEqual({ page: 1, page_size: 50, sort: "-created_at" }); + expect(await screen.findByText("ecc1869c-6231-4380-a56d-1a0be457477d")).toBeInTheDocument(); }); - it("should open delete modal from the actions menu", async () => { + it("asks the server to sort when a sortable header is clicked", async () => { const user = userEvent.setup(); - vi.mocked(useBudgets).mockReturnValue({ - data: [ - { - budget_id: "budget-to-delete", - max_budget: 200, - rpm_limit: 20, - tpm_limit: 2000, - updated_at: "2024-01-02T00:00:00Z", - }, - ], - isLoading: false, - } as any); + renderPanel(); + await waitFor(() => expect(getMock).toHaveBeenCalled()); - renderWithProviders(); + await user.click(screen.getByTestId("sort-header-max_budget")); + await waitFor(() => expect(lastQuery().sort).toBe("-max_budget")); - await waitFor(() => { - expect(screen.getByText("budget-to-delete")).toBeInTheDocument(); - }); + await user.click(screen.getByTestId("sort-header-max_budget")); + await waitFor(() => expect(lastQuery().sort).toBe("max_budget")); - await user.click(screen.getByTestId("budget-actions-budget-to-delete")); + await user.click(screen.getByTestId("sort-header-budget_id")); + await waitFor(() => expect(lastQuery().sort).toBe("budget_id")); + }); + + it("searches on budget_id with a debounced q", async () => { + const user = userEvent.setup(); + renderPanel(); + await waitFor(() => expect(getMock).toHaveBeenCalled()); + + await user.type(screen.getByTestId("datatable-search"), "ecc"); + await waitFor(() => expect(lastQuery().q).toBe("ecc")); + expect(queries().some((query) => query.q === "e" || query.q === "ec")).toBe(false); + }); + + it("filters by reset duration and clears it again", async () => { + const user = userEvent.setup(); + renderPanel(); + await waitFor(() => expect(getMock).toHaveBeenCalled()); + + await openFilters(user); + await user.click(screen.getByTestId("budget-filter-duration-7d")); + await user.click(screen.getByTestId("budget-filter-duration-30d")); + await user.click(screen.getByTestId("filter-drawer-apply")); + + await waitFor(() => expect(lastQuery()["filter[budget_duration][in]"]).toBe("7d,30d")); + + await user.click(screen.getByTestId("filter-chip-remove-budget_duration")); + await waitFor(() => expect(lastQuery()).not.toHaveProperty("filter[budget_duration][in]")); + }); + + it("filters by budgets with no reset duration", async () => { + const user = userEvent.setup(); + renderPanel(); + await waitFor(() => expect(getMock).toHaveBeenCalled()); + + await openFilters(user); + await user.click(screen.getByTestId("budget-filter-duration-__unset__")); + await user.click(screen.getByTestId("filter-drawer-apply")); + + await waitFor(() => expect(lastQuery()["filter[budget_duration][is_null]"]).toBe("true")); + expect(lastQuery()).not.toHaveProperty("filter[budget_duration][in]"); + }); + + it("filters by a max budget range and clears it again", async () => { + const user = userEvent.setup(); + renderPanel(); + await waitFor(() => expect(getMock).toHaveBeenCalled()); + + await openFilters(user); + await user.type(screen.getByTestId("budget-filter-max-budget-min"), "10"); + await user.type(screen.getByTestId("budget-filter-max-budget-max"), "500"); + await user.click(screen.getByTestId("filter-drawer-apply")); + + await waitFor(() => expect(lastQuery()["filter[max_budget][gte]"]).toBe("10")); + expect(lastQuery()["filter[max_budget][lte]"]).toBe("500"); + + await user.click(screen.getByTestId("datatable-clear-filters")); + await waitFor(() => expect(lastQuery()).not.toHaveProperty("filter[max_budget][gte]")); + expect(lastQuery()).not.toHaveProperty("filter[max_budget][lte]"); + }); + + it("filters to unlimited budgets only", async () => { + const user = userEvent.setup(); + renderPanel(); + await waitFor(() => expect(getMock).toHaveBeenCalled()); + + await openFilters(user); + await user.type(screen.getByTestId("budget-filter-max-budget-min"), "10"); + await user.click(screen.getByTestId("budget-filter-max-budget-unlimited")); + await user.click(screen.getByTestId("filter-drawer-apply")); + + await waitFor(() => expect(lastQuery()["filter[max_budget][is_null]"]).toBe("true")); + expect(lastQuery()).not.toHaveProperty("filter[max_budget][gte]"); + }); + + it("filters by a created date range covering whole local days", async () => { + const user = userEvent.setup(); + renderPanel(); + await waitFor(() => expect(getMock).toHaveBeenCalled()); + + await openFilters(user); + await user.type(screen.getByTestId("budget-filter-created-from"), "2026-01-05"); + await user.type(screen.getByTestId("budget-filter-created-to"), "2026-01-06"); + await user.click(screen.getByTestId("filter-drawer-apply")); + + await waitFor(() => + expect(lastQuery()["filter[created_at][gte]"]).toBe(new Date("2026-01-05T00:00:00.000").toISOString()), + ); + expect(lastQuery()["filter[created_at][lte]"]).toBe(new Date("2026-01-06T23:59:59.999").toISOString()); + }); + + it("pages through the results and changes page size", async () => { + const user = userEvent.setup(); + respondWith(DEFAULT_ROWS, 400); + renderPanel(); + await waitFor(() => expect(getMock).toHaveBeenCalled()); + + await user.click(screen.getByTestId("pagination-next")); + await waitFor(() => expect(lastQuery().page).toBe(2)); + expect(lastQuery().page_size).toBe(50); + + await user.click(screen.getByTestId("pagination-page-size")); + await user.click(await screen.findByRole("option", { name: "25" })); + await waitFor(() => expect(lastQuery().page_size).toBe(25)); + }); + + it("renders an access-denied state when the route rejects the caller", async () => { + getMock.mockRejectedValue(new ApiError("Only proxy admins can view budgets", 403, FORBIDDEN_PROBLEM)); + renderPanel(); + expect(await screen.findByText("You do not have access to budgets")).toBeInTheDocument(); + expect(screen.queryByText("No budgets yet")).not.toBeInTheDocument(); + }); + + it("deletes a budget from the actions menu", async () => { + const user = userEvent.setup(); + budgetDeleteMock.mockResolvedValue(undefined); + renderPanel(); + await screen.findByText("ecc1869c-6231-4380-a56d-1a0be457477d"); + + await user.click(screen.getByTestId("budget-actions-ecc1869c-6231-4380-a56d-1a0be457477d")); await user.click(await screen.findByTestId("budget-action-delete")); + await screen.findByText("Delete Budget?"); + await user.click(screen.getByRole("button", { name: /^delete$/i })); - await waitFor(() => { - expect(screen.getByText("Delete Budget?")).toBeInTheDocument(); - }); + await waitFor(() => + expect(budgetDeleteMock).toHaveBeenCalledWith("sk-test", "ecc1869c-6231-4380-a56d-1a0be457477d"), + ); }); - it("should successfully delete a budget", async () => { + it("refetches the current page after a delete", async () => { const user = userEvent.setup(); - const deleteMutateAsync = vi.fn().mockResolvedValue(undefined); - vi.mocked(useBudgets).mockReturnValue({ - data: [ - { - budget_id: "budget-to-delete", - max_budget: 200, - rpm_limit: 20, - tpm_limit: 2000, - updated_at: "2024-01-02T00:00:00Z", - }, - ], - isLoading: false, - } as any); - vi.mocked(useDeleteBudget).mockReturnValue({ - mutateAsync: deleteMutateAsync, - isPending: false, - } as any); + budgetDeleteMock.mockResolvedValue(undefined); + renderPanel(); + await screen.findByText("ecc1869c-6231-4380-a56d-1a0be457477d"); + const before = getMock.mock.calls.length; - renderWithProviders(); - - await waitFor(() => { - expect(screen.getByText("budget-to-delete")).toBeInTheDocument(); - }); - - await user.click(screen.getByTestId("budget-actions-budget-to-delete")); + await user.click(screen.getByTestId("budget-actions-ecc1869c-6231-4380-a56d-1a0be457477d")); await user.click(await screen.findByTestId("budget-action-delete")); + await screen.findByText("Delete Budget?"); + await user.click(screen.getByRole("button", { name: /^delete$/i })); - await waitFor(() => { - expect(screen.getByText("Delete Budget?")).toBeInTheDocument(); - }); - - const confirmButton = screen.getByRole("button", { name: /delete/i }); - act(() => { - fireEvent.click(confirmButton); - }); - - await waitFor(() => { - expect(deleteMutateAsync).toHaveBeenCalledWith("budget-to-delete"); - }); - }); - - it("should render empty state without crashing", async () => { - vi.mocked(useBudgets).mockReturnValue({ - data: [], - isLoading: false, - } as any); - - renderWithProviders(); - - await waitFor(() => { - expect(screen.getByText("Create a budget to assign to customers.")).toBeInTheDocument(); - }); - }); - - it("should handle delete error", async () => { - const user = userEvent.setup(); - const deleteMutateAsync = vi.fn().mockRejectedValue(new Error("Delete failed")); - vi.mocked(useBudgets).mockReturnValue({ - data: [ - { - budget_id: "budget-to-delete", - max_budget: 200, - rpm_limit: 20, - tpm_limit: 2000, - updated_at: "2024-01-02T00:00:00Z", - }, - ], - isLoading: false, - } as any); - vi.mocked(useDeleteBudget).mockReturnValue({ - mutateAsync: deleteMutateAsync, - isPending: false, - } as any); - - renderWithProviders(); - - await waitFor(() => { - expect(screen.getByText("budget-to-delete")).toBeInTheDocument(); - }); - - await user.click(screen.getByTestId("budget-actions-budget-to-delete")); - await user.click(await screen.findByTestId("budget-action-delete")); - - await waitFor(() => { - expect(screen.getByText("Delete Budget?")).toBeInTheDocument(); - }); - - const confirmButton = screen.getByRole("button", { name: /delete/i }); - act(() => { - fireEvent.click(confirmButton); - }); - - await waitFor(() => { - expect(deleteMutateAsync).toHaveBeenCalledWith("budget-to-delete"); - }); - }); - - it("should open edit modal from the actions menu", async () => { - const user = userEvent.setup(); - vi.mocked(useBudgets).mockReturnValue({ - data: [ - { - budget_id: "budget-to-edit", - max_budget: 300, - rpm_limit: 30, - tpm_limit: 3000, - updated_at: "2024-01-03T00:00:00Z", - }, - ], - isLoading: false, - } as any); - - renderWithProviders(); - - await waitFor(() => { - expect(screen.getByText("budget-to-edit")).toBeInTheDocument(); - }); - - await user.click(screen.getByTestId("budget-actions-budget-to-edit")); - await user.click(await screen.findByTestId("budget-action-edit")); - - await waitFor(() => { - expect(screen.getByText("Edit Budget")).toBeInTheDocument(); - }); + await waitFor(() => expect(getMock.mock.calls.length).toBeGreaterThan(before)); }); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/budget_panel.tsx b/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/budget_panel.tsx index 2cf2a4c06ec..78c2c0ca74a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/budget_panel.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/budget_panel.tsx @@ -3,13 +3,13 @@ * */ -import React, { useState } from "react"; +import React, { useCallback, useState } from "react"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { Button } from "@/components/ui/button"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import DeleteResourceModal from "@/components/common_components/DeleteResourceModal"; import NotificationsManager from "@/components/molecules/notifications_manager"; -import { useBudgets, useDeleteBudget, budgetItem } from "@/app/(dashboard)/hooks/budgets/useBudgets"; +import { useBudgetList, useDeleteBudget, budgetItem } from "@/app/(dashboard)/hooks/budgets/useBudgets"; import BudgetModal from "./budget_modal"; import BudgetTable from "./BudgetTable"; import EditBudgetModal from "./edit_budget_modal"; @@ -31,21 +31,25 @@ const BudgetPanel: React.FC = ({ accessToken }) => { // Admin Viewer follows the read-parity rule: see budgets, no writes. const canModify = isProxyAdminRole(userRole ?? ""); - const { data: budgetList = [], isLoading } = useBudgets(); + const budgetList = useBudgetList(); const deleteBudget = useDeleteBudget(); - const handleEditCall = async (budget: budgetItem) => { - if (accessToken == null) { - return; - } - setSelectedBudget(budget); - setIsEditModalVisible(true); - }; + // Stable identities keep the memoized column defs stable; new ones remount every header and cell. + const handleEditCall = useCallback( + (budget: budgetItem) => { + if (accessToken == null) { + return; + } + setSelectedBudget(budget); + setIsEditModalVisible(true); + }, + [accessToken], + ); - const handleDeleteClick = (budget: budgetItem) => { + const handleDeleteClick = useCallback((budget: budgetItem) => { setSelectedBudget(budget); setIsDeleteModalVisible(true); - }; + }, []); const handleDeleteConfirm = async () => { if (!selectedBudget || accessToken == null) { @@ -99,8 +103,7 @@ const BudgetPanel: React.FC = ({ accessToken }) => { )}

Create a budget to assign to customers.

{ + it("sends nothing when no filter is active", () => { + expect(serializeBudgetFilters([])).toEqual({}); + }); + + it("maps selected durations onto the in operator", () => { + expect(serializeBudgetFilters([{ id: "budget_duration", value: ["7d", "30d"] }])).toEqual({ + "filter[budget_duration][in]": "7d,30d", + }); + }); + + it("maps 'Not set' onto is_null instead of in", () => { + expect(serializeBudgetFilters([{ id: "budget_duration", value: [BUDGET_DURATION_UNSET] }])).toEqual({ + "filter[budget_duration][is_null]": "true", + }); + }); + + it("never sends in alongside is_null for the same field", () => { + const params = serializeBudgetFilters([{ id: "budget_duration", value: ["7d", BUDGET_DURATION_UNSET] }]); + expect(params["filter[budget_duration][in]"]).toBeUndefined(); + expect(params["filter[budget_duration][is_null]"]).toBe("true"); + }); + + it("maps a max budget range onto gte and lte", () => { + expect(serializeBudgetFilters([{ id: "max_budget", value: { min: "10", max: "250.5" } }])).toEqual({ + "filter[max_budget][gte]": "10", + "filter[max_budget][lte]": "250.5", + }); + }); + + it("sends only the bound that was filled in", () => { + expect(serializeBudgetFilters([{ id: "max_budget", value: { min: "10", max: "" } }])).toEqual({ + "filter[max_budget][gte]": "10", + }); + }); + + it("maps 'Unlimited only' onto is_null and drops the range", () => { + const params = serializeBudgetFilters([{ id: "max_budget", value: { min: "10", unlimitedOnly: true } }]); + expect(params).toEqual({ "filter[max_budget][is_null]": "true" }); + }); + + it("widens a created-at day range to cover the whole local days", () => { + const params = serializeBudgetFilters([{ id: "created_at", value: { from: "2026-01-05", to: "2026-01-06" } }]); + expect(params["filter[created_at][gte]"]).toBe(new Date("2026-01-05T00:00:00.000").toISOString()); + expect(params["filter[created_at][lte]"]).toBe(new Date("2026-01-06T23:59:59.999").toISOString()); + }); + + it("ignores an unparseable date rather than sending a broken bound", () => { + expect(serializeBudgetFilters([{ id: "created_at", value: { from: "not-a-date" } }])).toEqual({}); + }); + + it("ignores filter ids the route does not declare", () => { + expect(serializeBudgetFilters([{ id: "spend", value: "5" }])).toEqual({}); + }); +}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/budgets/budgetFilters.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/budgets/budgetFilters.ts new file mode 100644 index 00000000000..f54eddb3913 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/budgets/budgetFilters.ts @@ -0,0 +1,91 @@ +import type { ColumnFilter, ColumnFiltersState } from "@tanstack/react-table"; + +export const BUDGET_DURATION_UNSET = "__unset__"; + +export const BUDGET_DURATION_FILTER_OPTIONS: readonly { value: string; label: string }[] = [ + { value: "1h", label: "hourly" }, + { value: "24h", label: "daily" }, + { value: "7d", label: "weekly" }, + { value: "30d", label: "monthly" }, + { value: BUDGET_DURATION_UNSET, label: "Not set" }, +]; + +export interface MaxBudgetFilterValue { + min?: string; + max?: string; + unlimitedOnly?: boolean; +} + +export interface CreatedAtFilterValue { + from?: string; + to?: string; +} + +type QueryEntry = readonly [string, string]; + +const entries = (key: string, value: string): QueryEntry[] => (value === "" ? [] : [[key, value]]); + +const asStringArray = (value: unknown): string[] => + Array.isArray(value) ? value.filter((item): item is string => typeof item === "string") : []; + +const asRecord = (value: unknown): Record => + typeof value === "object" && value !== null ? (value as Record) : {}; + +const asTrimmed = (value: unknown): string => (typeof value === "string" ? value.trim() : ""); + +/** The date inputs give a calendar day; the route wants an instant, so widen to the viewer's whole local day. */ +const isoAt = (day: string, time: string): string => { + if (day === "") { + return ""; + } + const parsed = new Date(`${day}T${time}`); + return Number.isNaN(parsed.getTime()) ? "" : parsed.toISOString(); +}; + +/** + * "Not set" is exclusive with the concrete durations. The route's contract does not say how it + * combines `in` with `is_null` on one field, and under AND semantics that pair can only match + * nothing, so we never send both. + */ +const durationParams = (value: unknown): QueryEntry[] => { + const selected = asStringArray(value); + if (selected.includes(BUDGET_DURATION_UNSET)) { + return [["filter[budget_duration][is_null]", "true"]]; + } + return entries("filter[budget_duration][in]", selected.join(",")); +}; + +const maxBudgetParams = (value: unknown): QueryEntry[] => { + const draft = asRecord(value); + if (draft.unlimitedOnly === true) { + return [["filter[max_budget][is_null]", "true"]]; + } + return [ + ...entries("filter[max_budget][gte]", asTrimmed(draft.min)), + ...entries("filter[max_budget][lte]", asTrimmed(draft.max)), + ]; +}; + +const createdAtParams = (value: unknown): QueryEntry[] => { + const draft = asRecord(value); + return [ + ...entries("filter[created_at][gte]", isoAt(asTrimmed(draft.from), "00:00:00.000")), + ...entries("filter[created_at][lte]", isoAt(asTrimmed(draft.to), "23:59:59.999")), + ]; +}; + +const filterParams = (filter: ColumnFilter): QueryEntry[] => { + switch (filter.id) { + case "budget_duration": + return durationParams(filter.value); + case "max_budget": + return maxBudgetParams(filter.value); + case "created_at": + return createdAtParams(filter.value); + default: + return []; + } +}; + +export const serializeBudgetFilters = (filters: ColumnFiltersState): Readonly> => + Object.fromEntries(filters.flatMap(filterParams)); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/budgets/useBudgets.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/budgets/useBudgets.ts index 0d8d94f2369..e5f24e5412d 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/budgets/useBudgets.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/budgets/useBudgets.ts @@ -1,28 +1,58 @@ -import { useQuery, useMutation, useQueryClient, UseQueryResult } from "@tanstack/react-query"; -import { createQueryKeys } from "../common/queryKeysFactory"; -import { getBudgetList, budgetCreateCall, budgetUpdateCall, budgetDeleteCall } from "@/components/networking"; +"use client"; + +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import type { SortingState } from "@tanstack/react-table"; +import { useCallback } from "react"; + import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; +import { apiClient, budgetCreateCall, budgetUpdateCall, budgetDeleteCall } from "@/components/networking"; + +import { createQueryKeys } from "../common/queryKeysFactory"; +import { + useResourceList, + type ResourceListPage, + type ResourceListQuery, + type ResourceListResult, +} from "../common/useResourceList"; +import { serializeBudgetFilters } from "./budgetFilters"; export interface budgetItem { budget_id: string; max_budget: number | null; + soft_budget: number | null; rpm_limit: number | null; tpm_limit: number | null; + budget_duration: string | null; + budget_reset_at: string | null; + created_at: string; updated_at: string; } +export const BUDGET_LIST_PATH = "/management/v1/budgets"; + export const budgetKeys = createQueryKeys("budgets"); -export const useBudgets = (): UseQueryResult => { +const DEFAULT_PAGE_SIZE = 50; +const DEFAULT_SORTING: SortingState = [{ id: "created_at", desc: true }]; + +export const useBudgetList = (): ResourceListResult => { const { accessToken } = useAuthorized(); - return useQuery({ - queryKey: budgetKeys.list({}), - queryFn: async () => { - const data = await getBudgetList(accessToken!); - return (data ?? []).filter((item: budgetItem | null): item is budgetItem => item != null); - }, + + const fetchPage = useCallback( + (query: ResourceListQuery, signal: AbortSignal): Promise> => + apiClient.get>(BUDGET_LIST_PATH, { accessToken, query, signal }), + [accessToken], + ); + + const listOptions = { + queryKey: budgetKeys.lists(), + fetchPage, + serializeFilters: serializeBudgetFilters, + defaultSorting: DEFAULT_SORTING, + defaultPageSize: DEFAULT_PAGE_SIZE, enabled: Boolean(accessToken), - }); + }; + return useResourceList(listOptions); }; export const useCreateBudget = () => { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/common/useResourceList.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/hooks/common/useResourceList.test.tsx new file mode 100644 index 00000000000..3ca67b082ec --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/common/useResourceList.test.tsx @@ -0,0 +1,161 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import type { ColumnFiltersState } from "@tanstack/react-table"; +import { act, renderHook, waitFor } from "@testing-library/react"; +import React, { type PropsWithChildren } from "react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { + toSortParam, + useResourceList, + type ResourceListPage, + type ResourceListQuery, + type UseResourceListOptions, +} from "./useResourceList"; + +interface Row { + id: string; +} + +const page = (rows: Row[], totalCount: number): ResourceListPage => ({ + data: rows, + meta: { total_count: totalCount, page: 1, page_size: 50, total_pages: 1 }, +}); + +const noFilters = (): Readonly> => ({}); + +const calls: ResourceListQuery[] = []; + +const renderList = (overrides: Partial> = {}) => { + const client = new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 0 } } }); + const wrapper = ({ children }: PropsWithChildren) => ( + {children} + ); + const fetchPage = vi.fn((query: ResourceListQuery) => { + calls.push(query); + return Promise.resolve(page([{ id: "a" }], 3)); + }); + const options: UseResourceListOptions = { + queryKey: ["widgets", "list"], + fetchPage, + serializeFilters: noFilters, + defaultSorting: [{ id: "created_at", desc: true }], + defaultPageSize: 50, + enabled: true, + ...overrides, + }; + return renderHook(() => useResourceList(options), { wrapper }); +}; + +const lastCall = (): ResourceListQuery => calls[calls.length - 1]; + +describe("toSortParam", () => { + it("prefixes descending fields with a minus and joins with commas", () => { + expect(toSortParam([{ id: "created_at", desc: true }])).toBe("-created_at"); + expect(toSortParam([{ id: "max_budget", desc: false }])).toBe("max_budget"); + expect( + toSortParam([ + { id: "a", desc: false }, + { id: "b", desc: true }, + ]), + ).toBe("a,-b"); + }); +}); + +describe("useResourceList", () => { + beforeEach(() => { + calls.length = 0; + }); + + it("requests the first page with the default sort", async () => { + const { result } = renderList(); + await waitFor(() => expect(result.current.rowCount).toBe(3)); + expect(lastCall()).toEqual({ page: 1, page_size: 50, sort: "-created_at" }); + }); + + it("exposes the returned rows and total count", async () => { + const { result } = renderList(); + await waitFor(() => expect(result.current.rows).toEqual([{ id: "a" }])); + expect(result.current.rowCount).toBe(3); + }); + + it("does not fetch while disabled", async () => { + const { result } = renderList({ enabled: false }); + await waitFor(() => expect(result.current.isLoading).toBe(false)); + expect(calls).toHaveLength(0); + }); + + it("sends the new sort and returns to the first page", async () => { + const { result } = renderList(); + await waitFor(() => expect(calls).toHaveLength(1)); + + act(() => result.current.onPaginationChange({ pageIndex: 2, pageSize: 50 })); + await waitFor(() => expect(lastCall().page).toBe(3)); + + act(() => result.current.onSortingChange([{ id: "max_budget", desc: false }])); + await waitFor(() => expect(lastCall().sort).toBe("max_budget")); + expect(lastCall().page).toBe(1); + }); + + it("omits sort entirely when nothing is sorted", async () => { + const { result } = renderList({ defaultSorting: [] }); + await waitFor(() => expect(calls).toHaveLength(1)); + expect(result.current.sorting).toEqual([]); + expect(lastCall()).not.toHaveProperty("sort"); + }); + + it("debounces the search into a single trimmed q and returns to the first page", async () => { + const { result } = renderList(); + await waitFor(() => expect(calls).toHaveLength(1)); + + act(() => result.current.onPaginationChange({ pageIndex: 1, pageSize: 50 })); + await waitFor(() => expect(lastCall().page).toBe(2)); + + act(() => result.current.onSearchChange("bud")); + act(() => result.current.onSearchChange("budg ")); + + await waitFor(() => expect(lastCall().q).toBe("budg")); + expect(lastCall().page).toBe(1); + expect(calls.some((call) => call.q === "bud")).toBe(false); + }); + + it("stops sending q once the search box is cleared", async () => { + const { result } = renderList(); + act(() => result.current.onSearchChange("budget")); + await waitFor(() => expect(lastCall().q).toBe("budget")); + + act(() => result.current.onSearchChange("")); + await waitFor(() => expect(lastCall()).not.toHaveProperty("q")); + }); + + it("merges serialized filters into the request and returns to the first page", async () => { + const serializeFilters = (filters: ColumnFiltersState): Readonly> => + filters.length === 0 ? {} : { "filter[colour][in]": String(filters[0].value) }; + const { result } = renderList({ serializeFilters }); + await waitFor(() => expect(calls).toHaveLength(1)); + + act(() => result.current.onPaginationChange({ pageIndex: 3, pageSize: 50 })); + await waitFor(() => expect(lastCall().page).toBe(4)); + + act(() => result.current.onColumnFiltersChange([{ id: "colour", value: "red" }])); + await waitFor(() => expect(lastCall()["filter[colour][in]"]).toBe("red")); + expect(lastCall().page).toBe(1); + + act(() => result.current.onColumnFiltersChange([])); + await waitFor(() => expect(lastCall()).not.toHaveProperty("filter[colour][in]")); + }); + + it("sends the requested page size", async () => { + const { result } = renderList(); + await waitFor(() => expect(calls).toHaveLength(1)); + + act(() => result.current.onPaginationChange({ pageIndex: 0, pageSize: 25 })); + await waitFor(() => expect(lastCall().page_size).toBe(25)); + }); + + it("surfaces a failed page as an error instead of empty rows", async () => { + const fetchPage = vi.fn(() => Promise.reject(new Error("boom"))); + const { result } = renderList({ fetchPage }); + await waitFor(() => expect(result.current.error?.message).toBe("boom")); + expect(result.current.rows).toEqual([]); + }); +}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/common/useResourceList.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/common/useResourceList.ts new file mode 100644 index 00000000000..fb40d108234 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/common/useResourceList.ts @@ -0,0 +1,142 @@ +"use client"; + +import { useDebouncedValue } from "@tanstack/react-pacer/debouncer"; +import { useQuery, type UseQueryOptions } from "@tanstack/react-query"; +import type { ColumnFiltersState, OnChangeFn, PaginationState, SortingState } from "@tanstack/react-table"; +import { useCallback, useMemo, useState } from "react"; + +import { DEBOUNCE_WAIT_MS } from "@/utils/debounceConstants"; + +export type ResourceListQuery = Readonly>; + +export interface ResourceListMeta { + total_count: number; + page: number; + page_size: number; + total_pages: number; +} + +export interface ResourceListPage { + data: TRow[]; + meta: ResourceListMeta; +} + +export interface UseResourceListOptions { + /** Prefix every list variant hangs off, so invalidating the resource root refetches whichever page is on screen. */ + queryKey: readonly unknown[]; + fetchPage: (query: ResourceListQuery, signal: AbortSignal) => Promise>; + /** Must be referentially stable; it feeds the query key. */ + serializeFilters: (filters: ColumnFiltersState) => Readonly>; + defaultSorting: SortingState; + defaultPageSize: number; + enabled: boolean; +} + +export interface ResourceListResult { + rows: TRow[]; + rowCount: number; + isLoading: boolean; + isFetching: boolean; + error: Error | null; + refetch: () => void; + + sorting: SortingState; + onSortingChange: OnChangeFn; + pagination: PaginationState; + onPaginationChange: OnChangeFn; + columnFilters: ColumnFiltersState; + onColumnFiltersChange: OnChangeFn; + searchValue: string; + onSearchChange: (value: string) => void; +} + +/** JSON:API sort form: comma separated fields, `-` prefix for descending. */ +export const toSortParam = (sorting: SortingState): string => + sorting.map((entry) => (entry.desc ? `-${entry.id}` : entry.id)).join(","); + +/** + * State container for a table whose sorting, paging, search and filtering all run + * on the server. It owns those four pieces of state, folds them into one JSON:API + * query, and returns the exact props DataTable's server modes want. + * + * Empty parameters are dropped rather than sent blank because the management + * routes reject query params they do not declare. + */ +export function useResourceList(options: UseResourceListOptions): ResourceListResult { + const { queryKey, fetchPage, serializeFilters, defaultSorting, defaultPageSize, enabled } = options; + + const [sorting, setSorting] = useState(defaultSorting); + const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: defaultPageSize }); + const [columnFilters, setColumnFilters] = useState([]); + const [searchValue, setSearchValue] = useState(""); + const [debouncedSearch] = useDebouncedValue(searchValue, { wait: DEBOUNCE_WAIT_MS }); + + const query = useMemo(() => { + const sort = toSortParam(sorting); + const search = debouncedSearch.trim(); + return { + page: pagination.pageIndex + 1, + page_size: pagination.pageSize, + ...(sort === "" ? {} : { sort }), + ...(search === "" ? {} : { q: search }), + ...serializeFilters(columnFilters), + }; + }, [sorting, pagination.pageIndex, pagination.pageSize, debouncedSearch, columnFilters, serializeFilters]); + + const queryOptions: UseQueryOptions, Error, ResourceListPage, readonly unknown[]> = { + queryKey: [...queryKey, query], + queryFn: ({ signal }) => fetchPage(query, signal), + enabled, + placeholderData: (previous) => previous, + }; + const { data, isLoading, isFetching, error, refetch: refetchQuery } = useQuery(queryOptions); + + const toFirstPage = useCallback(() => setPagination((previous) => ({ ...previous, pageIndex: 0 })), []); + + const onSortingChange = useCallback>( + (updater) => { + setSorting(updater); + toFirstPage(); + }, + [toFirstPage], + ); + + const onColumnFiltersChange = useCallback>( + (updater) => { + setColumnFilters(updater); + toFirstPage(); + }, + [toFirstPage], + ); + + const onSearchChange = useCallback( + (value: string) => { + setSearchValue(value); + toFirstPage(); + }, + [toFirstPage], + ); + + const refetch = useCallback(() => { + void refetchQuery(); + }, [refetchQuery]); + + const rows = useMemo(() => data?.data ?? [], [data]); + + return { + rows, + rowCount: data?.meta.total_count ?? 0, + isLoading, + isFetching, + error, + refetch, + sorting, + onSortingChange, + pagination, + onPaginationChange: setPagination, + columnFilters, + onColumnFiltersChange, + searchValue, + onSearchChange, + }; +} From a685cc1511387149285dca3ea623fa6db4de7873 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Thu, 30 Jul 2026 19:38:24 -0700 Subject: [PATCH 04/92] feat(proxy): add a generic list contract for management/v1 entity lists Paging, sorting, filtering and search for an entity collection, declared once as a ListSpec and served by handle_list. The route injects a ListExecutor that owns its table, so this module never imports Prisma. The caller's scope is derived from the caller alone and ANDed with whatever they filtered on, so a query parameter can only narrow what they may read. This is the shared half of the budgets list; it lands here so the endpoint has something to register against, and drops out when the framework arrives on its own branch. --- .../management_v1/list_framework.py | 308 ++++++++++++++++++ .../management_endpoints/management_v1.py | 33 +- 2 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 litellm/proxy/management_endpoints/management_v1/list_framework.py diff --git a/litellm/proxy/management_endpoints/management_v1/list_framework.py b/litellm/proxy/management_endpoints/management_v1/list_framework.py new file mode 100644 index 00000000000..6e800d295f9 --- /dev/null +++ b/litellm/proxy/management_endpoints/management_v1/list_framework.py @@ -0,0 +1,308 @@ +"""Generic paging/sorting/filtering contract for `/management/v1` entity lists. + +Prisma-free by construction: a route declares a `ListSpec` and injects a +`ListExecutor` that owns the table, so the parsing, scoping and envelope rules +stay in one place and every entity list answers the same way. +""" + +from __future__ import annotations + +import math +from collections.abc import Callable, Mapping, Sequence +from dataclasses import dataclass +from datetime import datetime +from typing import Generic, Literal, Protocol, TypeAlias, TypeVar +from urllib.parse import urlencode + +from fastapi import Request +from pydantic import JsonValue + +from litellm.proxy._types import UserAPIKeyAuth +from litellm.proxy.management_endpoints.management_v1.common import ( + PROBLEM_TYPE_BASE, + ManagementProblem, +) +from litellm.types.proxy.management_endpoints.management_v1 import ( + ListLinks, + ListMeta, + ListResponse, + ProblemDetail, +) + +FilterOp: TypeAlias = Literal["eq", "in", "gte", "lte", "contains", "is_null"] +FilterType: TypeAlias = Literal["string", "number", "datetime"] + +# Quoted so the recursive alias parses under the repo's 3.10 floor, where neither +# the `type` statement nor a forward reference inside a `|` expression exists. +WhereLeaf: TypeAlias = "str | int | float | bool | datetime | None" +WhereValue: TypeAlias = "WhereLeaf | Sequence[WhereLeaf] | Where | Sequence[Where]" +Where: TypeAlias = "Mapping[str, WhereValue]" +OrderBy: TypeAlias = "Sequence[Mapping[str, Literal['asc', 'desc']]]" + +RowT = TypeVar("RowT") + +PAGINATION_PARAMS = frozenset({"page", "page_size", "sort", "q"}) + + +@dataclass(frozen=True, slots=True) +class FilterSpec: + type: FilterType + ops: frozenset[FilterOp] + + +@dataclass(frozen=True, slots=True) +class SortKey: + field: str + descending: bool + + +@dataclass(frozen=True, slots=True) +class ScopeAll: + """The caller may read every row.""" + + +@dataclass(frozen=True, slots=True) +class ScopeWhere: + """The caller may read only rows matching `where`.""" + + where: Where + + +@dataclass(frozen=True, slots=True) +class ScopeDenied: + """The caller may not read the collection at all.""" + + detail: str + + +Scope: TypeAlias = "ScopeAll | ScopeWhere | ScopeDenied" + + +class ListExecutor(Protocol, Generic[RowT]): + """The table half of a list, injected so the framework never imports Prisma.""" + + async def count(self, where: Where) -> int: ... + + async def find_many(self, where: Where, order: OrderBy, skip: int, take: int) -> Sequence[RowT]: ... + + +@dataclass(frozen=True, slots=True) +class ListSpec(Generic[RowT]): + resource: str + sortable: frozenset[str] + searchable: frozenset[str] + filters: Mapping[str, FilterSpec] + default_sort: tuple[SortKey, ...] + default_page_size: int + max_page_size: int + scope: Callable[[UserAPIKeyAuth], Scope] + serialize: Callable[[RowT], Mapping[str, JsonValue]] + tiebreaker: str + + +@dataclass(frozen=True, slots=True) +class QueryPlan: + where: Where + order: OrderBy + skip: int + take: int + page: int + page_size: int + + +def _problem(slug: str, title: str, detail: str, allowed: Sequence[str] | None = None) -> ManagementProblem: + return ManagementProblem( + ProblemDetail( + type=f"{PROBLEM_TYPE_BASE}{slug}", + title=title, + status=400, + detail=detail, + allowed=list(allowed) if allowed is not None else None, + ) + ) + + +def _allowed_params(filters: Mapping[str, FilterSpec]) -> frozenset[str]: + return PAGINATION_PARAMS | frozenset( + f"filter[{field}][{op}]" for field, filter_spec in filters.items() for op in filter_spec.ops + ) + + +def _reject_unknown_params(request: Request, filters: Mapping[str, FilterSpec]) -> None: + allowed = _allowed_params(filters) + unknown = tuple(sorted(name for name in request.query_params if name not in allowed)) + if not unknown: + return + raise _problem( + "unknown-query-parameter", + "Unknown query parameter", + f"Unrecognized query parameter(s): {', '.join(unknown)}.", + sorted(allowed), + ) + + +def _positive_int(raw: str | None, default: int, name: str) -> int: + if raw is None: + return default + try: + value = int(raw) + except ValueError: + raise _problem("invalid-query-parameter", "Invalid query parameter", f"{name} must be an integer.") + if value < 1: + raise _problem("invalid-query-parameter", "Invalid query parameter", f"{name} must be at least 1.") + return value + + +def _parse_sort(raw: str | None, sortable: frozenset[str], default_sort: tuple[SortKey, ...]) -> tuple[SortKey, ...]: + if raw is None: + return default_sort + keys = tuple( + SortKey(field=token.removeprefix("-"), descending=token.startswith("-")) + for token in (part.strip() for part in raw.split(",")) + if token + ) + unknown = tuple(key.field for key in keys if key.field not in sortable) + if unknown: + raise _problem( + "invalid-sort-field", + "Invalid sort field", + f"Cannot sort on: {', '.join(unknown)}.", + sorted(sortable), + ) + return keys or default_sort + + +def _order_by(keys: Sequence[SortKey], tiebreaker: str) -> OrderBy: + tail = () if any(key.field == tiebreaker for key in keys) else (SortKey(field=tiebreaker, descending=False),) + return tuple({key.field: ("desc" if key.descending else "asc")} for key in (*keys, *tail)) + + +def _coerce(value: str, filter_type: FilterType, param: str) -> WhereLeaf: + if filter_type == "number": + try: + return float(value) + except ValueError: + raise _problem("invalid-filter-value", "Invalid filter value", f"{param} must be a number.") + if filter_type == "datetime": + try: + return datetime.fromisoformat(value) + except ValueError: + raise _problem("invalid-filter-value", "Invalid filter value", f"{param} must be an ISO-8601 timestamp.") + return value + + +def _bool(value: str, param: str) -> bool: + if value.lower() in ("true", "1"): + return True + if value.lower() in ("false", "0"): + return False + raise _problem("invalid-filter-value", "Invalid filter value", f"{param} must be true or false.") + + +def _condition(field: str, op: FilterOp, raw: str, filter_type: FilterType, param: str) -> Where: + if op == "is_null": + return {field: None} if _bool(raw, param) else {field: {"not": None}} + if op == "in": + return {field: {"in": tuple(_coerce(part, filter_type, param) for part in raw.split(",") if part)}} + if op == "contains": + return {field: {"contains": raw, "mode": "insensitive"}} + if op == "eq": + return {field: _coerce(raw, filter_type, param)} + return {field: {op: _coerce(raw, filter_type, param)}} + + +def _filter_conditions(request: Request, filters: Mapping[str, FilterSpec]) -> tuple[Where, ...]: + return tuple( + _condition(field, op, request.query_params[f"filter[{field}][{op}]"], spec.type, f"filter[{field}][{op}]") + for field, spec in filters.items() + for op in sorted(spec.ops) + if f"filter[{field}][{op}]" in request.query_params + ) + + +def _search_condition(raw: str | None, searchable: frozenset[str]) -> tuple[Where, ...]: + if not raw or not searchable: + return () + return ({"OR": tuple({field: {"contains": raw, "mode": "insensitive"}} for field in sorted(searchable))},) + + +def build_query_plan(request: Request, spec: ListSpec[RowT], scope: Scope) -> QueryPlan: + """Turn the query string into the executor's arguments, or raise a 400 problem. + + `scope` is derived from the caller, never from the query string, and is ANDed + with the caller's filters so a filter can only ever narrow what they may read. + """ + _reject_unknown_params(request, spec.filters) + + page = _positive_int(request.query_params.get("page"), 1, "page") + page_size = min( + _positive_int(request.query_params.get("page_size"), spec.default_page_size, "page_size"), + spec.max_page_size, + ) + keys = _parse_sort(request.query_params.get("sort"), spec.sortable, spec.default_sort) + + scope_conditions: tuple[Where, ...] = (scope.where,) if isinstance(scope, ScopeWhere) else () + conditions = ( + scope_conditions + + _filter_conditions(request, spec.filters) + + _search_condition(request.query_params.get("q"), spec.searchable) + ) + + return QueryPlan( + where={"AND": conditions} if conditions else {}, + order=_order_by(keys, spec.tiebreaker), + skip=(page - 1) * page_size, + take=page_size, + page=page, + page_size=page_size, + ) + + +def _page_url(request: Request, page: int) -> str: + others = tuple((key, value) for key, value in request.query_params.multi_items() if key != "page") + return f"{request.url.path}?{urlencode((*others, ('page', page)))}" + + +def _links(request: Request, page: int, last_page: int) -> ListLinks: + return ListLinks( + self_link=_page_url(request, page), + first=_page_url(request, 1), + prev=_page_url(request, page - 1) if page > 1 else None, + next=_page_url(request, page + 1) if page < last_page else None, + last=_page_url(request, last_page), + ) + + +async def handle_list( + request: Request, + spec: ListSpec[RowT], + executor: ListExecutor[RowT], + caller: UserAPIKeyAuth, +) -> ListResponse: + """Serve one page of `spec.resource` under the caller's scope.""" + scope = spec.scope(caller) + if isinstance(scope, ScopeDenied): + raise ManagementProblem( + ProblemDetail( + type=f"{PROBLEM_TYPE_BASE}forbidden", + title="Forbidden", + status=403, + detail=scope.detail, + ) + ) + + plan = build_query_plan(request, spec, scope) + total_count = await executor.count(plan.where) + rows = await executor.find_many(where=plan.where, order=plan.order, skip=plan.skip, take=plan.take) + total_pages = math.ceil(total_count / plan.page_size) + + return ListResponse( + data=tuple(spec.serialize(row) for row in rows), + meta=ListMeta( + page=plan.page, + page_size=plan.page_size, + total_count=total_count, + total_pages=total_pages, + ), + links=_links(request, plan.page, max(total_pages, 1)), + ) diff --git a/litellm/types/proxy/management_endpoints/management_v1.py b/litellm/types/proxy/management_endpoints/management_v1.py index 2aecc54f114..a7427bc7590 100644 --- a/litellm/types/proxy/management_endpoints/management_v1.py +++ b/litellm/types/proxy/management_endpoints/management_v1.py @@ -1,6 +1,8 @@ """Shared response shapes for the `/management/v1` control-plane surface.""" -from pydantic import BaseModel, ConfigDict, Field +from collections.abc import Mapping + +from pydantic import BaseModel, ConfigDict, Field, JsonValue class ProblemDetail(BaseModel): @@ -37,3 +39,32 @@ class FacetListResponse(BaseModel): data: list[str] meta: PageMeta links: PageLinks + + +class ListMeta(BaseModel): + """An entity list can afford the COUNT(*) a facet cannot, so it reports a real total.""" + + page: int + page_size: int + total_count: int + total_pages: int + + +class ListLinks(BaseModel): + """Hypermedia for an entity list. `first`/`last` exist here because `total_pages` is known.""" + + model_config = ConfigDict(populate_by_name=True) + + self_link: str = Field(alias="self") + first: str + prev: str | None = None + next: str | None = None + last: str + + +class ListResponse(BaseModel): + """One page of an entity collection. Rows are flat: no `{type, id, attributes}` wrapper.""" + + data: tuple[Mapping[str, JsonValue], ...] + meta: ListMeta + links: ListLinks From f0866d0446a76ee84bda688b93881f95d3ade9a8 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Thu, 30 Jul 2026 19:38:32 -0700 Subject: [PATCH 05/92] feat(proxy): add GET /management/v1/budgets The Budgets page reads /budget/list, which returns the whole table as a bare array with no way to page, sort or filter it. A customer with enough budgets to fill the page has no way to find one. Registers LiteLLM_BudgetTable against the management/v1 list contract: sortable on budget_id, max_budget, tpm_limit, rpm_limit and created_at, default order newest-first with budget_id breaking ties, search on budget_id, and filters for budget_duration, max_budget and created_at. budget_duration is deliberately not sortable; the column holds "7d"/"30d" strings, so a lexicographic ORDER BY puts "30d" ahead of "7d". tpm_limit and rpm_limit are BigInt? in Prisma, so rows validate through a pydantic model on the way out and serialize as JSON numbers. A caller without admin view is refused 403 as a problem document rather than served an empty page. /budget/list is untouched. --- litellm/proxy/_types.py | 1 + .../management_v1/__init__.py | 4 + .../management_v1/budgets.py | 195 ++++++++ tests/e2e/coverage_registry/mgmt.yaml | 2 + .../test_budget_customer_user_org_e2e.py | 166 +++++- .../auth/test_admin_viewer_handler_access.py | 9 + .../proxy/auth/test_route_checks.py | 1 + .../management_v1/test_budgets.py | 471 ++++++++++++++++++ ui/litellm-dashboard/src/lib/http/schema.d.ts | 100 ++++ 9 files changed, 946 insertions(+), 3 deletions(-) create mode 100644 litellm/proxy/management_endpoints/management_v1/budgets.py create mode 100644 tests/test_litellm/proxy/management_endpoints/management_v1/test_budgets.py diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 9e98cb46b9a..9f3b32328c5 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -840,6 +840,7 @@ class LiteLLMRoutes(enum.Enum): "/config/list", "/config/field/info", "/budget/list", + "/management/v1/budgets", "/budget/settings", # Invitation viewing (admin viewer cannot create/delete; can read). "/invitation/info", diff --git a/litellm/proxy/management_endpoints/management_v1/__init__.py b/litellm/proxy/management_endpoints/management_v1/__init__.py index 257de66130b..a06c6b2591c 100644 --- a/litellm/proxy/management_endpoints/management_v1/__init__.py +++ b/litellm/proxy/management_endpoints/management_v1/__init__.py @@ -2,11 +2,15 @@ from fastapi import APIRouter +from litellm.proxy.management_endpoints.management_v1.budgets import ( + router as budgets_router, +) from litellm.proxy.management_endpoints.management_v1.spend_logs import ( router as spend_logs_router, ) router = APIRouter() +router.include_router(budgets_router) router.include_router(spend_logs_router) __all__ = ["router"] diff --git a/litellm/proxy/management_endpoints/management_v1/budgets.py b/litellm/proxy/management_endpoints/management_v1/budgets.py new file mode 100644 index 00000000000..79c3876c7ab --- /dev/null +++ b/litellm/proxy/management_endpoints/management_v1/budgets.py @@ -0,0 +1,195 @@ +"""`GET /management/v1/budgets`.""" + +from collections.abc import Mapping, Sequence +from dataclasses import dataclass +from datetime import datetime +from typing import Annotated + +from fastapi import APIRouter, Depends, Request +from pydantic import BaseModel, ConfigDict, JsonValue, TypeAdapter + +from litellm._logging import verbose_proxy_logger +from litellm.proxy._types import ( + CommonProxyErrors, + UserAPIKeyAuth, + user_api_key_has_admin_view, +) +from litellm.proxy.auth.user_api_key_auth import user_api_key_auth +from litellm.proxy.management_endpoints.management_v1.common import ( + MANAGEMENT_V1_PREFIX, + PROBLEM_TYPE_BASE, + ManagementProblem, +) +from litellm.proxy.management_endpoints.management_v1.list_framework import ( + FilterSpec, + ListSpec, + OrderBy, + Scope, + ScopeAll, + ScopeDenied, + SortKey, + Where, + handle_list, +) +from litellm.proxy.utils import PrismaClient +from litellm.types.proxy.management_endpoints.management_v1 import ( + ListResponse, + ProblemDetail, +) + +router = APIRouter(prefix=MANAGEMENT_V1_PREFIX) + + +class BudgetRow(BaseModel): + """The `LiteLLM_BudgetTable` columns this list serves. + + Validating the untyped Prisma row through here is what makes `tpm_limit` / + `rpm_limit` ints: they are `BigInt?` in the schema, which the query engine can + hand back as a decimal string. + """ + + model_config = ConfigDict(from_attributes=True) + + budget_id: str + max_budget: float | None = None + soft_budget: float | None = None + tpm_limit: int | None = None + rpm_limit: int | None = None + budget_duration: str | None = None + budget_reset_at: datetime | None = None + created_at: datetime + updated_at: datetime + + +_BUDGET_ROWS = TypeAdapter(tuple[BudgetRow, ...]) + + +@dataclass(frozen=True, slots=True) +class PrismaBudgetListExecutor: + """The `ListExecutor` half of the budgets list: everything Prisma-shaped lives here.""" + + prisma_client: PrismaClient + + async def count(self, where: Where) -> int: + return int(await self.prisma_client.db.litellm_budgettable.count(where=dict(where))) + + async def find_many(self, where: Where, order: OrderBy, skip: int, take: int) -> Sequence[BudgetRow]: + rows = await self.prisma_client.db.litellm_budgettable.find_many( + where=dict(where), order=list(order), skip=skip, take=take + ) + return _BUDGET_ROWS.validate_python(rows) + + +def _iso(value: datetime | None) -> str | None: + return value.isoformat() if value is not None else None + + +def _serialize(row: BudgetRow) -> Mapping[str, JsonValue]: + return { + "budget_id": row.budget_id, + "max_budget": row.max_budget, + "soft_budget": row.soft_budget, + "tpm_limit": row.tpm_limit, + "rpm_limit": row.rpm_limit, + "budget_duration": row.budget_duration, + "budget_reset_at": _iso(row.budget_reset_at), + "created_at": _iso(row.created_at), + "updated_at": _iso(row.updated_at), + } + + +def _scope(caller: UserAPIKeyAuth) -> Scope: + if user_api_key_has_admin_view(caller): + return ScopeAll() + return ScopeDenied( + detail="Only proxy admins can list budgets, your role={}".format(caller.user_role), + ) + + +# budget_duration is deliberately absent from `sortable`: the column holds strings +# like "7d" and "30d", so a lexicographic ORDER BY puts "30d" ahead of "7d". +BUDGETS_LIST_SPEC: ListSpec[BudgetRow] = ListSpec( + resource="budgets", + sortable=frozenset({"budget_id", "max_budget", "tpm_limit", "rpm_limit", "created_at"}), + searchable=frozenset({"budget_id"}), + filters={ + "budget_duration": FilterSpec(type="string", ops=frozenset({"in", "is_null"})), + "max_budget": FilterSpec(type="number", ops=frozenset({"gte", "lte", "is_null"})), + "created_at": FilterSpec(type="datetime", ops=frozenset({"gte", "lte"})), + }, + default_sort=(SortKey(field="created_at", descending=True), SortKey(field="budget_id", descending=False)), + default_page_size=50, + max_page_size=100, + scope=_scope, + serialize=_serialize, + tiebreaker="budget_id", +) + + +@router.get( + "/budgets", + tags=["budget management"], + dependencies=[Depends(user_api_key_auth)], + response_model=ListResponse, +) +async def list_budgets( + request: Request, + user_api_key_dict: Annotated[UserAPIKeyAuth, Depends(user_api_key_auth)], +) -> ListResponse: + """ + The budgets defined on this proxy, paged, sortable and filterable, for the + Budgets page. + + Readable by a proxy admin or an admin viewer; anyone else is refused 403. The + older `/budget/list` answers with the whole table as a bare array and has no + way to page, sort or filter it. + + `sort` takes a comma-separated list of `budget_id`, `max_budget`, `tpm_limit`, + `rpm_limit` or `created_at`, each optionally prefixed with `-` for descending, + and defaults to `-created_at,budget_id`. `q` is a case-insensitive substring + match on `budget_id`. `page_size` defaults to 50 and is capped at 100. + Filters are `filter[budget_duration][in|is_null]`, + `filter[max_budget][gte|lte|is_null]` and `filter[created_at][gte|lte]`. + + Example curl: + ``` + curl --location --globoff 'http://0.0.0.0:4000/management/v1/budgets?sort=-max_budget&filter[budget_duration][in]=7d,30d&page_size=25' \ + --header 'Authorization: Bearer sk-1234' + ``` + """ + try: + from litellm.proxy.proxy_server import prisma_client + + if prisma_client is None: + raise ManagementProblem( + ProblemDetail( + type=f"{PROBLEM_TYPE_BASE}database-not-connected", + title="Database not connected", + status=503, + detail=CommonProxyErrors.db_not_connected_error.value, + ) + ) + + return await handle_list( + request=request, + spec=BUDGETS_LIST_SPEC, + executor=PrismaBudgetListExecutor(prisma_client=prisma_client), + caller=user_api_key_dict, + ) + + except ManagementProblem: + raise + except Exception as e: # noqa: BLE001 # a driver error answers as a problem document, not the OpenAI error shape + verbose_proxy_logger.exception( + "litellm.proxy.management_endpoints.management_v1.budgets.list_budgets(): Exception occured - {}".format( + str(e) + ) + ) + raise ManagementProblem( + ProblemDetail( + type=f"{PROBLEM_TYPE_BASE}internal-server-error", + title="Internal server error", + status=500, + detail="Failed to list budgets.", + ) + ) diff --git a/tests/e2e/coverage_registry/mgmt.yaml b/tests/e2e/coverage_registry/mgmt.yaml index 68c5ef6b31d..2a0fc5c9f29 100644 --- a/tests/e2e/coverage_registry/mgmt.yaml +++ b/tests/e2e/coverage_registry/mgmt.yaml @@ -57,6 +57,8 @@ - {id: mgmt.budget.update.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "budget_management_endpoints.py:155", rationale: "Limit changes apply"} - {id: mgmt.budget.delete.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "budget_management_endpoints.py:280", rationale: "Clears limits"} - {id: mgmt.budget.list.happy_path, module: mgmt, tier: P1, surface: api, assertions: [happy_path], source: "budget_management_endpoints.py:215", rationale: "Budget enumeration"} +- {id: mgmt.budget.list_v1.happy_path, module: mgmt, tier: P1, surface: api, assertions: [happy_path], source: "management_v1/budgets.py:129", rationale: "Budget enumeration the Budgets page can page, sort and filter"} +- {id: mgmt.budget.list_v1.admin_only, module: mgmt, tier: P1, surface: api, assertions: [admin_only], source: "management_v1/budgets.py:129", rationale: "A caller without admin view is refused, not served an empty page"} - {id: mgmt.callback.list.happy_path, module: mgmt, tier: P2, surface: api, assertions: [happy_path], source: "callback_management_endpoints.py", rationale: "Callback config (smoke)"} - {id: mgmt.cache_settings.update.happy_path, module: mgmt, tier: P2, surface: api, assertions: [happy_path], source: "cache_settings_endpoints.py", rationale: "Cache config (smoke). Deliberately uncovered: the previous test read the live settings and wrote them back, which proves nothing (identical values in, so a no-op POST still passes) while being able to break the deployment. /cache/settings persists what it receives and that row outranks YAML cache_params, re-applied on a timer, so a write that omits ssl or redis_startup_nodes turns a TLS cluster into a plaintext standalone node and every later Redis call hangs. That took out 60 of 72 tests on 2026-07-25. GET cannot round-trip it either: it resolves the stored row overlaid with REDIS_* env and never reads YAML, so on a fresh deploy it cannot see YAML ssl to echo back. A safe test needs an isolated proxy, or LIT-4816 fixed so a partial write cannot downgrade transport. Do not re-add a read-then-write-back test against a shared proxy."} - {id: mgmt.cost_tracking.estimate.happy_path, module: mgmt, tier: P2, surface: api, assertions: [happy_path], source: "cost_tracking_settings.py", rationale: "Cost estimate (smoke)"} diff --git a/tests/e2e/management/test_budget_customer_user_org_e2e.py b/tests/e2e/management/test_budget_customer_user_org_e2e.py index 54cc18b228b..12372bb7cc1 100644 --- a/tests/e2e/management/test_budget_customer_user_org_e2e.py +++ b/tests/e2e/management/test_budget_customer_user_org_e2e.py @@ -19,10 +19,10 @@ import time from collections.abc import Callable import pytest -from pydantic import BaseModel, RootModel +from pydantic import BaseModel, Field, RootModel from e2e_config import unique_marker -from e2e_http import NoBody, unwrap +from e2e_http import NoBody, Success, UnauthorizedError, UnknownApiError, unwrap from lifecycle import ResourceManager from management_client import ManagementClient from models import KeyGenerateBody, OrgInfoParams, OrgNewBody, UserNewBody @@ -44,9 +44,11 @@ def _poll[T](client: ManagementClient, attempt: Callable[[], T | None], failure: class BudgetNewBody(BaseModel): - max_budget: float + max_budget: float | None = None soft_budget: float | None = None budget_duration: str | None = None + budget_id: str | None = None + tpm_limit: int | None = None class BudgetNewResponse(BaseModel): @@ -204,6 +206,164 @@ class TestBudgetManagement: ) +# ---------- /management/v1/budgets ---------- + +_BUDGETS_V1 = "/management/v1/budgets" + + +class BudgetPageParams(BaseModel): + """Query for GET /management/v1/budgets. The filter fields serialize to the + bracketed keys the route reads them under, so nothing here is a raw dict.""" + + q: str | None = None + sort: str | None = None + page: int | None = None + page_size: int | None = None + duration_in: str | None = Field(default=None, serialization_alias="filter[budget_duration][in]") + max_budget_is_null: bool | None = Field(default=None, serialization_alias="filter[max_budget][is_null]") + not_a_parameter: str | None = Field(default=None, serialization_alias="filter[budget_id][eq]") + + +class BudgetPageMeta(BaseModel): + page: int + page_size: int + total_count: int + total_pages: int + + +class BudgetPageLinks(BaseModel): + first: str + prev: str | None = None + next: str | None = None + last: str + + +class BudgetPageRow(BaseModel): + budget_id: str + max_budget: float | None = None + tpm_limit: int | None = None + budget_duration: str | None = None + + +class BudgetPageResponse(BaseModel): + data: list[BudgetPageRow] + meta: BudgetPageMeta + links: BudgetPageLinks + + +def _list_budgets(client: ManagementClient, params: BudgetPageParams) -> BudgetPageResponse: + return unwrap( + client.proxy.transport.get( + _BUDGETS_V1, + headers=client.proxy.transport.master, + params=params, + response_type=BudgetPageResponse, + ) + ) + + +def _list_budget_ids(client: ManagementClient, params: BudgetPageParams) -> tuple[str, ...]: + return tuple(row.budget_id for row in _list_budgets(client, params).data) + + +def _list_status(client: ManagementClient, params: BudgetPageParams, key: str | None = None) -> int: + headers = client.proxy.transport.master if key is None else client.proxy.transport.bearer(key) + outcome = client.proxy.transport.get( + _BUDGETS_V1, headers=headers, params=params, response_type=BudgetPageResponse + ) + match outcome: + case Success(status_code=status_code): + return status_code + case UnauthorizedError(): + return 401 + case UnknownApiError(status_code=status_code): + return status_code + case _: + raise AssertionError(outcome) + + +class TestBudgetListV1: + """The paged, sorted, filtered budget list the Budgets page reads. + + Every test tags its own budgets with a marker in the budget_id and searches on + it, so budgets left behind by other suites cannot move the assertions. + """ + + @pytest.mark.covers("mgmt.budget.list_v1.happy_path") + def test_sorts_pages_and_filters_the_budgets_it_created( + self, client: ManagementClient, resources: ResourceManager + ) -> None: + marker = unique_marker() + small, medium, large = (f"{marker}-small", f"{marker}-medium", f"{marker}-large") + for budget_id, max_budget, duration in ( + (small, 1.0, "7d"), + (medium, 2.0, "30d"), + (large, 3.0, "30d"), + ): + _create_budget( + client, + resources, + BudgetNewBody( + budget_id=budget_id, max_budget=max_budget, budget_duration=duration, tpm_limit=60000 + ), + ) + + mine = BudgetPageParams(q=marker, sort="-max_budget") + _ = _poll( + client, + lambda: mine if len(_list_budget_ids(client, mine)) == 3 else None, + f"{_BUDGETS_V1} never listed all three budgets tagged {marker}", + ) + + assert _list_budget_ids(client, mine) == (large, medium, small) + + page_two = _list_budgets(client, BudgetPageParams(q=marker, sort="-max_budget", page=2, page_size=1)) + assert [row.budget_id for row in page_two.data] == [medium] + assert page_two.meta.total_count == 3 + assert page_two.meta.total_pages == 3 + assert page_two.meta.page_size == 1 + assert page_two.links.prev is not None and page_two.links.next is not None + + assert set(_list_budget_ids(client, BudgetPageParams(q=marker, duration_in="30d"))) == {medium, large} + + limits = _list_budgets(client, BudgetPageParams(q=marker, sort="budget_id")).data + assert [row.tpm_limit for row in limits] == [60000, 60000, 60000] + + @pytest.mark.covers("mgmt.budget.list_v1.happy_path") + def test_is_null_finds_the_budget_left_uncapped( + self, client: ManagementClient, resources: ResourceManager + ) -> None: + marker = unique_marker() + uncapped = _create_budget(client, resources, BudgetNewBody(budget_id=f"{marker}-uncapped")) + _ = _create_budget(client, resources, BudgetNewBody(budget_id=f"{marker}-capped", max_budget=4.0)) + + params = BudgetPageParams(q=marker, max_budget_is_null=True) + found = _poll( + client, + lambda: params if _list_budget_ids(client, params) == (uncapped,) else None, + f"{_BUDGETS_V1} never isolated the uncapped budget {uncapped}", + ) + + assert [row.max_budget for row in _list_budgets(client, found).data] == [None] + + @pytest.mark.covers("mgmt.budget.list_v1.happy_path") + def test_refuses_a_sort_field_and_a_parameter_it_does_not_support(self, client: ManagementClient) -> None: + assert _list_status(client, BudgetPageParams(sort="budget_duration")) == 400 + assert _list_status(client, BudgetPageParams(not_a_parameter="b-1")) == 400 + + @pytest.mark.covers("mgmt.budget.list_v1.admin_only") + def test_is_refused_for_a_non_admin_key(self, client: ManagementClient, resources: ResourceManager) -> None: + key = client.proxy.generate_key(KeyGenerateBody()) + resources.defer(lambda: client.proxy.delete_key(key)) + + status = _list_status(client, BudgetPageParams(), key=key) + + assert status in (401, 403), ( + f"a non-admin key listing budgets must be refused 401/403, got {status}. Serving 200 with an " + f"empty page would read as 'this proxy has no budgets'" + ) + + # ---------- customer / end-user ---------- diff --git a/tests/test_litellm/proxy/auth/test_admin_viewer_handler_access.py b/tests/test_litellm/proxy/auth/test_admin_viewer_handler_access.py index b0d2595e48c..9f4a801eb83 100644 --- a/tests/test_litellm/proxy/auth/test_admin_viewer_handler_access.py +++ b/tests/test_litellm/proxy/auth/test_admin_viewer_handler_access.py @@ -51,6 +51,7 @@ def admin_viewer_client(monkeypatch): mock_budget_table = MagicMock() mock_budget_table.find_many = AsyncMock(return_value=[]) mock_budget_table.find_first = AsyncMock(return_value=None) + mock_budget_table.count = AsyncMock(return_value=0) mock_invitation_table = MagicMock() mock_invitation_table.find_unique = AsyncMock(return_value=None) @@ -106,6 +107,14 @@ def test_budget_list_allows_admin_viewer(admin_viewer_client): assert resp.status_code == 200, resp.text +def test_management_v1_budgets_allows_admin_viewer(admin_viewer_client): + """`/management/v1/budgets` is the paged/sortable budget list; same read tier as + `/budget/list`, and it answers 403 rather than an empty page when it refuses.""" + resp = admin_viewer_client.get("/management/v1/budgets") + _assert_not_role_blocked(resp) + assert resp.status_code == 200, resp.text + + def test_budget_settings_allows_admin_viewer(admin_viewer_client): """`/budget/settings` describes a budget's fields; read-only.""" resp = admin_viewer_client.get("/budget/settings", params={"budget_id": "b1"}) diff --git a/tests/test_litellm/proxy/auth/test_route_checks.py b/tests/test_litellm/proxy/auth/test_route_checks.py index a6d4dc63697..06764139eda 100644 --- a/tests/test_litellm/proxy/auth/test_route_checks.py +++ b/tests/test_litellm/proxy/auth/test_route_checks.py @@ -1960,6 +1960,7 @@ ADMIN_VIEWER_SETTINGS_ROUTES = [ "/config/field/info", # Budgets page "/budget/list", + "/management/v1/budgets", "/budget/settings", # Invitation viewing (admin viewer cannot create/delete; can read) "/invitation/info", diff --git a/tests/test_litellm/proxy/management_endpoints/management_v1/test_budgets.py b/tests/test_litellm/proxy/management_endpoints/management_v1/test_budgets.py new file mode 100644 index 00000000000..500c072bc7d --- /dev/null +++ b/tests/test_litellm/proxy/management_endpoints/management_v1/test_budgets.py @@ -0,0 +1,471 @@ +from datetime import datetime, timezone +from typing import Any +from unittest.mock import AsyncMock, MagicMock + +import pytest +from fastapi import FastAPI, Request +from fastapi.exceptions import RequestValidationError +from fastapi.testclient import TestClient + +from litellm.proxy._types import LiteLLMRoutes, LitellmUserRoles +from litellm.proxy.auth.user_api_key_auth import UserAPIKeyAuth, user_api_key_auth +from litellm.proxy.management_endpoints.management_v1 import router +from litellm.proxy.management_endpoints.management_v1.budgets import BUDGETS_LIST_SPEC +from litellm.proxy.management_endpoints.management_v1.common import ( + MANAGEMENT_V1_PREFIX, + PROBLEM_TYPE_BASE, + ManagementProblem, + problem_response, +) +from litellm.proxy.management_endpoints.management_v1.list_framework import ( + ScopeWhere, + build_query_plan, +) +from litellm.types.proxy.management_endpoints.management_v1 import ProblemDetail + +app = FastAPI() + + +@app.exception_handler(ManagementProblem) +async def management_problem_exception_handler(request: Request, exc: ManagementProblem): + return problem_response(exc.problem) + + +@app.exception_handler(RequestValidationError) +async def validation_exception_handler(request: Request, exc: RequestValidationError): + return problem_response( + ProblemDetail( + type=f"{PROBLEM_TYPE_BASE}invalid-query-parameter", + title="Invalid query parameter", + status=400, + detail="The request query parameters are invalid.", + ) + ) + + +app.include_router(router) +client = TestClient(app) + +BUDGETS_PATH = f"{MANAGEMENT_V1_PREFIX}/budgets" +SORTABLE = ["budget_id", "created_at", "max_budget", "rpm_limit", "tpm_limit"] + + +def _row(budget_id: str, **overrides: Any) -> dict[str, Any]: + return { + "budget_id": budget_id, + "max_budget": 10.0, + "soft_budget": None, + "tpm_limit": None, + "rpm_limit": None, + "budget_duration": "30d", + "budget_reset_at": None, + "created_at": datetime(2026, 7, 20, 12, 0, tzinfo=timezone.utc), + "updated_at": datetime(2026, 7, 21, 12, 0, tzinfo=timezone.utc), + **overrides, + } + + +@pytest.fixture +def budget_table(monkeypatch): + table = MagicMock() + table.count = AsyncMock(return_value=0) + table.find_many = AsyncMock(return_value=[]) + prisma_client = MagicMock() + prisma_client.db.litellm_budgettable = table + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", prisma_client) + return table + + +@pytest.fixture +def as_proxy_admin(): + app.dependency_overrides[user_api_key_auth] = lambda: UserAPIKeyAuth( + user_id="admin", user_role=LitellmUserRoles.PROXY_ADMIN + ) + yield + app.dependency_overrides.clear() + + +def _serve(budget_table, rows: list[dict[str, Any]], total: int | None = None) -> None: + budget_table.find_many = AsyncMock(return_value=rows) + budget_table.count = AsyncMock(return_value=len(rows) if total is None else total) + + +def _as_role(role: LitellmUserRoles): + original = app.dependency_overrides.copy() + app.dependency_overrides[user_api_key_auth] = lambda: UserAPIKeyAuth(user_id="u", user_role=role) + return original + + +def _get(query: str = ""): + suffix = f"?{query}" if query else "" + return client.get(f"{BUDGETS_PATH}{suffix}", headers={"Authorization": "Bearer k"}) + + +def test_returns_flat_rows_in_the_control_plane_envelope(budget_table, as_proxy_admin): + """`{data, meta, links}` with flat rows; no JSON:API `{type, id, attributes}` wrapper.""" + _serve(budget_table, [_row("b-1")]) + + response = _get() + + assert response.status_code == 200 + body = response.json() + assert set(body) == {"data", "meta", "links"} + assert body["data"][0]["budget_id"] == "b-1" + assert "attributes" not in body["data"][0] + + +def test_serves_the_columns_the_budgets_page_renders(budget_table, as_proxy_admin): + _serve(budget_table, [_row("b-1", soft_budget=5.0, budget_reset_at=datetime(2026, 8, 1, tzinfo=timezone.utc))]) + + row = _get().json()["data"][0] + + assert set(row) == { + "budget_id", + "max_budget", + "soft_budget", + "tpm_limit", + "rpm_limit", + "budget_duration", + "budget_reset_at", + "created_at", + "updated_at", + } + assert row["soft_budget"] == 5.0 + assert row["budget_reset_at"].startswith("2026-08-01T00:00:00") + + +def test_defaults_to_newest_first_with_budget_id_breaking_ties(budget_table, as_proxy_admin): + """Two budgets created in the same transaction share a created_at; without the + tiebreaker their relative order is undefined and pages can repeat or drop rows.""" + _serve(budget_table, []) + + _get() + + assert budget_table.find_many.call_args.kwargs["order"] == [ + {"created_at": "desc"}, + {"budget_id": "asc"}, + ] + + +def test_appends_the_tiebreaker_to_an_explicit_sort(budget_table, as_proxy_admin): + _serve(budget_table, []) + + _get("sort=-max_budget") + + assert budget_table.find_many.call_args.kwargs["order"] == [ + {"max_budget": "desc"}, + {"budget_id": "asc"}, + ] + + +def test_does_not_duplicate_the_tiebreaker_when_it_is_sorted_on(budget_table, as_proxy_admin): + _serve(budget_table, []) + + _get("sort=-budget_id") + + assert budget_table.find_many.call_args.kwargs["order"] == [{"budget_id": "desc"}] + + +def test_refuses_to_sort_on_budget_duration(budget_table, as_proxy_admin): + """The column holds "7d"/"30d", so a lexicographic ORDER BY would put "30d" + before "7d" and silently mis-order the page.""" + _serve(budget_table, []) + + response = _get("sort=budget_duration") + + assert response.status_code == 400 + assert response.headers["content-type"].startswith("application/problem+json") + body = response.json() + assert "budget_duration" in body["detail"] + assert body["allowed"] == SORTABLE + budget_table.find_many.assert_not_called() + + +def test_the_advertised_sort_fields_are_the_ones_that_work(budget_table, as_proxy_admin): + """Guards the rejection above against drifting from what the spec actually accepts.""" + _serve(budget_table, []) + + for field in SORTABLE: + assert _get(f"sort={field}").status_code == 200, field + assert sorted(BUDGETS_LIST_SPEC.sortable) == SORTABLE + + +def test_rejects_an_unknown_query_parameter(budget_table, as_proxy_admin): + """A silently ignored filter over-returns budgets, which is worse than a rejected request.""" + _serve(budget_table, []) + + response = _get("filtre[max_budget][gte]=5") + + assert response.status_code == 400 + assert response.headers["content-type"].startswith("application/problem+json") + body = response.json() + assert "filtre[max_budget][gte]" in body["detail"] + assert "filter[max_budget][gte]" in body["allowed"] + budget_table.count.assert_not_called() + budget_table.find_many.assert_not_called() + + +def test_rejects_an_operator_the_filter_does_not_declare(budget_table, as_proxy_admin): + """`max_budget` takes ranges, not `in`; accepting an undeclared operator is how a + filter starts meaning something the query planner never checked.""" + _serve(budget_table, []) + + assert _get("filter[max_budget][in]=5,10").status_code == 400 + assert _get("filter[created_at][is_null]=true").status_code == 400 + + +def test_omitted_page_size_serves_fifty(budget_table, as_proxy_admin): + _serve(budget_table, []) + + body = _get().json() + + assert body["meta"]["page_size"] == 50 + assert budget_table.find_many.call_args.kwargs["take"] == 50 + + +def test_clamps_an_oversized_page_size_to_a_hundred(budget_table, as_proxy_admin): + """Unclamped, one request can ask the proxy to serialize the whole budget table.""" + _serve(budget_table, []) + + body = _get("page_size=500").json() + + assert body["meta"]["page_size"] == 100 + assert budget_table.find_many.call_args.kwargs["take"] == 100 + + +def test_offsets_by_page(budget_table, as_proxy_admin): + _serve(budget_table, []) + + _get("page=3&page_size=25") + + assert budget_table.find_many.call_args.kwargs["skip"] == 50 + assert budget_table.find_many.call_args.kwargs["take"] == 25 + + +@pytest.mark.parametrize( + "role", + [ + LitellmUserRoles.INTERNAL_USER, + LitellmUserRoles.INTERNAL_USER_VIEW_ONLY, + LitellmUserRoles.TEAM, + ], +) +def test_refuses_a_caller_without_admin_view(budget_table, role): + """Budgets are proxy-wide, so a caller who cannot read all of them must be told + so. Answering 200 with an empty list would read as "there are no budgets".""" + _serve(budget_table, [_row("b-1")]) + original = _as_role(role) + try: + response = _get() + finally: + app.dependency_overrides = original + + assert response.status_code == 403 + assert response.headers["content-type"].startswith("application/problem+json") + assert response.json()["status"] == 403 + budget_table.count.assert_not_called() + budget_table.find_many.assert_not_called() + + +@pytest.mark.parametrize("role", [LitellmUserRoles.PROXY_ADMIN, LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY]) +def test_admins_and_admin_viewers_may_read_every_budget(budget_table, role): + _serve(budget_table, [_row("b-1")]) + original = _as_role(role) + try: + response = _get() + finally: + app.dependency_overrides = original + + assert response.status_code == 200 + assert [row["budget_id"] for row in response.json()["data"]] == ["b-1"] + + +def test_a_denied_caller_stays_denied_whatever_they_filter_on(budget_table): + """The scope decision reads the caller, never the query string.""" + _serve(budget_table, [_row("b-1")]) + original = _as_role(LitellmUserRoles.INTERNAL_USER) + try: + response = _get("filter[max_budget][gte]=0&q=b-") + finally: + app.dependency_overrides = original + + assert response.status_code == 403 + + +def test_a_filter_narrows_the_scope_predicate_instead_of_replacing_it(budget_table, as_proxy_admin): + """A filter is ANDed in. Assigning it over the scope clause is what would let a + caller widen their own read.""" + _serve(budget_table, []) + + _get("filter[max_budget][gte]=5") + + where = budget_table.find_many.call_args.kwargs["where"] + assert {"max_budget": {"gte": 5.0}} in where["AND"] + + +def test_a_scoped_caller_keeps_their_scope_clause_alongside_their_filter(): + """Same spec, driven through the planner with a row-scoped caller: the scope + clause has to survive next to whatever the caller filtered on.""" + request = Request( + { + "type": "http", + "method": "GET", + "path": BUDGETS_PATH, + "headers": [], + "query_string": b"filter[max_budget][gte]=5", + } + ) + + plan = build_query_plan(request, BUDGETS_LIST_SPEC, ScopeWhere(where={"budget_id": {"in": ("b-1",)}})) + + assert {"budget_id": {"in": ("b-1",)}} in plan.where["AND"] + assert {"max_budget": {"gte": 5.0}} in plan.where["AND"] + + +def test_q_matches_budget_id_case_insensitively(budget_table, as_proxy_admin): + """budget_id is the only text identity on the row; matching anything else would + return budgets whose ids do not contain what the user typed.""" + _serve(budget_table, []) + + _get("q=Prod") + + where = budget_table.find_many.call_args.kwargs["where"] + assert {"OR": ({"budget_id": {"contains": "Prod", "mode": "insensitive"}},)} in where["AND"] + + +def test_q_does_not_search_any_other_column(budget_table, as_proxy_admin): + _serve(budget_table, []) + + _get("q=30d") + + searched = budget_table.find_many.call_args.kwargs["where"]["AND"][0]["OR"] + assert [next(iter(clause)) for clause in searched] == ["budget_id"] + assert BUDGETS_LIST_SPEC.searchable == frozenset({"budget_id"}) + + +def test_is_null_selects_the_unlimited_budgets(budget_table, as_proxy_admin): + """"Unlimited" is max_budget IS NULL; `max_budget = 0` would be a hard zero cap.""" + _serve(budget_table, [_row("b-unlimited", max_budget=None)]) + + body = _get("filter[max_budget][is_null]=true").json() + + assert {"max_budget": None} in budget_table.find_many.call_args.kwargs["where"]["AND"] + assert body["data"][0]["max_budget"] is None + + +def test_is_null_false_selects_the_capped_budgets(budget_table, as_proxy_admin): + _serve(budget_table, []) + + _get("filter[max_budget][is_null]=false") + + assert {"max_budget": {"not": None}} in budget_table.find_many.call_args.kwargs["where"]["AND"] + + +def test_in_filter_splits_the_requested_durations(budget_table, as_proxy_admin): + _serve(budget_table, []) + + _get("filter[budget_duration][in]=7d,30d") + + assert {"budget_duration": {"in": ("7d", "30d")}} in budget_table.find_many.call_args.kwargs["where"]["AND"] + + +def test_created_at_range_is_read_as_a_timestamp(budget_table, as_proxy_admin): + _serve(budget_table, []) + + _get("filter[created_at][gte]=2026-07-01T00:00:00%2B00:00") + + assert { + "created_at": {"gte": datetime(2026, 7, 1, tzinfo=timezone.utc)} + } in budget_table.find_many.call_args.kwargs["where"]["AND"] + + +def test_rejects_a_filter_value_that_is_not_of_the_declared_type(budget_table, as_proxy_admin): + _serve(budget_table, []) + + assert _get("filter[max_budget][gte]=lots").status_code == 400 + assert _get("filter[created_at][gte]=yesterday").status_code == 400 + + +def test_reports_the_total_and_links_every_page_on_a_middle_page(budget_table, as_proxy_admin): + """The Budgets page renders a page count, so the total has to be the match total, + not the length of the page it just received.""" + _serve(budget_table, [_row("b-3"), _row("b-4")], total=7) + + body = _get("page=2&page_size=2").json() + + assert body["meta"] == {"page": 2, "page_size": 2, "total_count": 7, "total_pages": 4} + links = body["links"] + assert "page=1" in links["first"] and "page_size=2" in links["first"] + assert "page=1" in links["prev"] + assert "page=3" in links["next"] + assert "page=4" in links["last"] + assert "page=2" in links["self"] + + +def test_the_last_page_has_no_next(budget_table, as_proxy_admin): + _serve(budget_table, [_row("b-5")], total=5) + + links = _get("page=3&page_size=2").json()["links"] + + assert links["next"] is None + assert "page=2" in links["prev"] + + +def test_the_first_page_has_no_prev(budget_table, as_proxy_admin): + _serve(budget_table, [_row("b-1")], total=5) + + links = _get("page_size=2").json()["links"] + + assert links["prev"] is None + assert "page=2" in links["next"] + + +def test_an_empty_table_still_links_a_first_and_last_page(budget_table, as_proxy_admin): + _serve(budget_table, [], total=0) + + body = _get().json() + + assert body["meta"]["total_count"] == 0 + assert body["meta"]["total_pages"] == 0 + assert "page=1" in body["links"]["first"] and "page=1" in body["links"]["last"] + + +def test_counts_over_the_same_predicate_it_pages(budget_table, as_proxy_admin): + """A total counted without the caller's filter would page through rows the + filter excluded.""" + _serve(budget_table, [], total=0) + + _get("filter[budget_duration][in]=30d") + + assert budget_table.count.call_args.kwargs["where"] == budget_table.find_many.call_args.kwargs["where"] + + +def test_bigint_limits_serialize_as_json_numbers(budget_table, as_proxy_admin): + """tpm_limit/rpm_limit are BigInt? in Prisma; the query engine can hand them back + as decimal strings, and a quoted "60000" breaks arithmetic in the dashboard.""" + _serve(budget_table, [_row("b-1", tpm_limit="60000", rpm_limit=1200)]) + + row = _get().json()["data"][0] + + assert row["tpm_limit"] == 60000 + assert row["rpm_limit"] == 1200 + assert isinstance(row["tpm_limit"], int) and not isinstance(row["tpm_limit"], bool) + assert '"tpm_limit": "60000"' not in _get().text + + +def test_reports_a_missing_database_as_a_problem_document(monkeypatch, as_proxy_admin): + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None) + + response = _get() + + assert response.status_code == 503 + assert response.headers["content-type"].startswith("application/problem+json") + + +def test_is_reachable_by_the_roles_that_can_open_the_budgets_page(): + """Route-level auth gate, which the dependency_overrides above bypass. The handler's + admin-view check is dead code if RouteChecks rejects the role first.""" + assert BUDGETS_PATH in LiteLLMRoutes.admin_viewer_routes.value + assert ("/budget/list" in LiteLLMRoutes.admin_viewer_routes.value) == ( + BUDGETS_PATH in LiteLLMRoutes.admin_viewer_routes.value + ) diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index 380b6545da8..752b762e773 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -7169,6 +7169,43 @@ export interface paths { patch?: never; trace?: never; }; + "/management/v1/budgets": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List Budgets + * @description The budgets defined on this proxy, paged, sortable and filterable, for the + * Budgets page. + * + * Readable by a proxy admin or an admin viewer; anyone else is refused 403. The + * older `/budget/list` answers with the whole table as a bare array and has no + * way to page, sort or filter it. + * + * `sort` takes a comma-separated list of `budget_id`, `max_budget`, `tpm_limit`, + * `rpm_limit` or `created_at`, each optionally prefixed with `-` for descending, + * and defaults to `-created_at,budget_id`. `q` is a case-insensitive substring + * match on `budget_id`. `page_size` defaults to 50 and is capped at 100. + * Filters are `filter[budget_duration][in|is_null]`, + * `filter[max_budget][gte|lte|is_null]` and `filter[created_at][gte|lte]`. + * + * Example curl: + * ``` + * curl --location --globoff 'http://0.0.0.0:4000/management/v1/budgets?sort=-max_budget&filter[budget_duration][in]=7d,30d&page_size=25' --header 'Authorization: Bearer sk-1234' + * ``` + */ + get: operations["list_budgets_management_v1_budgets_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/management/v1/spend_logs/end_users": { parameters: { query?: never; @@ -24771,6 +24808,7 @@ export interface components { /** Updated By */ updated_by?: string | null; }; + JsonValue: unknown; /** KeyHealthResponse */ KeyHealthResponse: { /** @@ -24922,6 +24960,36 @@ export interface components { /** Guardrails */ guardrails: components["schemas"]["GuardrailInfoResponse"][]; }; + /** + * ListLinks + * @description Hypermedia for an entity list. `first`/`last` exist here because `total_pages` is known. + */ + ListLinks: { + /** First */ + first: string; + /** Last */ + last: string; + /** Next */ + next?: string | null; + /** Prev */ + prev?: string | null; + /** Self */ + self: string; + }; + /** + * ListMeta + * @description An entity list can afford the COUNT(*) a facet cannot, so it reports a real total. + */ + ListMeta: { + /** Page */ + page: number; + /** Page Size */ + page_size: number; + /** Total Count */ + total_count: number; + /** Total Pages */ + total_pages: number; + }; /** * ListPluginsResponse * @description Response from listing plugins. @@ -24937,6 +25005,18 @@ export interface components { /** Prompts */ prompts: components["schemas"]["PromptSpec"][]; }; + /** + * ListResponse + * @description One page of an entity collection. Rows are flat: no `{type, id, attributes}` wrapper. + */ + ListResponse: { + /** Data */ + data: { + [key: string]: components["schemas"]["JsonValue"]; + }[]; + links: components["schemas"]["ListLinks"]; + meta: components["schemas"]["ListMeta"]; + }; /** * ListRunsResponse * @description Response from listing runs @@ -43416,6 +43496,26 @@ export interface operations { }; }; }; + list_budgets_management_v1_budgets_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListResponse"]; + }; + }; + }; + }; list_spend_log_end_users_management_v1_spend_logs_end_users_get: { parameters: { query: { From 86da406f998d42980b283106de674f4b52193c9e Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Thu, 30 Jul 2026 22:07:51 -0700 Subject: [PATCH 06/92] fix(type-discipline): exempt values frozen in place by tuple/frozenset/MappingProxyType from LIT002 --- scripts/check_type_discipline.py | 34 +++++++++++++++++-- .../test_check_type_discipline.py | 16 +++++++++ type-discipline-budget.json | 2 +- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/scripts/check_type_discipline.py b/scripts/check_type_discipline.py index 809dc141eb8..88679f28190 100644 --- a/scripts/check_type_discipline.py +++ b/scripts/check_type_discipline.py @@ -20,7 +20,10 @@ LIT002 Mutable-collection *construction*: a list/dict/set literal or comprehens generator (`tuple(f(x) for x in xs)`), a tuple literal, or a frozen dataclass / NamedTuple / ReadOnly TypedDict. Generator expressions and `tuple`/`frozenset` calls are not construction and pass. Annotation-internal lists (`Callable[[int], - str]`) are exempt. Suppress with `# mutable-ok: `. + str]`) are exempt, as is a value passed directly to a freezing wrapper + (`tuple(...)`, `frozenset(...)`, `MappingProxyType(...)`): it is frozen before + it can escape, though anything mutable nested inside it still counts. + Suppress with `# mutable-ok: `. LIT003 noqa suppression without rule codes or without a reason. Required shape: `# noqa: TID251 # ` LIT004 pyright/mypy ignore without bracketed codes or without a reason. @@ -90,6 +93,7 @@ MUTABLE_CONSTRUCTORS = frozenset(( # are common methods (e.g. pydantic's `model.dict()`), not collection construction. A # qualified `collections.deque(...)` still counts. QUALIFIED_CONSTRUCTORS = MUTABLE_CONSTRUCTORS - frozenset(("dict", "list", "set")) +FREEZING_WRAPPERS = frozenset(("tuple", "frozenset", "MappingProxyType")) UNSAFE_GUARDS = frozenset(("TypeGuard", "TypeIs")) MIN_REASON_LEN = 3 @@ -382,6 +386,31 @@ def _annotation_node_ids(tree: ast.AST) -> frozenset[int]: ) +def _callable_name(func: ast.expr) -> str | None: + if isinstance(func, ast.Name): + return func.id + if isinstance(func, ast.Attribute): + return func.attr + return None + + +def _frozen_argument_ids(tree: ast.AST) -> frozenset[int]: + """ids() of every expression passed directly to a freezing wrapper. + + `MappingProxyType({...})`, `frozenset({...})`, and `tuple([...])` freeze their + argument before it can escape, so the literal inside is a one-shot build, not a + mutable value anyone can grow later. Only the argument itself is exempt; a + mutable collection nested inside it still trips LIT002. + """ + return frozenset( + id(node.args[0]) + for node in ast.walk(tree) + if isinstance(node, ast.Call) + and len(node.args) == 1 + and _callable_name(node.func) in FREEZING_WRAPPERS + ) + + def _construction_kind(node: ast.expr) -> str | None: """Human label if `node` builds a mutable collection, else None.""" if isinstance(node, ast.List): @@ -407,8 +436,9 @@ def _construction_kind(node: ast.expr) -> str | None: def iter_construction_violations(path: Path, tree: ast.AST, comments: Comments) -> Iterator[Violation]: in_annotation = _annotation_node_ids(tree) + frozen_arguments = _frozen_argument_ids(tree) for node in ast.walk(tree): - if not isinstance(node, ast.expr) or id(node) in in_annotation: + if not isinstance(node, ast.expr) or id(node) in in_annotation or id(node) in frozen_arguments: continue kind = _construction_kind(node) if kind is None or node.lineno in comments.mutable_ok_lines: diff --git a/tests/test_litellm/test_check_type_discipline.py b/tests/test_litellm/test_check_type_discipline.py index 13edf6d1a95..f624eb926d1 100644 --- a/tests/test_litellm/test_check_type_discipline.py +++ b/tests/test_litellm/test_check_type_discipline.py @@ -152,6 +152,22 @@ def test_qualified_collections_constructors_still_count(tmp_path): assert "LIT002" in _codes(tmp_path, "import collections\nm = collections.defaultdict(list)\n") +def test_value_frozen_by_wrapper_is_exempt(tmp_path): + assert "LIT002" not in _codes(tmp_path, "from types import MappingProxyType\nm = MappingProxyType({'a': 1})\n") + assert "LIT002" not in _codes(tmp_path, "import types\nm = types.MappingProxyType({'a': 1})\n") + assert "LIT002" not in _codes(tmp_path, "from types import MappingProxyType\nm = MappingProxyType(dict(a=1))\n") + assert "LIT002" not in _codes(tmp_path, "f = frozenset({1, 2})\n") + assert "LIT002" not in _codes(tmp_path, "t = tuple([1, 2])\n") + + +def test_mutable_nested_inside_frozen_wrapper_still_counts(tmp_path): + assert "LIT002" in _codes(tmp_path, "from types import MappingProxyType\nm = MappingProxyType({'a': []})\n") + + +def test_unfrozen_literal_still_counts(tmp_path): + assert "LIT002" in _codes(tmp_path, "from types import MappingProxyType\nd = {'a': 1}\nm = MappingProxyType(d)\n") + + def test_mutable_ok_with_reason_suppresses_both_rules(tmp_path): codes = _codes(tmp_path, "x: dict[str, int] = {} # mutable-ok: in-place buffer mutated hot path\n") assert "LIT001" not in codes diff --git a/type-discipline-budget.json b/type-discipline-budget.json index c9a1b59cc06..2d5e4dd3a50 100644 --- a/type-discipline-budget.json +++ b/type-discipline-budget.json @@ -3,7 +3,7 @@ "limit": 23253 }, "LIT002": { - "limit": 27427 + "limit": 27280 }, "LIT003": { "limit": 292 From 7be3ddd0ffbd5de7d504fb1016c999228c964a11 Mon Sep 17 00:00:00 2001 From: Tin Date: Sat, 25 Jul 2026 15:09:51 -0700 Subject: [PATCH 07/92] fix(mcp): recover the tool-name prefix boundary from registered prefixes The gateway publishes a tool as `` and has to recover that boundary on the way back in, to compare a called name against a toolset or allow/deny list and to rebuild the native name sent upstream. Several sites recovered it by cutting at the FIRST separator and others reconstructed it by hand from `MCPServer.name` with a literal `-`, so both disagreed with the prefix the server actually publishes `get_server_prefix` publishes short_prefix, then alias, then server_name, then server_id; it never reads `name`. A server with no alias therefore publishes its hyphen-filled UUID `server_id` as the prefix, and cutting at the first separator leaves most of the UUID glued to the tool name. Every comparison against the stored `(server_id, tool_name)` toolset row then misses: an allowlist denies a tool the list endpoint just advertised, and a disallowed entry stops blocking, which fails open Recover the boundary in one place instead. `match_known_server_prefix` matches a name against the server's registered prefixes, longest first so a prefix that itself contains the separator beats a shorter prefix that is merely its leading segment, and returns None when the name carries none of them. `strip_known_server_prefix` and `is_tool_name_prefixed` both delegate to it, and the sites that receive a wire name call the owner rather than re-deriving the boundary. `split_server_prefix_from_name` stays for the routing pair it was written for, with a docstring saying so The server-level permission checks are the other half. They run after the boundary is already resolved, so their input is bare and the correction there is to derive the wire form rather than strip it back out; stripping a stored entry a second time cuts a boundary the caller already consumed, which breaks a native name that itself opens with the server prefix. Deriving from `get_server_prefix` alone is not enough either, because routing resolves an inbound name against every prefix from `iter_known_server_prefixes`, so enforcement keyed to the published spelling answers for fewer names than are reachable. Turning `LITELLM_USE_SHORT_MCP_TOOL_PREFIX` on republishes every tool under the short ID while an entry stored under the alias stays routable and silently stops being enforced, which is a fail-open on a config nobody edited. `iter_known_tool_name_spellings` yields the bare name plus the wire form under each accepted prefix, and the allow list, the deny list, `allowed_params` and the routing map that `_create_prefixed_tools` builds now all key off that one function, so the set of names enforcement honors and the set routing accepts cannot drift apart `_tool_name_matches` takes the server as a required argument, so a future caller cannot silently fall back to guessing, and it matches against that same spelling set, so `tools/list` hides exactly what dispatch refuses. Answering for fewer spellings in the filter than enforcement honors leaves a blocked tool advertised, which is how the alias-form entry above stayed listed even once the call was refused. The OpenAPI registry lookup builds its key the same way registration does, via `add_server_prefix_to_name` and `get_server_prefix`, because registration used exactly one key; a server whose `name` differs from its published prefix stops missing its own tools --- .../mcp_server/mcp_server_manager.py | 64 +-- .../mcp_server/rest_endpoints.py | 2 +- .../proxy/_experimental/mcp_server/server.py | 50 +- .../proxy/_experimental/mcp_server/utils.py | 66 ++- tests/mcp_tests/test_mcp_server.py | 3 + .../mcp_server/test_mcp_server.py | 259 +++++++++- .../mcp_server/test_mcp_server_manager.py | 468 ++++++++++++++++++ .../mcp_server/test_openapi_tool_auth.py | 4 + .../mcp_server/test_short_mcp_tool_prefix.py | 66 +++ 9 files changed, 902 insertions(+), 80 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index ae1095da336..285e7b26104 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -116,12 +116,13 @@ from litellm.proxy._experimental.mcp_server.utils import ( get_server_prefix, interpolate_headers, is_short_mcp_tool_prefix_enabled, - is_tool_name_prefixed, iter_known_server_prefixes, + iter_known_tool_name_spellings, + match_known_server_prefix, merge_mcp_headers, normalize_server_name, parse_admin_env_vars, - split_server_prefix_from_name, + strip_known_server_prefix, validate_mcp_server_name, ) from litellm.proxy._types import ( @@ -4185,10 +4186,8 @@ class MCPServerManager: # Register every known prefix form (alias, server_name, server_id, # short ID) so call_tool can resolve regardless of which form a # caller / cached client is using. - self.tool_name_to_mcp_server_name_mapping[original_name] = prefix - for known_prefix in iter_known_server_prefixes(server): - qualified = add_server_prefix_to_name(original_name, known_prefix) - self.tool_name_to_mcp_server_name_mapping[qualified] = prefix + for spelling in iter_known_tool_name_spellings(original_name, server): + self.tool_name_to_mcp_server_name_mapping[spelling] = prefix verbose_logger.info(f"Successfully fetched {len(prefixed_tools)} tools from server {server.name}") return prefixed_tools @@ -4261,20 +4260,27 @@ class MCPServerManager: def check_allowed_or_banned_tools(self, tool_name: str, server: MCPServer) -> bool: """ - Check if the tool is allowed or banned for the given server + Check if the tool is allowed or banned for the given server. + + ``tool_name`` is bare: every caller resolves the boundary against the + server's registered prefixes before dispatch (``server.py``'s + ``original_tool_name``, the Responses handler's ``sanitized_tool_name``). + Stored entries are matched by deriving the spellings routing accepts + rather than by stripping the entries, which would cut a second boundary + out of a native name that itself opens with the server prefix. """ from litellm.proxy._experimental.mcp_server.utils import ( server_applies_tool_allowlist, ) + spellings = tuple(iter_known_tool_name_spellings(tool_name, server)) + if server_applies_tool_allowlist(server): if not server.allowed_tools: return False - return tool_name in server.allowed_tools or f"{server.name}-{tool_name}" in server.allowed_tools + return any(spelling in server.allowed_tools for spelling in spellings) if server.disallowed_tools: - return ( - tool_name not in server.disallowed_tools and f"{server.name}-{tool_name}" not in server.disallowed_tools - ) + return all(spelling not in server.disallowed_tools for spelling in spellings) return True def validate_allowed_params(self, tool_name: str, arguments: dict[str, Any], server: MCPServer) -> None: @@ -4282,7 +4288,8 @@ class MCPServerManager: Filter arguments to only include allowed parameters for the given tool. Args: - tool_name: Name of the tool (with or without prefix) + tool_name: Bare tool name, already resolved against the server's + registered prefixes by the caller arguments: Dictionary of arguments to filter server: MCPServer configuration @@ -4292,19 +4299,14 @@ class MCPServerManager: Raises: HTTPException: If allowed_params is configured for this tool but arguments contain disallowed params """ - from litellm.proxy._experimental.mcp_server.utils import ( - split_server_prefix_from_name, - ) - # If no allowed_params configured, return all arguments if not server.allowed_params: return - # Get the unprefixed tool name to match against config - unprefixed_tool_name, _ = split_server_prefix_from_name(tool_name) - - # Check both prefixed and unprefixed tool names - allowed_params_list = server.allowed_params.get(tool_name) or server.allowed_params.get(unprefixed_tool_name) + spellings = iter_known_tool_name_spellings(tool_name, server) + allowed_params_list = next( + (server.allowed_params[name] for name in spellings if name in server.allowed_params), None + ) # If this tool doesn't have allowed_params specified, allow all params if allowed_params_list is None: @@ -4390,8 +4392,11 @@ class MCPServerManager: global_mcp_tool_registry, ) - # Get the tool from the registry - tool = global_mcp_tool_registry.get_tool(f"{server.name}-{tool_name}") + # Registration used add_server_prefix_to_name(base, get_server_prefix(server)), + # and tool_name is the bare base name by the time call_tool reaches here, so + # rebuilding the key the same way reproduces it exactly + registry_key = add_server_prefix_to_name(tool_name, get_server_prefix(server)) + tool = global_mcp_tool_registry.get_tool(registry_key) if tool is None: # Tool not found in registry error_msg = f"OpenAPI tool {tool_name} not found in registry" @@ -5251,7 +5256,7 @@ class MCPServerManager: for tool in tools: # The tool.name here is already prefixed from _get_tools_from_server # Extract original name for mapping - original_name, _ = split_server_prefix_from_name(tool.name) + original_name = strip_known_server_prefix(tool.name, server) self.tool_name_to_mcp_server_name_mapping[original_name] = server.name self.tool_name_to_mcp_server_name_mapping[tool.name] = server.name @@ -5288,13 +5293,10 @@ class MCPServerManager: # If not found and tool name is prefixed, extract the prefix and # match against any known form. - if is_tool_name_prefixed(tool_name, known_server_prefixes=set(prefix_to_server.keys())): - ( - original_tool_name, - server_name_from_prefix, - ) = split_server_prefix_from_name(tool_name) - normalised_prefix = normalize_server_name(server_name_from_prefix) - matched_server = prefix_to_server.get(normalised_prefix) + matched = match_known_server_prefix(tool_name, prefix_to_server.keys()) + if matched is not None: + matched_prefix, original_tool_name = matched + matched_server = prefix_to_server.get(matched_prefix) if matched_server is not None and ( original_tool_name in self.tool_name_to_mcp_server_name_mapping or tool_name in self.tool_name_to_mcp_server_name_mapping diff --git a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py index 9b51513f4ac..1736e34d70c 100644 --- a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py +++ b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py @@ -542,7 +542,7 @@ if MCP_AVAILABLE: user_api_key_auth=user_api_key_auth, ) if allowed_tools_for_server is not None: - tools = [tool for tool in tools if _tool_name_matches(tool.name, allowed_tools_for_server)] + tools = [tool for tool in tools if _tool_name_matches(tool.name, allowed_tools_for_server, server)] return _create_tool_response_objects(tools, server) diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index ec07d33f24d..5181f3a2676 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -65,6 +65,7 @@ from litellm.proxy._experimental.mcp_server.utils import ( extract_mcp_tool_result_error_message, get_server_prefix, iter_known_server_prefixes, + iter_known_tool_name_spellings, ) from litellm.proxy._types import ( ProxyException, @@ -1416,34 +1417,32 @@ if MCP_AVAILABLE: return allowed_mcp_servers - def _tool_name_matches(tool_name: str, filter_list: list[str]) -> bool: + def _tool_name_matches(tool_name: str, filter_list: list[str], mcp_server: MCPServer) -> bool: """ Check if a tool name matches any name in the filter list. - Checks both the full tool name and unprefixed version (without server prefix). - This allows users to configure simple tool names regardless of prefixing. - Comparison is case-insensitive to handle OpenAPI operationIds that may be in camelCase. + Matches via the same ``iter_known_tool_name_spellings`` the server-level + permission checks use, so discovery hides exactly what dispatch refuses; + covering fewer spellings here leaves a blocked tool advertised in + ``tools/list``. Comparison is case-insensitive to handle OpenAPI + operationIds that may be in camelCase. Args: tool_name: The tool name to check (may be prefixed like "server-tool_name") filter_list: List of tool names to match against + mcp_server: The server the tool belongs to, whose registered prefixes + locate the boundary exactly. Required: guessing the boundary at + the first separator silently mismatches every tool on a server + whose prefix contains the separator. Returns: - True if the tool name (prefixed or unprefixed) is in the filter list + True if any spelling of the tool name is in the filter list """ - from litellm.proxy._experimental.mcp_server.utils import ( - split_server_prefix_from_name, - ) + filter_list_lower = {f.lower() for f in filter_list} + bare_name = strip_known_server_prefix(tool_name, mcp_server) + spellings = (tool_name, *iter_known_tool_name_spellings(bare_name, mcp_server)) - # Normalize filter list to lowercase for case-insensitive comparison - filter_list_lower = [f.lower() for f in filter_list] - - if tool_name.lower() in filter_list_lower: - return True - - # Check if the unprefixed name is in the list (case-insensitive) - unprefixed_name, _ = split_server_prefix_from_name(tool_name) - return unprefixed_name.lower() in filter_list_lower + return any(spelling.lower() in filter_list_lower for spelling in spellings) def filter_tools_by_allowed_tools( tools: list[MCPTool], @@ -1473,12 +1472,16 @@ if MCP_AVAILABLE: if server_applies_tool_allowlist(mcp_server): if not mcp_server.allowed_tools: return [] - tools_to_return = [tool for tool in tools if _tool_name_matches(tool.name, mcp_server.allowed_tools)] + tools_to_return = [ + tool for tool in tools if _tool_name_matches(tool.name, mcp_server.allowed_tools, mcp_server) + ] # Filter by disallowed_tools (blacklist) if mcp_server.disallowed_tools: tools_to_return = [ - tool for tool in tools_to_return if not _tool_name_matches(tool.name, mcp_server.disallowed_tools) + tool + for tool in tools_to_return + if not _tool_name_matches(tool.name, mcp_server.disallowed_tools, mcp_server) ] return tools_to_return @@ -1498,7 +1501,7 @@ if MCP_AVAILABLE: return tools for tool in tools: - unprefixed, _ = split_server_prefix_from_name(tool.name) + unprefixed = strip_known_server_prefix(tool.name, mcp_server) lookup_key = unprefixed or tool.name if lookup_key in display_name_map: tool.name = display_name_map[lookup_key] @@ -2699,6 +2702,7 @@ if MCP_AVAILABLE: break if mcp_server is not None: server_name = mcp_server.name + original_tool_name = strip_known_server_prefix(name, mcp_server) if requested_server is not None: if mcp_server is not None and mcp_server.server_id != requested_server.server_id: @@ -2716,6 +2720,7 @@ if MCP_AVAILABLE: if mcp_server is None: mcp_server = requested_server server_name = requested_server.name + original_tool_name = strip_known_server_prefix(name, requested_server) # Only enforce server-level permissions when we can resolve a server if server_name: @@ -2887,13 +2892,14 @@ if MCP_AVAILABLE: _request_resolved_auth_headers.reset(_resolved_token) response = CallToolResult(content=cast(Any, local_content), isError=False) - # Try managed MCP server tool (pass the full prefixed name) + # Try managed MCP server tool (the name is bare; the prefix boundary was + # already resolved above against this server's registered prefixes) # Primary and recommended way to use external MCP servers ######################################################### elif mcp_server: response = await _handle_managed_mcp_tool( server_name=server_name, - name=original_tool_name, # Pass the full name (potentially prefixed) + name=original_tool_name, arguments=arguments, user_api_key_auth=user_api_key_auth, mcp_auth_header=mcp_auth_header, diff --git a/litellm/proxy/_experimental/mcp_server/utils.py b/litellm/proxy/_experimental/mcp_server/utils.py index afd396adc4c..e8f8c08188e 100644 --- a/litellm/proxy/_experimental/mcp_server/utils.py +++ b/litellm/proxy/_experimental/mcp_server/utils.py @@ -326,8 +326,34 @@ def iter_known_server_prefixes(server: Any) -> Iterator[str]: yield from _emit(server_id) +def iter_known_tool_name_spellings(tool_name: str, server: Any) -> Iterator[str]: + """Yield every name that denotes the bare ``tool_name`` on ``server``. + + The bare name, then its wire spelling under each prefix from + ``iter_known_server_prefixes``. Routing resolves an inbound name against that + whole set, so anything keyed by tool name (the routing map, the allow/deny + lists, ``allowed_params``) must cover it too or it answers for fewer names + than are reachable, which fails open on ``disallowed_tools``. + ``get_server_prefix`` alone covers only the published spelling, and that moves + with the alias and with ``LITELLM_USE_SHORT_MCP_TOOL_PREFIX``. These are + spellings of one tool on one server, so honoring all of them normalizes the + entry rather than widening a grant. + """ + yield tool_name + for prefix in iter_known_server_prefixes(server): + yield add_server_prefix_to_name(tool_name, prefix) + + def split_server_prefix_from_name(prefixed_name: str) -> Tuple[str, str]: - """Return the unprefixed name plus the server name used as prefix.""" + """Return the unprefixed name plus the server name used as prefix. + + Cuts at the FIRST separator, so the two halves are only trustworthy as a + pair: they reassemble into ``prefixed_name`` exactly, which is what makes + this safe for routing. Reading one half on its own is a guess about where the + boundary fell, and that guess is wrong whenever the prefix itself contains + the separator. Callers that compare a half against configuration must use + :func:`match_known_server_prefix` or :func:`strip_known_server_prefix`. + """ if MCP_TOOL_PREFIX_SEPARATOR in prefixed_name: parts = prefixed_name.split(MCP_TOOL_PREFIX_SEPARATOR, 1) if len(parts) == 2: @@ -335,6 +361,27 @@ def split_server_prefix_from_name(prefixed_name: str) -> Tuple[str, str]: return prefixed_name, "" +def match_known_server_prefix(name: str, known_prefixes: Iterable[str]) -> tuple[str, str] | None: + """Return ``(matched_prefix, bare_name)`` when ``name`` carries a known prefix. + + Candidates are normalized and tried LONGEST first, so a prefix that itself + contains :data:`MCP_TOOL_PREFIX_SEPARATOR` (the UUID ``server_id`` used when + a server has no alias, or a legacy hyphenated alias) wins over a shorter + prefix that is merely its leading segment. Returns ``None`` when no candidate + matches, i.e. ``name`` carries none of these prefixes. + """ + candidates = sorted( + {normalize_server_name(prefix) for prefix in known_prefixes if prefix}, + key=len, + reverse=True, + ) + for prefix in candidates: + separator_suffixed = prefix + MCP_TOOL_PREFIX_SEPARATOR + if name.startswith(separator_suffixed): + return prefix, name[len(separator_suffixed) :] + return None + + def strip_known_server_prefix(name: str, server: Optional[Any]) -> str: """Strip ``server``'s registered prefix from a prefixed tool/resource name. @@ -352,11 +399,8 @@ def strip_known_server_prefix(name: str, server: Optional[Any]) -> str: """ if server is None: return split_server_prefix_from_name(name)[0] - for prefix in iter_known_server_prefixes(server): - candidate = normalize_server_name(prefix) + MCP_TOOL_PREFIX_SEPARATOR - if name.startswith(candidate): - return name[len(candidate) :] - return name + matched = match_known_server_prefix(name, iter_known_server_prefixes(server)) + return name if matched is None else matched[1] def is_tool_name_prefixed( @@ -367,15 +411,16 @@ def is_tool_name_prefixed( Check if tool name has a known MCP server prefix. When ``known_server_prefixes`` is provided the function verifies that the - substring before the first separator is an actual registered server - prefix. Without it the check falls back to the legacy heuristic + name actually starts with one of those prefixes followed by the separator, + matching the longest candidate first so a prefix containing the separator + still resolves. Without it the check falls back to the legacy heuristic (separator present anywhere in the name), which can produce false positives for non-MCP tools whose names contain hyphens (e.g. ``text-to-speech``, ``code-review``). Args: tool_name: Tool name to check. - known_server_prefixes: Optional set of normalised server prefixes + known_server_prefixes: Optional set of normalized server prefixes currently registered in the MCP manager. Pass this whenever the caller has access to the server registry so that the check is accurate. @@ -387,8 +432,7 @@ def is_tool_name_prefixed( return False if known_server_prefixes is not None: - candidate_prefix = tool_name.split(MCP_TOOL_PREFIX_SEPARATOR, 1)[0] - return normalize_server_name(candidate_prefix) in known_server_prefixes + return match_known_server_prefix(tool_name, known_server_prefixes) is not None # Legacy fallback – separator present somewhere in the name. return True diff --git a/tests/mcp_tests/test_mcp_server.py b/tests/mcp_tests/test_mcp_server.py index f1e6539439a..390f6917573 100644 --- a/tests/mcp_tests/test_mcp_server.py +++ b/tests/mcp_tests/test_mcp_server.py @@ -2010,6 +2010,9 @@ async def test_get_tools_for_single_server_applies_disallowed_tools_without_allo mock_server.mcp_info = {"server_name": "zapier"} mock_server.name = "zapier" mock_server.server_id = "zapier" + mock_server.server_name = "zapier" + mock_server.alias = None + mock_server.short_prefix = None mock_server.allowed_tools = None mock_server.disallowed_tools = ["send_email"] diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py index c0affdf46b3..a3577c85621 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py @@ -1029,6 +1029,9 @@ async def test_get_tools_from_mcp_servers_continues_when_one_server_fails(): working_server.server_name = "working_server" working_server.auth_type = None working_server.extra_headers = None + working_server.short_prefix = None + working_server.tool_name_to_display_name = None + working_server.tool_name_to_description = None failing_server = MagicMock() failing_server.name = "failing_server" @@ -1039,6 +1042,9 @@ async def test_get_tools_from_mcp_servers_continues_when_one_server_fails(): failing_server.server_name = "failing_server" failing_server.auth_type = None failing_server.extra_headers = None + failing_server.short_prefix = None + failing_server.tool_name_to_display_name = None + failing_server.tool_name_to_description = None # Mock global_mcp_server_manager mock_manager = MagicMock() @@ -4507,28 +4513,36 @@ def test_tool_name_matches_case_insensitive(): except ImportError: pytest.skip("MCP server not available") + server = MCPServer( + server_id="srv-per-store", + name="per_store", + server_name="per_store", + url="http://127.0.0.1:5115/mcp", + transport=MCPTransport.http, + ) + # Test case 1: Unprefixed tool name with camelCase in filter list - assert _tool_name_matches("addpet", ["addPet", "updatePet"]) is True - assert _tool_name_matches("updatepet", ["addPet", "updatePet"]) is True - assert _tool_name_matches("deletepet", ["addPet", "updatePet"]) is False + assert _tool_name_matches("addpet", ["addPet", "updatePet"], server) is True + assert _tool_name_matches("updatepet", ["addPet", "updatePet"], server) is True + assert _tool_name_matches("deletepet", ["addPet", "updatePet"], server) is False # Test case 2: Prefixed tool name with camelCase in filter list - assert _tool_name_matches("per_store-addpet", ["addPet", "updatePet"]) is True - assert _tool_name_matches("per_store-updatepet", ["addPet", "updatePet"]) is True - assert _tool_name_matches("per_store-deletepet", ["addPet", "updatePet"]) is False + assert _tool_name_matches("per_store-addpet", ["addPet", "updatePet"], server) is True + assert _tool_name_matches("per_store-updatepet", ["addPet", "updatePet"], server) is True + assert _tool_name_matches("per_store-deletepet", ["addPet", "updatePet"], server) is False # Test case 3: Mixed case variations - assert _tool_name_matches("findPetsByStatus", ["findpetsbystatus"]) is True - assert _tool_name_matches("findpetsbystatus", ["findPetsByStatus"]) is True - assert _tool_name_matches("FINDPETSBYSTATUS", ["findPetsByStatus"]) is True + assert _tool_name_matches("findPetsByStatus", ["findpetsbystatus"], server) is True + assert _tool_name_matches("findpetsbystatus", ["findPetsByStatus"], server) is True + assert _tool_name_matches("FINDPETSBYSTATUS", ["findPetsByStatus"], server) is True # Test case 4: Full prefixed name in filter list (case-insensitive) - assert _tool_name_matches("server-addPet", ["server-addpet"]) is True - assert _tool_name_matches("server-addpet", ["server-addPet"]) is True + assert _tool_name_matches("server-addPet", ["server-addpet"], server) is True + assert _tool_name_matches("server-addpet", ["server-addPet"], server) is True # Test case 5: Ensure non-matching names still don't match - assert _tool_name_matches("addpet", ["deletePet", "updatePet"]) is False - assert _tool_name_matches("server-addpet", ["deletePet", "updatePet"]) is False + assert _tool_name_matches("addpet", ["deletePet", "updatePet"], server) is False + assert _tool_name_matches("server-addpet", ["deletePet", "updatePet"], server) is False def test_filter_tools_by_allowed_tools_case_insensitive(): @@ -4581,6 +4595,7 @@ def test_filter_tools_by_allowed_tools_case_insensitive(): server = MCPServer( server_id="test-server", name="per_store", + server_name="per_store", transport=MCPTransport.http, allowed_tools=["addPet", "updatePet", "findPetsByStatus"], ) @@ -6121,6 +6136,70 @@ async def test_execute_mcp_tool_rest_server_id_authoritative_for_unprefixed_tool assert captured["name"] == "echo" +@pytest.mark.asyncio +async def test_execute_mcp_tool_strips_a_prefix_that_contains_the_separator(): + """A server with no alias publishes its UUID server_id as the tool prefix. + + Splitting that wire name at the first separator leaves a truncated UUID tail + glued to the tool name, which then travels to the upstream server as the tool + to call, into the spend log, and into the server-level allowed_tools check. + """ + from mcp.types import TextContent + + from litellm.proxy._experimental.mcp_server import server as mcp_module + + server_id = "117c814c-1a2b-4c4d-8e8f-0a1b2c3d4e5f" + alias_less_server = MCPServer( + server_id=server_id, + name=server_id, + url="http://127.0.0.1:5115/mcp", + transport=MCPTransport.http, + auth_type=MCPAuth.api_key, + authentication_token="abc123", + ) + + captured: dict = {} + + async def fake_handle_managed_mcp_tool(**kwargs): + captured.update(kwargs) + return mcp_module.CallToolResult( + content=[TextContent(type="text", text="ok")], + isError=False, + ) + + with ( + patch.object( + mcp_module.global_mcp_server_manager, + "_get_mcp_server_from_tool_name", + return_value=alias_less_server, + ), + patch.object( + mcp_module, + "_handle_managed_mcp_tool", + new=fake_handle_managed_mcp_tool, + ), + patch.object( + mcp_module.MCPRequestHandler, + "is_tool_allowed", + return_value=True, + ), + patch.object( + mcp_module.global_mcp_tool_registry, + "get_tool", + return_value=None, + ), + ): + await mcp_module.execute_mcp_tool( + name=f"{server_id}-read_wiki_contents", + arguments={"repoName": "acme/wiki"}, + allowed_mcp_servers=[alias_less_server], + start_time=datetime.now(), + ) + + assert captured["server_name"] == server_id + assert captured["name"] == "read_wiki_contents" + + @pytest.mark.asyncio async def test_execute_mcp_tool_rest_server_id_injects_requested_server_credentials(): """REST server_id must inject the requested server's auth, not a URL-collision peer's.""" @@ -6426,6 +6505,8 @@ async def test_execute_mcp_tool_sets_model_in_model_call_details(): fake_server.mcp_info = None fake_server.server_id = "srv-1" fake_server.server_name = "openapi-petstore" + fake_server.alias = None + fake_server.short_prefix = None fake_tool = MagicMock() fake_tool.name = "list_pets" @@ -6481,7 +6562,13 @@ async def test_execute_mcp_tool_sets_model_in_model_call_details(): @pytest.mark.asyncio async def test_execute_mcp_tool_rest_unresolved_prefixed_name_routes_to_requested_server(): - """A prefixed REST name that resolves to no tool must still dispatch to the server_id.""" + """A prefixed REST name that resolves to no tool must still dispatch to the server_id. + + The prefix here belongs to a different server, so it is not a prefix boundary on the + routed server and the name travels upstream whole. Stripping it would invoke the routed + server's similarly named tool instead, which is what the tool_server_mismatch 403 exists + to prevent when the prefix does resolve. + """ from mcp.types import TextContent from litellm.proxy._experimental.mcp_server import server as mcp_module @@ -6553,7 +6640,7 @@ async def test_execute_mcp_tool_rest_unresolved_prefixed_name_routes_to_requeste ) assert captured["server_name"] == "rest_target" - assert captured["name"] == "list_things" + assert captured["name"] == "known_prefix-list_things" routed_server = { requested_server.name: requested_server, @@ -7587,22 +7674,28 @@ async def test_aggregate_listing_reports_per_server_outcomes(): working_server = MagicMock() working_server.name = "working_server" working_server.alias = "working" + working_server.short_prefix = None working_server.allowed_tools = None working_server.disallowed_tools = None working_server.server_id = "working_server" working_server.server_name = "working_server" working_server.auth_type = None working_server.extra_headers = None + working_server.tool_name_to_display_name = None + working_server.tool_name_to_description = None broken_server = MagicMock() broken_server.name = "broken_server" broken_server.alias = "broken" + broken_server.short_prefix = None broken_server.allowed_tools = None broken_server.disallowed_tools = None broken_server.server_id = "broken_server" broken_server.server_name = "broken_server" broken_server.auth_type = None broken_server.extra_headers = None + broken_server.tool_name_to_display_name = None + broken_server.tool_name_to_description = None mock_manager = MagicMock() mock_manager.get_allowed_mcp_servers = AsyncMock(return_value=["working_server", "broken_server"]) @@ -7924,3 +8017,139 @@ async def test_post_mcp_call_guardrails_propagate_a_block(): user_api_key_auth=None, request_data={}, ) + + +class TestListFiltersHonorThePrefixBoundary: + """The listing filters compare a published (prefixed) tool name against + configured entries, so they have to locate the boundary with the server's + registered prefixes. An alias-less server publishes its UUID server_id as + the prefix, and that prefix contains the separator, so cutting at the first + separator drops every tool on the server from the listing. + """ + + SERVER_ID = "117c814c-1a2b-4c4d-8e8f-0a1b2c3d4e5f" + + @staticmethod + def _alias_less_server(**overrides): + from litellm.proxy._experimental.mcp_server.mcp_server_manager import MCPServer + + return MCPServer( + server_id=TestListFiltersHonorThePrefixBoundary.SERVER_ID, + name=TestListFiltersHonorThePrefixBoundary.SERVER_ID, + url="http://127.0.0.1:5115/mcp", + transport=MCPTransport.http, + **overrides, + ) + + def _published_tools(self, *bare_names: str): + from mcp.types import Tool as MCPTool + + return [ + MCPTool(name=f"{self.SERVER_ID}-{bare}", description=bare, inputSchema={"type": "object"}) + for bare in bare_names + ] + + def test_bare_allowlist_entry_keeps_the_published_tool(self): + from litellm.proxy._experimental.mcp_server.server import ( + filter_tools_by_allowed_tools, + ) + + server = self._alias_less_server(allowed_tools=["read_wiki_contents"]) + tools = self._published_tools("read_wiki_contents", "read_wiki_structure") + + kept = filter_tools_by_allowed_tools(tools, server) + + assert [tool.name for tool in kept] == [f"{self.SERVER_ID}-read_wiki_contents"] + + def test_bare_blocklist_entry_excludes_the_published_tool(self): + from litellm.proxy._experimental.mcp_server.server import ( + filter_tools_by_allowed_tools, + ) + + server = self._alias_less_server(disallowed_tools=["read_wiki_structure"]) + tools = self._published_tools("read_wiki_contents", "read_wiki_structure") + + kept = filter_tools_by_allowed_tools(tools, server) + + assert [tool.name for tool in kept] == [f"{self.SERVER_ID}-read_wiki_contents"] + + def test_unrelated_entry_does_not_match(self): + from litellm.proxy._experimental.mcp_server.server import _tool_name_matches + + server = self._alias_less_server() + + assert not _tool_name_matches(f"{self.SERVER_ID}-read_wiki_contents", ["read_wiki_structure"], server) + + def test_match_is_still_case_insensitive(self): + from litellm.proxy._experimental.mcp_server.server import _tool_name_matches + + server = self._alias_less_server() + + assert _tool_name_matches(f"{self.SERVER_ID}-findPetsByStatus", ["findpetsbystatus"], server) + + def test_alias_form_entry_matches_a_tool_published_under_the_short_prefix(self, monkeypatch): + # Routing accepts the alias form, so an entry stored before short + # prefixes were turned on still governs the tool. Matching only the + # published spelling left it advertised while dispatch refused it. + from litellm.proxy._experimental.mcp_server.mcp_server_manager import MCPServer + from litellm.proxy._experimental.mcp_server.server import _tool_name_matches + + monkeypatch.setenv("LITELLM_USE_SHORT_MCP_TOOL_PREFIX", "true") + server = MCPServer( + server_id=self.SERVER_ID, + name="deepwiki_cfg", + alias="deepwiki_cfg", + short_prefix="eiG", + url="http://127.0.0.1:5115/mcp", + transport=MCPTransport.http, + ) + + assert _tool_name_matches("eiG-read_wiki_contents", ["deepwiki_cfg-read_wiki_contents"], server) + + def test_discovery_hides_exactly_what_dispatch_refuses(self, monkeypatch): + """Both halves of one decision, driven through both production paths. + + A spelling the blocklist enforces but the filter misses leaves a blocked + tool advertised; the reverse hides a tool that would have been callable. + """ + from mcp.types import Tool as MCPTool + + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + MCPServer, + MCPServerManager, + ) + from litellm.proxy._experimental.mcp_server.server import ( + filter_tools_by_allowed_tools, + ) + + monkeypatch.setenv("LITELLM_USE_SHORT_MCP_TOOL_PREFIX", "true") + + def _server(**overrides): + return MCPServer( + server_id=self.SERVER_ID, + name="deepwiki_prod", + alias="deepwiki", + server_name="deepwiki_prod", + short_prefix="eiG", + url="http://127.0.0.1:5115/mcp", + transport=MCPTransport.http, + **overrides, + ) + + manager = MCPServerManager() + manager._create_prefixed_tools( + [MCPTool(name="read_wiki_contents", description="", inputSchema={"type": "object"})], + _server(), + ) + registered = sorted(manager.tool_name_to_mcp_server_name_mapping) + assert len(registered) > 1 + + published = MCPTool(name="eiG-read_wiki_contents", description="", inputSchema={"type": "object"}) + for spelling in registered: + server = _server(disallowed_tools=[spelling]) + + refused = not manager.check_allowed_or_banned_tools("read_wiki_contents", server) + hidden = filter_tools_by_allowed_tools([published], server) == [] + + assert refused, spelling + assert hidden, spelling diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py index 8a8dea0ba28..2977f13caf1 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py @@ -9189,3 +9189,471 @@ class TestDiscoveryFailureLogging: assert "typo_row" in caplog.text assert "authorization_url, token_url" in caplog.text assert "unresolved" in caplog.text + + +def _unrestricted_auth() -> MagicMock: + """A caller with no object_permission, so only server-level checks apply.""" + user_api_key_auth = MagicMock() + user_api_key_auth.object_permission = None + user_api_key_auth.object_permission_id = None + return user_api_key_auth + + +def _permissive_proxy_logging() -> MagicMock: + proxy_logging_obj = MagicMock() + proxy_logging_obj._create_mcp_request_object_from_kwargs = MagicMock(return_value={}) + proxy_logging_obj._convert_mcp_to_llm_format = MagicMock(return_value={}) + proxy_logging_obj.pre_call_hook = AsyncMock(return_value={}) + return proxy_logging_obj + + +ALIAS_LESS_SERVER_ID = "117c814c-1a2b-4c4d-8e8f-0a1b2c3d4e5f" + + +class TestServerToolListsHonorThePrefixBoundary: + """The server-level allowed_tools / disallowed_tools / allowed_params checks + receive a BARE tool name. Every caller resolves the prefix boundary before + dispatch (``server.py``'s ``original_tool_name``, the Responses handler's + ``sanitized_tool_name``), and ``call_tool`` hands that same value to the + upstream client verbatim, which only works because it carries no prefix. + + Stored entries may also carry a prefix, and routing accepts *every* prefix + from ``iter_known_server_prefixes`` (short ID, alias, server_name, + server_id), so enforcement derives that whole set via + ``iter_known_tool_name_spellings``. Rebuilding a single comparand as + ``f"{server.name}-{tool_name}"`` used a field the prefix chain never reads + (``get_server_prefix`` is short_prefix, then alias, then server_name, then + server_id) and hardcoded the separator; deriving only ``get_server_prefix`` + covers just the currently published spelling. Either way the check answers + for fewer spellings than are reachable, and on the blocklist arm that is a + fail-open. + """ + + async def _run_check(self, server: MCPServer, name: str, arguments: dict[str, Any] | None = None) -> None: + await MCPServerManager().pre_call_tool_check( + name=name, + arguments=arguments if arguments is not None else {}, + server_name=server.name, + user_api_key_auth=_unrestricted_auth(), + proxy_logging_obj=_permissive_proxy_logging(), + server=server, + ) + + @staticmethod + def _aliased_server(**overrides: Any) -> MCPServer: + return MCPServer( + server_id="dd7f2b9e-2c4a-4f1b-9e0a-8d3c6b5a4f21", + name="petstore_prod", + alias="petstore", + server_name="petstore_prod", + url="https://petstore.example.com/mcp", + transport=MCPTransport.http, + **overrides, + ) + + @staticmethod + def _alias_less_server(**overrides: Any) -> MCPServer: + # No alias and no server_name, so the published prefix is the UUID + # server_id, which itself contains the prefix separator. + return MCPServer( + server_id=ALIAS_LESS_SERVER_ID, + name=ALIAS_LESS_SERVER_ID, + url="https://wiki.example.com/mcp", + transport=MCPTransport.http, + **overrides, + ) + + @pytest.mark.asyncio + async def test_allowlist_entry_prefixed_with_the_alias_matches_a_bare_call(self): + # The dashboard shows tools under the published prefix, so admins store + # "petstore-getpetbyid"; the display name "petstore_prod" is not it. + server = self._aliased_server(allowed_tools=["petstore-getpetbyid"]) + + await self._run_check(server, "getpetbyid") + + @pytest.mark.asyncio + async def test_bare_allowlist_entry_matches_on_an_alias_less_server(self): + server = self._alias_less_server(allowed_tools=["read_wiki_contents"]) + + await self._run_check(server, "read_wiki_contents") + + @pytest.mark.asyncio + async def test_wire_form_allowlist_entry_matches_on_an_alias_less_server(self): + # The published prefix is the UUID server_id, so it contains the + # separator; the derived wire form has to reproduce it whole. + server = self._alias_less_server(allowed_tools=[f"{ALIAS_LESS_SERVER_ID}-read_wiki_contents"]) + + await self._run_check(server, "read_wiki_contents") + + @pytest.mark.asyncio + async def test_wire_form_entry_matches_a_native_name_that_opens_with_the_prefix(self): + # "petstore-getpetbyid" is a real upstream tool name here, so its wire + # form is "petstore-petstore-getpetbyid". Stripping the stored entry + # instead of deriving the wire form cut a boundary the caller had already + # consumed, leaving asymmetric operands that denied a permitted call. + server = self._aliased_server(allowed_tools=["petstore-petstore-getpetbyid"]) + + await self._run_check(server, "petstore-getpetbyid") + + @pytest.mark.asyncio + async def test_wire_form_blocklist_entry_blocks_a_native_name_that_opens_with_the_prefix(self): + server = self._aliased_server(disallowed_tools=["petstore-petstore-getpetbyid"]) + + with pytest.raises(HTTPException) as exc_info: + await self._run_check(server, "petstore-getpetbyid") + + assert exc_info.value.status_code == 403 + + @pytest.mark.asyncio + async def test_wire_form_blocklist_entry_blocks_under_the_short_prefix_mode(self, monkeypatch): + # short_prefix wins in get_server_prefix but is never server.name, so the + # hand-built comparand could not match a stored wire-form entry and the + # blocklisted tool stayed callable. + monkeypatch.setenv("LITELLM_USE_SHORT_MCP_TOOL_PREFIX", "true") + server = self._aliased_server(short_prefix="F3X", disallowed_tools=["F3X-deletepet"]) + + with pytest.raises(HTTPException) as exc_info: + await self._run_check(server, "deletepet") + + assert exc_info.value.status_code == 403 + + @pytest.mark.asyncio + async def test_alias_form_blocklist_entry_still_blocks_under_the_short_prefix_mode(self, monkeypatch): + # Turning short prefixes on republishes every tool under the short ID, + # but routing still resolves the alias form, so an entry an admin stored + # before the flip stays reachable and has to stay enforced. Deriving only + # the published spelling silently stops honoring it: a fail-open on a + # config nobody edited. + monkeypatch.setenv("LITELLM_USE_SHORT_MCP_TOOL_PREFIX", "true") + server = self._aliased_server(short_prefix="F3X", disallowed_tools=["petstore-deletepet"]) + + with pytest.raises(HTTPException) as exc_info: + await self._run_check(server, "deletepet") + + assert exc_info.value.status_code == 403 + + @pytest.mark.asyncio + async def test_alias_form_allowlist_entry_still_matches_under_the_short_prefix_mode(self, monkeypatch): + monkeypatch.setenv("LITELLM_USE_SHORT_MCP_TOOL_PREFIX", "true") + server = self._aliased_server(short_prefix="F3X", allowed_tools=["petstore-getpetbyid"]) + + await self._run_check(server, "getpetbyid") + + @pytest.mark.asyncio + async def test_server_name_form_blocklist_entry_still_blocks_under_the_short_prefix_mode(self, monkeypatch): + # server_name sits third in the prefix chain, so it is published only + # when alias and short_prefix are both absent, yet routing accepts it + # regardless. + monkeypatch.setenv("LITELLM_USE_SHORT_MCP_TOOL_PREFIX", "true") + server = self._aliased_server(short_prefix="F3X", disallowed_tools=["petstore_prod-deletepet"]) + + with pytest.raises(HTTPException) as exc_info: + await self._run_check(server, "deletepet") + + assert exc_info.value.status_code == 403 + + @pytest.mark.asyncio + async def test_raw_server_id_form_blocklist_entry_still_blocks_under_the_short_prefix_mode(self, monkeypatch): + monkeypatch.setenv("LITELLM_USE_SHORT_MCP_TOOL_PREFIX", "true") + server = self._alias_less_server(disallowed_tools=[f"{ALIAS_LESS_SERVER_ID}-read_wiki_contents"]) + + with pytest.raises(HTTPException) as exc_info: + await self._run_check(server, "read_wiki_contents") + + assert exc_info.value.status_code == 403 + + @pytest.mark.asyncio + async def test_allowed_params_keyed_by_the_alias_form_are_enforced_under_the_short_prefix_mode(self, monkeypatch): + monkeypatch.setenv("LITELLM_USE_SHORT_MCP_TOOL_PREFIX", "true") + server = self._aliased_server(short_prefix="F3X", allowed_params={"petstore-getpetbyid": ["petid"]}) + + with pytest.raises(HTTPException) as exc_info: + await self._run_check( + server, + "getpetbyid", + arguments={"petid": "7", "include_internal": "true"}, + ) + + assert exc_info.value.status_code == 403 + assert "include_internal" in exc_info.value.detail["error"] + + @pytest.mark.asyncio + async def test_foreign_prefix_entry_does_not_match_under_the_short_prefix_mode(self, monkeypatch): + # Honoring every known prefix must not become "honor any prefix": the + # widened set is this server's spellings only. + monkeypatch.setenv("LITELLM_USE_SHORT_MCP_TOOL_PREFIX", "true") + server = self._aliased_server(short_prefix="F3X", allowed_tools=["other_server-getpetbyid"]) + + with pytest.raises(HTTPException) as exc_info: + await self._run_check(server, "getpetbyid") + + assert exc_info.value.status_code == 403 + + @pytest.mark.parametrize("short_prefix_mode", [False, True]) + @pytest.mark.asyncio + async def test_every_spelling_routing_registers_is_also_enforced(self, monkeypatch, short_prefix_mode): + """The invariant, driven through production code on both sides. + + ``_create_prefixed_tools`` decides which spellings reach dispatch, so + every key it registers has to be a spelling the blocklist can refuse. + Any key routing accepts but enforcement misses is a callable blocked + tool. + """ + monkeypatch.setenv("LITELLM_USE_SHORT_MCP_TOOL_PREFIX", "true" if short_prefix_mode else "false") + shape = self._aliased_server(short_prefix="F3X") + + manager = MCPServerManager() + manager._create_prefixed_tools([MCPTool(name="deletepet", description="", inputSchema={})], shape) + registered = sorted(manager.tool_name_to_mcp_server_name_mapping) + assert len(registered) > 1 + + for spelling in registered: + server = self._aliased_server(short_prefix="F3X", disallowed_tools=[spelling]) + with pytest.raises(HTTPException) as exc_info: + await self._run_check(server, "deletepet") + assert exc_info.value.status_code == 403, spelling + + @pytest.mark.asyncio + async def test_wire_form_allowlist_entry_follows_a_non_default_separator(self): + from litellm.proxy._experimental.mcp_server import utils as mcp_utils + + server = self._aliased_server(allowed_tools=["petstore__getpetbyid"]) + + with patch.object(mcp_utils, "MCP_TOOL_PREFIX_SEPARATOR", "__"): + await self._run_check(server, "getpetbyid") + + @pytest.mark.asyncio + async def test_tool_outside_the_allowlist_is_still_denied(self): + server = self._aliased_server(allowed_tools=["petstore-getpetbyid"]) + + with pytest.raises(HTTPException) as exc_info: + await self._run_check(server, "deletepet") + + assert exc_info.value.status_code == 403 + + @pytest.mark.asyncio + async def test_allowlist_entry_prefixed_for_another_server_does_not_match(self): + # Reducing both sides must not widen the allowlist across servers: a + # foreign prefix is not one of this server's known prefixes, so the + # entry keeps it and never collapses onto a bare name. + server = self._aliased_server(allowed_tools=["other_server-getpetbyid"]) + + with pytest.raises(HTTPException) as exc_info: + await self._run_check(server, "getpetbyid") + + assert exc_info.value.status_code == 403 + + @pytest.mark.asyncio + async def test_prefixed_disallowed_entry_blocks_a_bare_call(self): + # Fail-open regression: the blocklist arm answered "not banned" whenever + # the stored entry carried a prefix it failed to reconstruct. + server = self._aliased_server(disallowed_tools=["petstore-deletepet"]) + + with pytest.raises(HTTPException) as exc_info: + await self._run_check(server, "deletepet") + + assert exc_info.value.status_code == 403 + + @pytest.mark.asyncio + async def test_tool_outside_the_blocklist_is_still_allowed(self): + server = self._aliased_server(disallowed_tools=["petstore-deletepet"]) + + await self._run_check(server, "getpetbyid") + + @pytest.mark.asyncio + async def test_allowed_params_are_enforced_for_a_bare_key(self): + server = self._alias_less_server(allowed_params={"read_wiki_contents": ["repo"]}) + + with pytest.raises(HTTPException) as exc_info: + await self._run_check( + server, + "read_wiki_contents", + arguments={"repo": "acme/wiki", "internal_only": "true"}, + ) + + assert exc_info.value.status_code == 403 + assert "internal_only" in exc_info.value.detail["error"] + + @pytest.mark.asyncio + async def test_allowed_params_are_enforced_for_a_wire_form_key(self): + # A key stored under the published prefix matched nothing, so the lookup + # returned None and the check silently allowed every parameter instead of + # enforcing the configured list. + server = self._alias_less_server(allowed_params={f"{ALIAS_LESS_SERVER_ID}-read_wiki_contents": ["repo"]}) + + with pytest.raises(HTTPException) as exc_info: + await self._run_check( + server, + "read_wiki_contents", + arguments={"repo": "acme/wiki", "internal_only": "true"}, + ) + + assert exc_info.value.status_code == 403 + assert "internal_only" in exc_info.value.detail["error"] + + @pytest.mark.asyncio + async def test_allowed_params_still_accept_the_configured_parameters(self): + server = self._alias_less_server(allowed_params={f"{ALIAS_LESS_SERVER_ID}-read_wiki_contents": ["repo"]}) + + await self._run_check(server, "read_wiki_contents", arguments={"repo": "acme/wiki"}) + + @pytest.mark.asyncio + async def test_allowed_params_are_enforced_for_a_native_name_that_opens_with_the_prefix(self): + server = self._aliased_server(allowed_params={"petstore-petstore-getpetbyid": ["petid"]}) + + with pytest.raises(HTTPException) as exc_info: + await self._run_check( + server, + "petstore-getpetbyid", + arguments={"petid": "7", "include_internal": "true"}, + ) + + assert exc_info.value.status_code == 403 + assert "include_internal" in exc_info.value.detail["error"] + + @pytest.mark.asyncio + async def test_an_explicitly_empty_allowed_params_list_refuses_every_parameter(self): + server = self._alias_less_server(allowed_params={"read_wiki_contents": []}) + + with pytest.raises(HTTPException) as exc_info: + await self._run_check(server, "read_wiki_contents", arguments={"repo": "acme/wiki"}) + + assert exc_info.value.status_code == 403 + assert "repo" in exc_info.value.detail["error"] + + @pytest.mark.asyncio + async def test_an_explicitly_empty_allowed_params_list_still_permits_an_argument_free_call(self): + server = self._alias_less_server(allowed_params={"read_wiki_contents": []}) + + await self._run_check(server, "read_wiki_contents", arguments={}) + + +class TestOpenAPIRegistryKeyMatchesRegistration: + """OpenAPI tools are registered under ``add_server_prefix_to_name(base, get_server_prefix(server))``, + so the dispatch lookup has to build its key the same way from the bare name ``call_tool`` + hands it. Rebuilding it as ``f"{server.name}-{bare_name}"`` used a field the prefix chain + never reads and hardcoded the separator, so every call on a server whose published prefix + differs from its display name failed with "not found in registry" instead of dispatching. + """ + + @staticmethod + def _register(server: MCPServer, base_tool_name: str) -> str: + from litellm.proxy._experimental.mcp_server.utils import ( + add_server_prefix_to_name, + get_server_prefix, + ) + + return add_server_prefix_to_name(base_tool_name, get_server_prefix(server)) + + async def _call(self, server: MCPServer, registered_key: str, bare_tool_name: str) -> CallToolResult: + from litellm.proxy._experimental.mcp_server.tool_registry import ( + global_mcp_tool_registry, + ) + + async def handler(**kwargs: Any) -> str: + return "dispatched" + + tool = MagicMock() + tool.handler = handler + + with patch.dict(global_mcp_tool_registry.tools, {registered_key: tool}, clear=True): + return await MCPServerManager()._call_openapi_tool_handler(server, bare_tool_name, {}) + + @pytest.mark.asyncio + async def test_aliased_server_dispatches_when_name_differs_from_published_prefix(self): + server = MCPServer( + server_id="dd7f2b9e-2c4a-4f1b-9e0a-8d3c6b5a4f21", + name="petstore_prod", + alias="petstore", + server_name="petstore_prod", + url=None, + transport=MCPTransport.http, + spec_path="https://example.com/petstore.yaml", + ) + registered_key = self._register(server, "list_pets") + assert registered_key == "petstore-list_pets" + + result = await self._call(server, registered_key, "list_pets") + + assert result.isError is False + assert result.content[0].text == "dispatched" + + @pytest.mark.asyncio + async def test_alias_less_server_dispatches_when_the_prefix_contains_the_separator(self): + server = MCPServer( + server_id=ALIAS_LESS_SERVER_ID, + name=ALIAS_LESS_SERVER_ID, + url=None, + transport=MCPTransport.http, + spec_path="https://example.com/wiki.yaml", + ) + registered_key = self._register(server, "read_wiki_contents") + assert registered_key == f"{ALIAS_LESS_SERVER_ID}-read_wiki_contents" + + result = await self._call(server, registered_key, "read_wiki_contents") + + assert result.isError is False + assert result.content[0].text == "dispatched" + + @pytest.mark.asyncio + async def test_dispatch_keeps_a_native_name_that_opens_with_the_prefix(self): + # Registration prefixes the upstream name whatever it looks like, so + # "petstore-list_pets" is registered as "petstore-petstore-list_pets". + # Stripping the bare name again before rebuilding the key cut that + # leading segment back off and the lookup missed. + server = MCPServer( + server_id="dd7f2b9e-2c4a-4f1b-9e0a-8d3c6b5a4f21", + name="petstore_prod", + alias="petstore", + server_name="petstore_prod", + url=None, + transport=MCPTransport.http, + spec_path="https://example.com/petstore.yaml", + ) + registered_key = self._register(server, "petstore-list_pets") + assert registered_key == "petstore-petstore-list_pets" + + result = await self._call(server, registered_key, "petstore-list_pets") + + assert result.isError is False + assert result.content[0].text == "dispatched" + + @pytest.mark.asyncio + async def test_dispatch_follows_a_non_default_prefix_separator(self): + from litellm.proxy._experimental.mcp_server import utils as mcp_utils + + server = MCPServer( + server_id="dd7f2b9e-2c4a-4f1b-9e0a-8d3c6b5a4f21", + name="petstore_prod", + alias="petstore", + server_name="petstore_prod", + url=None, + transport=MCPTransport.http, + spec_path="https://example.com/petstore.yaml", + ) + + with patch.object(mcp_utils, "MCP_TOOL_PREFIX_SEPARATOR", "__"): + registered_key = self._register(server, "list_pets") + assert registered_key == "petstore__list_pets" + + result = await self._call(server, registered_key, "list_pets") + + assert result.isError is False + assert result.content[0].text == "dispatched" + + @pytest.mark.asyncio + async def test_unregistered_tool_is_still_reported_missing(self): + server = MCPServer( + server_id="dd7f2b9e-2c4a-4f1b-9e0a-8d3c6b5a4f21", + name="petstore_prod", + alias="petstore", + server_name="petstore_prod", + url=None, + transport=MCPTransport.http, + spec_path="https://example.com/petstore.yaml", + ) + + result = await self._call(server, "petstore-list_pets", "delete_pet") + + assert result.isError is True + assert "not found in registry" in result.content[0].text diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_openapi_tool_auth.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_openapi_tool_auth.py index 1e4349c3143..c4b3c7f5f67 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_openapi_tool_auth.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_openapi_tool_auth.py @@ -32,6 +32,8 @@ async def test_openapi_local_tool_runs_pre_call_tool_check(): fake_server.mcp_info = None fake_server.server_id = "srv-1" fake_server.server_name = "openapi-petstore" + fake_server.alias = None + fake_server.short_prefix = None fake_tool = MagicMock() fake_tool.name = "list_pets" @@ -111,6 +113,8 @@ async def test_openapi_local_tool_blocked_when_pre_call_check_raises(): fake_server.mcp_info = None fake_server.server_id = "srv-1" fake_server.server_name = "openapi-petstore" + fake_server.alias = None + fake_server.short_prefix = None fake_tool = MagicMock() fake_tool.name = "delete_pet" diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_short_mcp_tool_prefix.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_short_mcp_tool_prefix.py index 662ef585c6b..6e3ac014840 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_short_mcp_tool_prefix.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_short_mcp_tool_prefix.py @@ -20,7 +20,9 @@ from litellm.proxy._experimental.mcp_server.utils import ( compute_short_server_prefix, get_server_prefix, is_short_mcp_tool_prefix_enabled, + is_tool_name_prefixed, iter_known_server_prefixes, + match_known_server_prefix, strip_known_server_prefix, ) from litellm.types.mcp_server.mcp_server_manager import MCPServer @@ -195,6 +197,70 @@ class TestStripKnownServerPrefix: assert strip_known_server_prefix("svc-tool", None) == "tool" +# --------------------------------------------------------------------------- +# match_known_server_prefix — the shared boundary primitive +# --------------------------------------------------------------------------- + + +class TestMatchKnownServerPrefix: + """Locates the boundary by matching registered prefixes instead of cutting + at the first separator, preferring the longest candidate so a prefix that + itself contains the separator still wins.""" + + def test_returns_matched_prefix_and_bare_name(self): + assert match_known_server_prefix("deepwiki-contents", ["deepwiki"]) == ( + "deepwiki", + "contents", + ) + + def test_returns_none_when_no_candidate_matches(self): + assert match_known_server_prefix("contents", ["deepwiki"]) is None + + def test_separator_must_follow_the_prefix(self): + assert match_known_server_prefix("deepwikicontents", ["deepwiki"]) is None + + def test_uuid_prefix_survives_its_own_separators(self): + server_id = "117c814c-1a2b-3c4d-9e8f" + assert match_known_server_prefix(f"{server_id}-contents", [server_id]) == ( + server_id, + "contents", + ) + + def test_longest_candidate_wins_over_leading_segment(self): + # "svc" is a registered prefix in its own right and also the leading + # segment of "svc-prod". A first-separator split hands the tool to + # "svc" with a bare name of "prod-run", attributing it to the wrong + # server; longest-match keeps it on "svc-prod". + assert match_known_server_prefix("svc-prod-run", ["svc", "svc-prod"]) == ( + "svc-prod", + "run", + ) + + def test_candidates_are_normalised_before_matching(self): + assert match_known_server_prefix("my_server-run", ["my server"]) == ( + "my_server", + "run", + ) + + def test_empty_candidate_never_matches_a_leading_separator(self): + assert match_known_server_prefix("-run", [""]) is None + + +class TestIsToolNamePrefixedBoundary: + """The known-prefix gate decides which branch the call path takes, so it has + to agree with the prefix the list path actually emitted.""" + + def test_uuid_prefix_is_recognised(self): + server_id = "117c814c-1a2b-3c4d-9e8f" + assert is_tool_name_prefixed(f"{server_id}-contents", known_server_prefixes={server_id}) + + def test_unrelated_hyphenated_tool_is_still_not_prefixed(self): + # Negative control: an upstream tool whose own name contains the + # separator must not start reading as prefixed just because the gate + # got more permissive about where the boundary can fall. + assert not is_tool_name_prefixed("text-to-speech", known_server_prefixes={"deepwiki"}) + + # --------------------------------------------------------------------------- # Manager-level behaviour: list + reverse-lookup # --------------------------------------------------------------------------- From 33a92bd48f4df5a21bce0f503f0fe2cc7bff2b0a Mon Sep 17 00:00:00 2001 From: tin Date: Mon, 27 Jul 2026 19:07:33 +0000 Subject: [PATCH 08/92] fix(mcp): keep REST tool listing in step with key/team grant enforcement The REST listing filter matched key/team grants through _tool_name_matches, which after the prefix-boundary change answers for every spelling routing accepts. Key-level entries in mcp_tool_permissions and toolset rows name a tool on one server and dispatch compares them bare, so a wire-form entry advertised a tool that tools/call then refused. REST listing now goes through filter_tools_by_key_team_permissions, the same function the MCP list path uses. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../mcp_server/rest_endpoints.py | 18 +++--- tests/mcp_tests/test_mcp_server.py | 59 +++++++++++++++++++ 2 files changed, 67 insertions(+), 10 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py index 1736e34d70c..b5d2471336f 100644 --- a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py +++ b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py @@ -99,9 +99,9 @@ if MCP_AVAILABLE: MCPServer, _apply_toolset_scope, _fire_mcp_tool_call_logging, - _tool_name_matches, execute_mcp_tool, filter_tools_by_allowed_tools, + filter_tools_by_key_team_permissions, ) ######################################################## @@ -530,19 +530,17 @@ if MCP_AVAILABLE: tools = filter_tools_by_allowed_tools(tools, server) # Filter by the key's effective tool permissions through the same - # primitive the MCP protocol path uses (direct grants, toolset grants, - # and team/agent/org ceilings), so REST listing cannot drift from it + # function the MCP protocol path uses (direct grants, toolset grants, + # and team/agent/org ceilings), so REST listing cannot drift from it. + # Entries here are tool names on one server, written bare by every + # writer, and dispatch compares them bare; matching a wider set of + # spellings would advertise a tool that tools/call then refuses if user_api_key_auth: - from litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp import ( - MCPRequestHandler, - ) - - allowed_tools_for_server = await MCPRequestHandler.get_allowed_tools_for_server( + tools = await filter_tools_by_key_team_permissions( + tools=tools, server_id=server.server_id, user_api_key_auth=user_api_key_auth, ) - if allowed_tools_for_server is not None: - tools = [tool for tool in tools if _tool_name_matches(tool.name, allowed_tools_for_server, server)] return _create_tool_response_objects(tools, server) diff --git a/tests/mcp_tests/test_mcp_server.py b/tests/mcp_tests/test_mcp_server.py index 390f6917573..434a9bc3809 100644 --- a/tests/mcp_tests/test_mcp_server.py +++ b/tests/mcp_tests/test_mcp_server.py @@ -2039,6 +2039,65 @@ async def test_get_tools_for_single_server_applies_disallowed_tools_without_allo assert [tool.name for tool in result] == ["read_email"] +@pytest.mark.asyncio +async def test_rest_listing_hides_key_grants_dispatch_would_refuse(): + """REST listing must answer for exactly the key/team grants dispatch honors. + + ``mcp_tool_permissions`` and toolset rows name a tool on one server, so both + the MCP list path and ``tools/call`` compare them bare. A wire-form entry + therefore grants nothing, and REST listing that matched the prefixed + spelling would advertise a tool the very next call refuses. + """ + from litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp import ( + MCPRequestHandler, + ) + from litellm.proxy._experimental.mcp_server.rest_endpoints import ( + _get_tools_for_single_server, + ) + from litellm.proxy._types import UserAPIKeyAuth + from mcp.types import Tool as MCPTool + + server_id = "3c6f6617-d23c-4f48-bfb0-f205e3b27bab" + mock_server = MagicMock() + mock_server.mcp_info = {"server_name": server_id} + mock_server.name = server_id + mock_server.server_id = server_id + mock_server.server_name = None + mock_server.alias = None + mock_server.short_prefix = None + mock_server.allowed_tools = None + mock_server.disallowed_tools = None + mock_server.tool_name_to_display_name = None + + mock_tools = [ + MCPTool( + name="read_wiki_contents", + description="Read a wiki", + inputSchema={"type": "object"}, + ), + ] + + with patch( + "litellm.proxy._experimental.mcp_server.rest_endpoints.global_mcp_server_manager" + ) as mock_manager, patch( + "litellm.proxy._experimental.mcp_server.server.global_mcp_server_manager" + ) as mock_server_manager, patch.object( + MCPRequestHandler, + "get_allowed_tools_for_server", + AsyncMock(return_value=[f"{server_id}-read_wiki_contents"]), + ): + mock_manager._get_tools_from_server = AsyncMock(return_value=mock_tools) + mock_server_manager.get_mcp_server_by_id.return_value = mock_server + + result = await _get_tools_for_single_server( + mock_server, + "Bearer test_token", + user_api_key_auth=UserAPIKeyAuth(api_key="sk-test"), + ) + + assert result == [] + + @pytest.mark.asyncio async def test_list_tool_rest_api_with_server_specific_auth(): """Test list_tool_rest_api with server-specific auth headers.""" From d200e4a8ea84c4f588d21e105ce4ccf663ad1109 Mon Sep 17 00:00:00 2001 From: Tin Date: Mon, 27 Jul 2026 13:32:37 -0700 Subject: [PATCH 09/92] refactor(mcp): answer every tool-name permission question through one matcher The allow list, the deny list, allowed_params and the discovery filter all ask the same question, "which configured entry names this tool on this server", and each answered it in its own idiom: any() over a spelling tuple, all() over the same tuple negated, a next() that pulled a value out of a dict, and a lowercased set membership. Two review findings on this PR were symptoms of that duplication. Deriving the operands differently at one site produced the over-strip; needing a value rather than a boolean at another produced a truthiness test that read an explicitly empty allowed_params list as "nothing configured" and allowed every parameter. match_known_tool_name returns the matching entry or None, and all four sites read it, so no site can test a container's values to decide membership and the empty-list fail-open is no longer representable. Matching is case-insensitive everywhere, which closes the last divergence between discovery and dispatch: a case-variant disallowed_tools entry used to hide a tool from tools/list while tools/call still executed it. Executable lines over the merge-base drop from +9 to +4, all of it the new owner; mcp_server_manager.py loses 12 lines and the discovery filter loses 17. --- .../mcp_server/mcp_server_manager.py | 36 +++++++------------ .../proxy/_experimental/mcp_server/server.py | 27 ++++---------- .../proxy/_experimental/mcp_server/utils.py | 33 ++++++++++------- .../mcp_server/test_mcp_server.py | 11 +++--- .../mcp_server/test_mcp_server_manager.py | 15 ++++++++ 5 files changed, 60 insertions(+), 62 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index 285e7b26104..0462e76c2ad 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -118,6 +118,7 @@ from litellm.proxy._experimental.mcp_server.utils import ( is_short_mcp_tool_prefix_enabled, iter_known_server_prefixes, iter_known_tool_name_spellings, + match_known_tool_name, match_known_server_prefix, merge_mcp_headers, normalize_server_name, @@ -4262,26 +4263,19 @@ class MCPServerManager: """ Check if the tool is allowed or banned for the given server. - ``tool_name`` is bare: every caller resolves the boundary against the - server's registered prefixes before dispatch (``server.py``'s - ``original_tool_name``, the Responses handler's ``sanitized_tool_name``). - Stored entries are matched by deriving the spellings routing accepts - rather than by stripping the entries, which would cut a second boundary - out of a native name that itself opens with the server prefix. + ``tool_name`` is bare: every caller resolves the boundary against the server's + registered prefixes before dispatch (``server.py``'s ``original_tool_name``, the + Responses handler's ``sanitized_tool_name``). Configured entries are matched by + deriving the spellings routing accepts, never by stripping the entry, which would + cut a second boundary out of a native name that opens with the server prefix. """ from litellm.proxy._experimental.mcp_server.utils import ( server_applies_tool_allowlist, ) - spellings = tuple(iter_known_tool_name_spellings(tool_name, server)) - if server_applies_tool_allowlist(server): - if not server.allowed_tools: - return False - return any(spelling in server.allowed_tools for spelling in spellings) - if server.disallowed_tools: - return all(spelling not in server.disallowed_tools for spelling in spellings) - return True + return match_known_tool_name(tool_name, server, server.allowed_tools or ()) is not None + return match_known_tool_name(tool_name, server, server.disallowed_tools or ()) is None def validate_allowed_params(self, tool_name: str, arguments: dict[str, Any], server: MCPServer) -> None: """ @@ -4299,18 +4293,12 @@ class MCPServerManager: Raises: HTTPException: If allowed_params is configured for this tool but arguments contain disallowed params """ - # If no allowed_params configured, return all arguments - if not server.allowed_params: + allowed_params = server.allowed_params or {} + matched = match_known_tool_name(tool_name, server, allowed_params) + if matched is None: return - spellings = iter_known_tool_name_spellings(tool_name, server) - allowed_params_list = next( - (server.allowed_params[name] for name in spellings if name in server.allowed_params), None - ) - - # If this tool doesn't have allowed_params specified, allow all params - if allowed_params_list is None: - return None + allowed_params_list = allowed_params[matched] # Filter arguments to only include allowed parameters disallowed_params = [param for param in arguments.keys() if param not in allowed_params_list] diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index 5181f3a2676..2c774224098 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -65,7 +65,7 @@ from litellm.proxy._experimental.mcp_server.utils import ( extract_mcp_tool_result_error_message, get_server_prefix, iter_known_server_prefixes, - iter_known_tool_name_spellings, + match_known_tool_name, ) from litellm.proxy._types import ( ProxyException, @@ -1421,28 +1421,13 @@ if MCP_AVAILABLE: """ Check if a tool name matches any name in the filter list. - Matches via the same ``iter_known_tool_name_spellings`` the server-level - permission checks use, so discovery hides exactly what dispatch refuses; - covering fewer spellings here leaves a blocked tool advertised in - ``tools/list``. Comparison is case-insensitive to handle OpenAPI - operationIds that may be in camelCase. - - Args: - tool_name: The tool name to check (may be prefixed like "server-tool_name") - filter_list: List of tool names to match against - mcp_server: The server the tool belongs to, whose registered prefixes - locate the boundary exactly. Required: guessing the boundary at - the first separator silently mismatches every tool on a server - whose prefix contains the separator. - - Returns: - True if any spelling of the tool name is in the filter list + Reads the same owner the server-level permission checks use, so discovery hides + exactly what dispatch refuses. ``mcp_server`` is required: guessing the boundary + at the first separator mismatches every tool on a server whose prefix contains + the separator. """ - filter_list_lower = {f.lower() for f in filter_list} bare_name = strip_known_server_prefix(tool_name, mcp_server) - spellings = (tool_name, *iter_known_tool_name_spellings(bare_name, mcp_server)) - - return any(spelling.lower() in filter_list_lower for spelling in spellings) + return match_known_tool_name(bare_name, mcp_server, filter_list) is not None def filter_tools_by_allowed_tools( tools: list[MCPTool], diff --git a/litellm/proxy/_experimental/mcp_server/utils.py b/litellm/proxy/_experimental/mcp_server/utils.py index e8f8c08188e..900d6259a5a 100644 --- a/litellm/proxy/_experimental/mcp_server/utils.py +++ b/litellm/proxy/_experimental/mcp_server/utils.py @@ -23,6 +23,8 @@ import importlib import os from urllib.parse import quote +from litellm.types.mcp_server.mcp_server_manager import MCPServer + # Constants # # NOTE: The environment-backed values below are read once, when this module is @@ -326,24 +328,31 @@ def iter_known_server_prefixes(server: Any) -> Iterator[str]: yield from _emit(server_id) -def iter_known_tool_name_spellings(tool_name: str, server: Any) -> Iterator[str]: - """Yield every name that denotes the bare ``tool_name`` on ``server``. - - The bare name, then its wire spelling under each prefix from - ``iter_known_server_prefixes``. Routing resolves an inbound name against that - whole set, so anything keyed by tool name (the routing map, the allow/deny - lists, ``allowed_params``) must cover it too or it answers for fewer names - than are reachable, which fails open on ``disallowed_tools``. - ``get_server_prefix`` alone covers only the published spelling, and that moves - with the alias and with ``LITELLM_USE_SHORT_MCP_TOOL_PREFIX``. These are - spellings of one tool on one server, so honoring all of them normalizes the - entry rather than widening a grant. +def iter_known_tool_name_spellings(tool_name: str, server: MCPServer) -> Iterator[str]: + """Yield every name that denotes the bare ``tool_name`` on ``server``: the bare name, + then its wire spelling under each prefix ``iter_known_server_prefixes`` accepts. + ``get_server_prefix`` covers only the currently published one, and that moves with the + alias and with ``LITELLM_USE_SHORT_MCP_TOOL_PREFIX``. """ yield tool_name for prefix in iter_known_server_prefixes(server): yield add_server_prefix_to_name(tool_name, prefix) +def match_known_tool_name(tool_name: str, server: MCPServer, names: Iterable[str]) -> str | None: + """Return the entry of ``names`` that denotes ``tool_name`` on ``server``, else ``None``. + + The single question every tool-name-keyed site asks: the allow list, the deny list, + ``allowed_params`` and the discovery filter. Matching spans every spelling routing + accepts and ignores case, so discovery hides exactly what dispatch refuses. Callers + read the returned entry rather than testing a container's values, which is what stops + an explicitly empty ``allowed_params`` list from reading as "nothing configured". + """ + entries = {name.casefold(): name for name in names} + spellings = map(str.casefold, iter_known_tool_name_spellings(tool_name, server)) + return next((entries[spelling] for spelling in spellings if spelling in entries), None) + + def split_server_prefix_from_name(prefixed_name: str) -> Tuple[str, str]: """Return the unprefixed name plus the server name used as prefix. diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py index a3577c85621..5115fde2687 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py @@ -8146,10 +8146,11 @@ class TestListFiltersHonorThePrefixBoundary: published = MCPTool(name="eiG-read_wiki_contents", description="", inputSchema={"type": "object"}) for spelling in registered: - server = _server(disallowed_tools=[spelling]) + for entry in (spelling, spelling.upper()): + server = _server(disallowed_tools=[entry]) - refused = not manager.check_allowed_or_banned_tools("read_wiki_contents", server) - hidden = filter_tools_by_allowed_tools([published], server) == [] + refused = not manager.check_allowed_or_banned_tools("read_wiki_contents", server) + hidden = filter_tools_by_allowed_tools([published], server) == [] - assert refused, spelling - assert hidden, spelling + assert refused, entry + assert hidden, entry diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py index 2977f13caf1..134a5d89225 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py @@ -9511,6 +9511,21 @@ class TestServerToolListsHonorThePrefixBoundary: assert exc_info.value.status_code == 403 assert "include_internal" in exc_info.value.detail["error"] + @pytest.mark.asyncio + async def test_a_case_variant_blocklist_entry_still_blocks(self): + server = self._aliased_server(disallowed_tools=["PetStore-DeletePet"]) + + with pytest.raises(HTTPException) as exc_info: + await self._run_check(server, "deletepet") + + assert exc_info.value.status_code == 403 + + @pytest.mark.asyncio + async def test_a_case_variant_allowlist_entry_grants_the_tool(self): + server = self._aliased_server(allowed_tools=["PetStore-GetPetById"]) + + await self._run_check(server, "getpetbyid") + @pytest.mark.asyncio async def test_an_explicitly_empty_allowed_params_list_refuses_every_parameter(self): server = self._alias_less_server(allowed_params={"read_wiki_contents": []}) From b2d4dde46468dfa07b819aad61116e62b27d469c Mon Sep 17 00:00:00 2001 From: Tin Date: Mon, 27 Jul 2026 16:29:40 -0700 Subject: [PATCH 10/92] fix(mcp): give the key/team grant question one predicate Bugbot flagged REST listing advertising key/team grants that tools/call then refuses. The listing side was fixed by routing through filter_tools_by_key_team_permissions, but the two paths still answered the question with separate implementations that only happened to agree: listing stripped the known prefix and compared bare, dispatch compared whatever name it was handed, and each carried its own reading of None and of an empty list. Changing either side silently diverges from the other, which is how this defect appeared in the first place. MCPRequestHandler.tool_is_granted owns the whole decision, and both is_tool_allowed_for_server and filter_tools_by_key_team_permissions read it. None still means no tool-level restriction and an empty list still grants nothing, now stated once. Grants are stored bare by every writer, so matching stays exact against the bare name, deliberately unlike the server-level lists, which honor every spelling routing accepts. test_key_team_listing_and_dispatch_agree drives both production paths over one matrix. It asserts the expected verdict as well as the agreement, because two paths reading one predicate makes equality alone tautological: a wrong predicate keeps them consistent and the agreement assertion alone survived two mutants that the verdict assertion kills. --- .../mcp_server/auth/user_api_key_auth_mcp.py | 26 +++++---- .../proxy/_experimental/mcp_server/server.py | 8 ++- .../mcp_server/test_mcp_server.py | 58 +++++++++++++++++++ 3 files changed, 77 insertions(+), 15 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py b/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py index 5d8ac8d678f..dc796837277 100644 --- a/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py +++ b/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py @@ -1963,6 +1963,18 @@ class MCPRequestHandler: return allowed_tools + @staticmethod + def tool_is_granted(bare_tool_name: str, allowed_tool_names: list[str] | None) -> bool: + """Whether key/team tool permissions reach ``bare_tool_name`` on one server. + + ``None`` means no tool-level restriction; an empty list grants nothing. Entries + name a tool on a single server and every writer stores them bare, so the + comparison is exact against the bare name rather than against the spellings + routing accepts. Both the listing path and the call path answer through here, so + discovery cannot advertise a tool that ``tools/call`` then refuses. + """ + return allowed_tool_names is None or bare_tool_name in allowed_tool_names + @staticmethod async def is_tool_allowed_for_server( tool_name: str, @@ -1973,7 +1985,7 @@ class MCPRequestHandler: Check if a specific tool is allowed for a server based on key/team permissions. Args: - tool_name: Name of the tool to check + tool_name: Bare tool name, already resolved against the server's prefixes server_id: Server ID user_api_key_auth: User auth @@ -1984,17 +1996,7 @@ class MCPRequestHandler: server_id=server_id, user_api_key_auth=user_api_key_auth, ) - - # None means no restrictions (allow all) - if allowed_tools is None: - return True - - # Empty list means no tools allowed - if not allowed_tools: - return False - - # Check if tool is in allowed list - return tool_name in allowed_tools + return MCPRequestHandler.tool_is_granted(tool_name, allowed_tools) @staticmethod def is_tool_allowed( diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index 2c774224098..48effdb0f6e 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -2305,14 +2305,16 @@ if MCP_AVAILABLE: server_id=server_id, user_api_key_auth=user_api_key_auth, ) - if allowed_tool_names is None: - return tools # Tools arrive prefixed with the server's own prefix; strip exactly that # prefix (resolved from the server) rather than the first separator, so a # prefix containing the separator still reduces to the stored bare name. server = global_mcp_server_manager.get_mcp_server_by_id(server_id) - return [t for t in tools if strip_known_server_prefix(t.name, server) in allowed_tool_names] + return [ + t + for t in tools + if MCPRequestHandler.tool_is_granted(strip_known_server_prefix(t.name, server), allowed_tool_names) + ] async def _list_mcp_tools( user_api_key_auth: UserAPIKeyAuth | None = None, diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py index 5115fde2687..b2cdcd51294 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py @@ -8154,3 +8154,61 @@ class TestListFiltersHonorThePrefixBoundary: assert refused, entry assert hidden, entry + + @pytest.mark.asyncio + @pytest.mark.parametrize( + "grants,expected", + [ + (None, True), + ([], False), + (["read_wiki_contents"], True), + (["read_wiki_structure"], False), + ([f"{SERVER_ID}-read_wiki_contents"], False), + (["READ_WIKI_CONTENTS"], False), + ], + ) + async def test_key_team_listing_and_dispatch_agree(self, grants, expected): + """The key/team grant question, driven through both production paths. + + Listing and dispatch read one predicate, so a row where the tool is advertised + and then refused (or hidden while callable) cannot exist. Asserting the expected + verdict as well as the agreement matters: both paths reading one predicate makes + equality alone tautological, so a wrong predicate would keep them consistent. + Grants are stored bare, so the wire-form and case-variant rows deny; that is + deliberately unlike the server-level lists, which honor every spelling. + """ + from mcp.types import Tool as MCPTool + + from litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp import ( + MCPRequestHandler, + ) + from litellm.proxy._experimental.mcp_server.server import ( + filter_tools_by_key_team_permissions, + ) + from litellm.proxy._types import UserAPIKeyAuth + + server = MCPServer( + server_id=self.SERVER_ID, + name=self.SERVER_ID, + url="http://127.0.0.1:5115/mcp", + transport=MCPTransport.http, + ) + published = MCPTool( + name=f"{self.SERVER_ID}-read_wiki_contents", description="", inputSchema={"type": "object"} + ) + auth = UserAPIKeyAuth(api_key="sk-test") + + with patch.object( + MCPRequestHandler, "get_allowed_tools_for_server", AsyncMock(return_value=grants) + ), patch( + "litellm.proxy._experimental.mcp_server.server.global_mcp_server_manager" + ) as mock_manager: + mock_manager.get_mcp_server_by_id.return_value = server + + listed = await filter_tools_by_key_team_permissions([published], self.SERVER_ID, auth) != [] + callable_ = await MCPRequestHandler.is_tool_allowed_for_server( + tool_name="read_wiki_contents", server_id=self.SERVER_ID, user_api_key_auth=auth + ) + + assert listed == callable_, f"grants={grants!r} listed={listed} callable={callable_}" + assert listed is expected, f"grants={grants!r} expected={expected} got={listed}" From 7962407be02ada648db0e1c6682d179fb00b3312 Mon Sep 17 00:00:00 2001 From: Tin Date: Mon, 27 Jul 2026 17:03:17 -0700 Subject: [PATCH 11/92] fix(mcp): keep tool identity exact, fold case only where registration does Greptile flagged that match_known_tool_name case-folded both the configured entry and the derived spellings. Routing keeps two tools whose names differ only in case as two tools, so folding merged identities the dispatcher separates: on a server exposing getPet and getpet, an allowlist naming getPet also granted getpet, and a blocklist naming getPet also denied getpet. That is unauthorized execution on one arm and the wrong tool denied on the other. Matching is now exact, which is what identity means here. The case leniency it replaces was never typo tolerance; _register_openapi_tools rewrites every operationId through sanitize_openapi_tool_name, so an allowed_tools entry holding the spec's own spelling never equals the registered name. That link is recovered by replaying the same rewrite, and only on servers that carry a spec_path, which is how the rest of the manager already recognizes an OpenAPI server. Every name that rewrite produces is lowercased, so no two tools on such a server can differ only in case and the fold cannot merge anything. Native servers get no folding at all. test_case_folding_applies_to_openapi_ servers_and_not_to_native_ones pins both halves, and two tests pin that a policy naming one tool leaves its case-variant sibling alone. Dropping the spec_path guard, dropping the fold, and forcing the fold path are all killed. The two pre-existing case-insensitivity tests describe OpenAPI servers in their own docstrings but built fixtures without a spec_path, a shape production never produces for one; they now set it. --- .../proxy/_experimental/mcp_server/utils.py | 30 ++++++++++++++----- .../mcp_server/test_mcp_server.py | 25 ++++++++++++---- .../mcp_server/test_mcp_server_manager.py | 18 +++++++---- 3 files changed, 54 insertions(+), 19 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/utils.py b/litellm/proxy/_experimental/mcp_server/utils.py index 900d6259a5a..4c0690082d5 100644 --- a/litellm/proxy/_experimental/mcp_server/utils.py +++ b/litellm/proxy/_experimental/mcp_server/utils.py @@ -343,14 +343,30 @@ def match_known_tool_name(tool_name: str, server: MCPServer, names: Iterable[str """Return the entry of ``names`` that denotes ``tool_name`` on ``server``, else ``None``. The single question every tool-name-keyed site asks: the allow list, the deny list, - ``allowed_params`` and the discovery filter. Matching spans every spelling routing - accepts and ignores case, so discovery hides exactly what dispatch refuses. Callers - read the returned entry rather than testing a container's values, which is what stops - an explicitly empty ``allowed_params`` list from reading as "nothing configured". + ``allowed_params`` and the discovery filter, so discovery hides exactly what dispatch + refuses. It spans every spelling routing accepts and no more. A tool's identity is its + exact name, because routing dispatches two names differing only in case as two tools, + and folding case here would let one policy decide both. + + OpenAPI servers are the exception, and not a fuzzy one. ``_register_openapi_tools`` + rewrites every operationId through ``sanitize_openapi_tool_name``, so configuration + holding the spec's own spelling never equals the registered name; replaying that exact + rewrite on the entry recovers the link. It cannot merge identities, because every name + it produces is lowercased, so no two tools on such a server differ only in case. + + Callers read the returned entry rather than testing a container's values, which is what + stops an explicitly empty ``allowed_params`` list from reading as "nothing configured". """ - entries = {name.casefold(): name for name in names} - spellings = map(str.casefold, iter_known_tool_name_spellings(tool_name, server)) - return next((entries[spelling] for spelling in spellings if spelling in entries), None) + from litellm.proxy._experimental.mcp_server.openapi_to_mcp_generator import ( + sanitize_openapi_tool_name, + ) + + spellings = set(iter_known_tool_name_spellings(tool_name, server)) + exact = next((name for name in names if name in spellings), None) + if exact is not None or not getattr(server, "spec_path", None): + return exact + sanitized = {sanitize_openapi_tool_name(spelling) for spelling in spellings} + return next((name for name in names if sanitize_openapi_tool_name(name) in sanitized), None) def split_server_prefix_from_name(prefixed_name: str) -> Tuple[str, str]: diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py index b2cdcd51294..3a84428add1 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py @@ -4519,6 +4519,7 @@ def test_tool_name_matches_case_insensitive(): server_name="per_store", url="http://127.0.0.1:5115/mcp", transport=MCPTransport.http, + spec_path="/specs/petstore.yaml", ) # Test case 1: Unprefixed tool name with camelCase in filter list @@ -4597,6 +4598,7 @@ def test_filter_tools_by_allowed_tools_case_insensitive(): name="per_store", server_name="per_store", transport=MCPTransport.http, + spec_path="/specs/petstore.yaml", allowed_tools=["addPet", "updatePet", "findPetsByStatus"], ) @@ -8080,12 +8082,19 @@ class TestListFiltersHonorThePrefixBoundary: assert not _tool_name_matches(f"{self.SERVER_ID}-read_wiki_contents", ["read_wiki_structure"], server) - def test_match_is_still_case_insensitive(self): + def test_case_folding_applies_to_openapi_servers_and_not_to_native_ones(self): + # Registration rewrites operationIds through sanitize_openapi_tool_name, so + # folding recovers a spec-spelled entry on an OpenAPI server. A native server + # gets none of it: routing dispatches two names differing only in case as two + # tools, so one policy must not decide both. from litellm.proxy._experimental.mcp_server.server import _tool_name_matches - server = self._alias_less_server() + native = self._alias_less_server() + openapi = self._alias_less_server(spec_path="/specs/petstore.yaml") - assert _tool_name_matches(f"{self.SERVER_ID}-findPetsByStatus", ["findpetsbystatus"], server) + assert _tool_name_matches(f"{self.SERVER_ID}-findPetsByStatus", ["findpetsbystatus"], openapi) + assert not _tool_name_matches(f"{self.SERVER_ID}-findPetsByStatus", ["findpetsbystatus"], native) + assert _tool_name_matches(f"{self.SERVER_ID}-findpetsbystatus", ["findpetsbystatus"], native) def test_alias_form_entry_matches_a_tool_published_under_the_short_prefix(self, monkeypatch): # Routing accepts the alias form, so an entry stored before short @@ -8111,6 +8120,10 @@ class TestListFiltersHonorThePrefixBoundary: A spelling the blocklist enforces but the filter misses leaves a blocked tool advertised; the reverse hides a tool that would have been callable. + Every spelling routing registers bans, and its upper-cased form bans nothing, + because a tool's identity is its exact name; asserting the verdict and not only + the agreement is what keeps this from passing on a matcher that answers wrongly + but consistently. """ from mcp.types import Tool as MCPTool @@ -8146,14 +8159,14 @@ class TestListFiltersHonorThePrefixBoundary: published = MCPTool(name="eiG-read_wiki_contents", description="", inputSchema={"type": "object"}) for spelling in registered: - for entry in (spelling, spelling.upper()): + for entry, expected in ((spelling, True), (spelling.upper(), False)): server = _server(disallowed_tools=[entry]) refused = not manager.check_allowed_or_banned_tools("read_wiki_contents", server) hidden = filter_tools_by_allowed_tools([published], server) == [] - assert refused, entry - assert hidden, entry + assert refused == hidden, entry + assert refused is expected, entry @pytest.mark.asyncio @pytest.mark.parametrize( diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py index 134a5d89225..cdef50ad6b5 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py @@ -9512,19 +9512,25 @@ class TestServerToolListsHonorThePrefixBoundary: assert "include_internal" in exc_info.value.detail["error"] @pytest.mark.asyncio - async def test_a_case_variant_blocklist_entry_still_blocks(self): - server = self._aliased_server(disallowed_tools=["PetStore-DeletePet"]) + async def test_a_blocklist_entry_does_not_reach_a_case_variant_sibling_tool(self): + server = self._aliased_server(disallowed_tools=["petstore-getPet"]) with pytest.raises(HTTPException) as exc_info: - await self._run_check(server, "deletepet") + await self._run_check(server, "getPet") assert exc_info.value.status_code == 403 + await self._run_check(server, "getpet") @pytest.mark.asyncio - async def test_a_case_variant_allowlist_entry_grants_the_tool(self): - server = self._aliased_server(allowed_tools=["PetStore-GetPetById"]) + async def test_an_allowlist_entry_does_not_grant_a_case_variant_sibling_tool(self): + server = self._aliased_server(allowed_tools=["petstore-getPet"]) - await self._run_check(server, "getpetbyid") + await self._run_check(server, "getPet") + + with pytest.raises(HTTPException) as exc_info: + await self._run_check(server, "getpet") + + assert exc_info.value.status_code == 403 @pytest.mark.asyncio async def test_an_explicitly_empty_allowed_params_list_refuses_every_parameter(self): From 8018bc3996c533ac642b5fc0e0511cef891315b2 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Thu, 30 Jul 2026 22:19:30 -0700 Subject: [PATCH 12/92] fix(e2e): exclude skipped tests from coverage-registry numerator The collector read @pytest.mark.covers off every collected item, and collection does not evaluate skips, so a test carrying both a skip and a covers marker reported its cell as covered while asserting nothing. 17 files under tests/e2e do exactly that, which inflated the headline from 290/434 to 311/434. A cell now counts as covered only when at least one test pytest would actually run declares it; a cell claimed by both a live and a skipped test stays covered. Skip state comes from pytest's own evaluator, so skip, skipif (bool and string conditions), and module-level pytestmark resolve exactly as they do in the e2e run. Cells left uncovered this way are listed under the headline and exported as skipped_markers (JSON) and litellm_e2e_coverage_skipped_markers (Prometheus) so the gap surfaces instead of disappearing; the Loki line contract is unchanged. A marker on a skipped test that points outside the registry is still an orphan, so --strict keeps its reach. Because skipif resolves against the environment the collector runs in, the number now depends on that environment; run it where the e2e suite runs. A pytest.skip() call inside a test body remains invisible to a static pass, which the module docstring and README both state. --- tests/e2e/CLAUDE.md | 2 + tests/e2e/coverage_registry/README.md | 10 ++ tests/e2e/coverage_registry/collector.py | 94 +++++++++++++++--- tests/e2e/coverage_registry/test_collector.py | 97 +++++++++++++++++++ 4 files changed, 189 insertions(+), 14 deletions(-) diff --git a/tests/e2e/CLAUDE.md b/tests/e2e/CLAUDE.md index 17aee22560c..180639b53e3 100644 --- a/tests/e2e/CLAUDE.md +++ b/tests/e2e/CLAUDE.md @@ -85,6 +85,8 @@ The metric is coverage: the share of registry rows that have a passing covering Tests do not declare a dashboard module directly. They only declare the registry cell id with `@pytest.mark.covers("...")`; the registry row decides the module, tier, endpoint, and dashboard rollup. Run `python -m coverage_registry.collector --strict` when you want CI to reject unknown marker ids. Add `--fail-on-collection-errors` when the job should also fail on pytest collection errors. +Skipping a test gives its cell back to the gap list: the collector counts a cell as covered only when a test pytest would actually run declares it, and prints the cells left claimed only by skipped tests. So a `@pytest.mark.skip` on a red cell is honest bookkeeping, not a way to keep the number up. + ### Naming grammar per module LLMs - endpoint features (subject = the route), seeded from the Claude Code compat matrix. `chat_completions`, `messages`, and `responses` roll up to `Core LLMs`. Other LLM endpoints, including `batches` and `realtime`, roll up to `Non-Core LLMs`. diff --git a/tests/e2e/coverage_registry/README.md b/tests/e2e/coverage_registry/README.md index aef4c16c89a..5627c88dee4 100644 --- a/tests/e2e/coverage_registry/README.md +++ b/tests/e2e/coverage_registry/README.md @@ -37,6 +37,16 @@ def test_openai_streaming_tool_calls(self) -> None: It is static: a collect-only pass reads the markers, so it runs no test and needs no live proxy. Whether a covered cell currently passes or fails is a separate, live concern. +A skipped test asserts nothing, so its markers do not count. A cell is covered only when +at least one test pytest would actually run declares it; a cell claimed by both a live +test and a skipped one stays covered. Skip state comes from pytest's own evaluator, so +`skip` and `skipif` resolve exactly as they do in the e2e run, which also means a +`skipif` on an absent credential makes that cell uncovered in the environments where the +test cannot run. Cells left uncovered this way are listed under the headline (and counted +by `litellm_e2e_coverage_skipped_markers`) so an unskipped-pending gap is visible rather +than inflating the number. The one skip the collector cannot see is `pytest.skip()` +called from inside a test body, since it does not exist until the test runs. + ``` cd tests/e2e && PYTHONPATH=. python -m coverage_registry.collector ``` diff --git a/tests/e2e/coverage_registry/collector.py b/tests/e2e/coverage_registry/collector.py index 50ef23bcb1a..e20f7884f55 100644 --- a/tests/e2e/coverage_registry/collector.py +++ b/tests/e2e/coverage_registry/collector.py @@ -5,6 +5,13 @@ Coverage here is static: it reads the markers via a collect-only pass, so it run no test and needs no live proxy. Whether a covered cell currently passes or fails (covered_pass vs covered_fail) is a separate, live concern layered on top later. +A skipped test asserts nothing, so its markers do not count: a cell is covered +only when at least one test that pytest would actually run declares it. Skip +state is read with pytest's own evaluator, so `skip` and `skipif` are resolved +exactly as the e2e run resolves them in this environment. The one skip the +collector cannot see is `pytest.skip()` called from inside a test body, which +does not exist until the test runs. + cd tests/e2e && PYTHONPATH=. python -m coverage_registry.collector """ @@ -20,6 +27,7 @@ from pathlib import Path from typing import Literal import pytest +from _pytest.skipping import evaluate_skip_marks from pydantic import BaseModel from .registry import load_registry @@ -28,22 +36,57 @@ from .schema import MODULE_ORDER, Cell, Tier, dashboard_module, loki_module_labe E2E_DIR = Path(__file__).resolve().parent.parent +@dataclass(frozen=True, slots=True) +class CollectedMarkers: + """What a collect-only pass saw: cell ids declared by tests that would run, + cell ids only ever declared by skipped tests, and nodes that failed to import.""" + + covered: frozenset[str] + skipped_only: frozenset[str] + collection_errors: tuple[str, ...] + + +def _is_skipped(item: pytest.Item) -> bool: + """True when pytest would skip this test instead of running it. + + A marker pytest cannot evaluate (for example a bare boolean `skipif` with no + reason) turns into a setup failure at run time, so the test asserts nothing + either way and is treated the same as a skip. + """ + try: + return evaluate_skip_marks(item) is not None + except (pytest.fail.Exception, TypeError): + return True + + class _CoversSink: """Pytest plugin: after collection, capture every cell id declared via - @pytest.mark.covers(...), plus any nodes that failed to import.""" + @pytest.mark.covers(...) split by whether its test would run, plus any nodes + that failed to import.""" def __init__(self) -> None: self.covered_ids: frozenset[str] = frozenset() + self.skipped_only_ids: frozenset[str] = frozenset() self.collection_errors: tuple[str, ...] = () def pytest_collection_finish(self, session: pytest.Session) -> None: - marker_args: tuple[tuple[object, ...], ...] = tuple( - marker.args - for item in session.items + marker_args: tuple[tuple[bool, tuple[object, ...]], ...] = tuple( + (skipped, marker.args) + for item, skipped in ((i, _is_skipped(i)) for i in session.items) for marker in item.iter_markers(name="covers") ) + declared = tuple( + (skipped, arg) + for skipped, args in marker_args + for arg in args + if isinstance(arg, str) + ) self.covered_ids = frozenset( - arg for args in marker_args for arg in args if isinstance(arg, str) + cell_id for skipped, cell_id in declared if not skipped + ) + self.skipped_only_ids = ( + frozenset(cell_id for skipped, cell_id in declared if skipped) + - self.covered_ids ) def pytest_collectreport(self, report: pytest.CollectReport) -> None: @@ -51,10 +94,8 @@ class _CoversSink: self.collection_errors = (*self.collection_errors, report.nodeid) -def collect_covered_ids( - e2e_dir: Path = E2E_DIR, -) -> tuple[frozenset[str], tuple[str, ...]]: - """Return (covered cell ids, nodeids that failed to import).""" +def collect_markers(e2e_dir: Path = E2E_DIR) -> CollectedMarkers: + """Read every @pytest.mark.covers marker in `e2e_dir` via a collect-only pass.""" sink = _CoversSink() with contextlib.redirect_stdout(io.StringIO()): pytest.main( @@ -68,7 +109,11 @@ def collect_covered_ids( ], plugins=[sink], ) - return sink.covered_ids, sink.collection_errors + return CollectedMarkers( + covered=sink.covered_ids, + skipped_only=sink.skipped_only_ids, + collection_errors=sink.collection_errors, + ) @dataclass(frozen=True, slots=True) @@ -93,6 +138,7 @@ class CoverageReport: p0_covered: int p0_gaps: tuple[str, ...] orphan_markers: tuple[str, ...] + skipped_markers: tuple[str, ...] collection_errors: tuple[str, ...] @property @@ -122,6 +168,7 @@ def compute_coverage( cells: tuple[Cell, ...], covered: frozenset[str], collection_errors: tuple[str, ...] = (), + skipped_only: frozenset[str] = frozenset(), ) -> CoverageReport: p0_cells = tuple(c for c in cells if c.tier is Tier.P0) registry_ids = frozenset(c.id for c in cells) @@ -132,7 +179,8 @@ def compute_coverage( p0_total=len(p0_cells), p0_covered=sum(1 for c in p0_cells if c.id in covered), p0_gaps=tuple(sorted(c.id for c in p0_cells if c.id not in covered)), - orphan_markers=tuple(sorted(covered - registry_ids)), + orphan_markers=tuple(sorted((covered | skipped_only) - registry_ids)), + skipped_markers=tuple(sorted(skipped_only & registry_ids)), collection_errors=collection_errors, ) @@ -161,6 +209,15 @@ def render(report: CoverageReport) -> str: if report.orphan_markers else () ) + skipped = ( + ( + f"\n{len(report.skipped_markers)} cell(s) are claimed only by skipped tests, " + f"so they count as uncovered (unskip the test or drop the marker):\n " + + "\n ".join(report.skipped_markers), + ) + if report.skipped_markers + else () + ) warning = ( ( f"\nWARNING: {len(report.collection_errors)} node(s) failed to import during " @@ -170,7 +227,7 @@ def render(report: CoverageReport) -> str: if report.collection_errors else () ) - return "\n".join((*lines, *orphans, *warning)) + return "\n".join((*lines, *orphans, *skipped, *warning)) def _report_dict(report: CoverageReport) -> dict[str, object]: @@ -190,6 +247,7 @@ def _report_dict(report: CoverageReport) -> dict[str, object]: for m in report.modules ], "orphan_markers": list(report.orphan_markers), + "skipped_markers": list(report.skipped_markers), "collection_errors": list(report.collection_errors), } @@ -234,6 +292,9 @@ def render_prometheus(report: CoverageReport) -> str: "# HELP litellm_e2e_coverage_orphan_markers Coverage markers not found in the registry.", "# TYPE litellm_e2e_coverage_orphan_markers gauge", f"litellm_e2e_coverage_orphan_markers {len(report.orphan_markers)}", + "# HELP litellm_e2e_coverage_skipped_markers Registry cells claimed only by skipped tests.", + "# TYPE litellm_e2e_coverage_skipped_markers gauge", + f"litellm_e2e_coverage_skipped_markers {len(report.skipped_markers)}", "# HELP litellm_e2e_coverage_collection_errors Pytest nodes that failed during collection.", "# TYPE litellm_e2e_coverage_collection_errors gauge", f"litellm_e2e_coverage_collection_errors {len(report.collection_errors)}", @@ -286,8 +347,13 @@ def main() -> int: ) args = _CliArgs.model_validate(vars(parser.parse_args())) cells = load_registry() - covered, errors = collect_covered_ids() - report = compute_coverage(cells, covered, errors) + markers = collect_markers() + report = compute_coverage( + cells, + markers.covered, + markers.collection_errors, + markers.skipped_only, + ) output = { "text": render, "json": render_json, diff --git a/tests/e2e/coverage_registry/test_collector.py b/tests/e2e/coverage_registry/test_collector.py index 079ee215866..a85190cc3ba 100644 --- a/tests/e2e/coverage_registry/test_collector.py +++ b/tests/e2e/coverage_registry/test_collector.py @@ -12,6 +12,7 @@ from pathlib import Path import pytest from coverage_registry.collector import ( + collect_markers, compute_coverage, render, render_json, @@ -61,6 +62,27 @@ def test_orphan_marker_is_reported_not_counted() -> None: assert report.orphan_markers == ("llm.ghost",) +def test_cell_claimed_only_by_a_skipped_test_is_uncovered() -> None: + cells = (_llm("llm.a", Tier.P0), _llm("llm.b", Tier.P0)) + report = compute_coverage( + cells, frozenset({"llm.a"}), skipped_only=frozenset({"llm.b"}) + ) + assert (report.covered, report.p0_covered) == (1, 1) + assert report.p0_gaps == ("llm.b",) + assert report.skipped_markers == ("llm.b",) + assert "only by skipped tests" in render(report) + assert '"skipped_markers": [\n "llm.b"\n ]' in render_json(report) + assert "litellm_e2e_coverage_skipped_markers 1" in render_prometheus(report) + + +def test_skipped_marker_outside_the_registry_is_still_an_orphan() -> None: + report = compute_coverage( + (_llm("llm.a", Tier.P0),), frozenset(), skipped_only=frozenset({"llm.ghost"}) + ) + assert report.orphan_markers == ("llm.ghost",) + assert report.skipped_markers == () + + def test_logging_and_guardrail_roll_up_into_one_module() -> None: cells = ( LoggingCell( @@ -175,6 +197,81 @@ def test_loki_render_exposes_exact_stdout_lines_for_loki() -> None: ) +_MARKED_TESTS = ''' +import pytest + + +@pytest.mark.covers("llm.runs") +def test_runs() -> None: + pass + + +@pytest.mark.skip(reason="stage red: product gap") +@pytest.mark.covers("llm.skipped") +def test_skipped() -> None: + pass + + +@pytest.mark.skipif(True, reason="credentials absent in this environment") +@pytest.mark.covers("llm.skipif_true") +def test_skipif_true() -> None: + pass + + +@pytest.mark.skipif(False, reason="credentials present in this environment") +@pytest.mark.covers("llm.skipif_false") +def test_skipif_false() -> None: + pass + + +@pytest.mark.skipif("True") +@pytest.mark.covers("llm.skipif_string") +def test_skipif_string_condition() -> None: + pass + + +@pytest.mark.covers("llm.shared") +def test_shared_cell_runs() -> None: + pass + + +@pytest.mark.skip(reason="stage red: product gap") +@pytest.mark.covers("llm.shared") +def test_shared_cell_skipped() -> None: + pass +''' + +_MODULE_LEVEL_SKIP = ''' +import pytest + +pytestmark = pytest.mark.skipif(True, reason="whole module needs a session fixture") + + +@pytest.mark.covers("llm.module_skipped") +def test_module_level_skip() -> None: + pass +''' + + +def test_collection_counts_only_markers_on_tests_that_would_run( + tmp_path: Path, +) -> None: + """The collect-only pass is the numerator, so a test pytest would skip must not + contribute its cell. A cell stays covered as long as one runnable test claims it.""" + (tmp_path / "test_marked.py").write_text(_MARKED_TESTS) + (tmp_path / "test_module_skip.py").write_text(_MODULE_LEVEL_SKIP) + + markers = collect_markers(tmp_path) + + assert markers.covered == frozenset( + {"llm.runs", "llm.skipif_false", "llm.shared"} + ) + assert markers.skipped_only == frozenset( + {"llm.skipped", "llm.skipif_true", "llm.skipif_string", "llm.module_skipped"} + ) + assert markers.collection_errors == () + + def test_real_registry_loads_and_ids_are_unique() -> None: cells = load_registry() ids = [c.id for c in cells] From 089a4fa228db8bcf28f78db56b6a37e61f061bb6 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Thu, 30 Jul 2026 22:26:05 -0700 Subject: [PATCH 13/92] fix(lint): restrict freezing-wrapper match to bare names and types.MappingProxyType --- scripts/check_type_discipline.py | 21 +++++++++++-------- .../test_check_type_discipline.py | 6 ++++++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/scripts/check_type_discipline.py b/scripts/check_type_discipline.py index 88679f28190..43a4cb66484 100644 --- a/scripts/check_type_discipline.py +++ b/scripts/check_type_discipline.py @@ -386,12 +386,15 @@ def _annotation_node_ids(tree: ast.AST) -> frozenset[int]: ) -def _callable_name(func: ast.expr) -> str | None: +def _is_freezing_wrapper(func: ast.expr) -> bool: if isinstance(func, ast.Name): - return func.id - if isinstance(func, ast.Attribute): - return func.attr - return None + return func.id in FREEZING_WRAPPERS + return ( + isinstance(func, ast.Attribute) + and func.attr == "MappingProxyType" + and isinstance(func.value, ast.Name) + and func.value.id == "types" + ) def _frozen_argument_ids(tree: ast.AST) -> frozenset[int]: @@ -400,14 +403,14 @@ def _frozen_argument_ids(tree: ast.AST) -> frozenset[int]: `MappingProxyType({...})`, `frozenset({...})`, and `tuple([...])` freeze their argument before it can escape, so the literal inside is a one-shot build, not a mutable value anyone can grow later. Only the argument itself is exempt; a - mutable collection nested inside it still trips LIT002. + mutable collection nested inside it still trips LIT002. Only bare names (plus + `types.MappingProxyType`) qualify, so an unrelated method that happens to share + a wrapper's name cannot exempt its argument. """ return frozenset( id(node.args[0]) for node in ast.walk(tree) - if isinstance(node, ast.Call) - and len(node.args) == 1 - and _callable_name(node.func) in FREEZING_WRAPPERS + if isinstance(node, ast.Call) and len(node.args) == 1 and _is_freezing_wrapper(node.func) ) diff --git a/tests/test_litellm/test_check_type_discipline.py b/tests/test_litellm/test_check_type_discipline.py index f624eb926d1..53d672fc4a8 100644 --- a/tests/test_litellm/test_check_type_discipline.py +++ b/tests/test_litellm/test_check_type_discipline.py @@ -160,6 +160,12 @@ def test_value_frozen_by_wrapper_is_exempt(tmp_path): assert "LIT002" not in _codes(tmp_path, "t = tuple([1, 2])\n") +def test_same_named_method_does_not_exempt_its_argument(tmp_path): + assert "LIT002" in _codes(tmp_path, "t = obj.tuple([1, 2])\n") + assert "LIT002" in _codes(tmp_path, "f = obj.frozenset({1, 2})\n") + assert "LIT002" in _codes(tmp_path, "m = obj.MappingProxyType({'a': 1})\n") + + def test_mutable_nested_inside_frozen_wrapper_still_counts(tmp_path): assert "LIT002" in _codes(tmp_path, "from types import MappingProxyType\nm = MappingProxyType({'a': []})\n") From 179ebdb86bfefea9cde3d9e9028b7e62927af1eb Mon Sep 17 00:00:00 2001 From: Tin Date: Mon, 27 Jul 2026 17:34:20 -0700 Subject: [PATCH 14/92] fix(mcp): make the operationId to tool-name map a single owner Greptile found that the OpenAPI fallback added a commit ago collapsed operation IDs that registration keeps apart: foo/bar and foo.bar register as two tools but sanitize_openapi_tool_name rewrites both to foo_bar, so a policy naming either also decided the other. The cause was two owners for one map, and picking the wrong one. Registration names an operationId inline at _register_openapi_tools with operation_id.replace(" ", "_").lower(), which keeps / and . ; the separate sanitize_openapi_tool_name replaces every character outside [a-zA-Z0-9_-] and belongs to register_tools_from_openapi, which has no production caller. Nothing made the matcher use the one that actually registers, so it used the lookalike. That inline expression is now openapi_tool_name in utils, and both registration and the matcher call it. Replaying the registering function is the whole safety argument, and it is structural rather than a claim: two operationIds that register as two tools normalize to two names here by construction, because this is the map that registered them. A coarser lookalike cannot be substituted without a test failing. The matcher also loses its exact-then-fallback split. The transform is identity on native servers and idempotent on already-registered names, so normalizing both sides is exact matching where no OpenAPI spec is involved. Executable lines drop by three this round; the branch is +5 over the merge-base for four shared owners that removed duplication at six call sites. --- .../mcp_server/mcp_server_manager.py | 5 ++- .../proxy/_experimental/mcp_server/utils.py | 45 ++++++++++--------- .../mcp_server/test_mcp_server_manager.py | 10 +++++ 3 files changed, 36 insertions(+), 24 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index 0462e76c2ad..89c83459524 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -118,10 +118,11 @@ from litellm.proxy._experimental.mcp_server.utils import ( is_short_mcp_tool_prefix_enabled, iter_known_server_prefixes, iter_known_tool_name_spellings, - match_known_tool_name, match_known_server_prefix, + match_known_tool_name, merge_mcp_headers, normalize_server_name, + openapi_tool_name, parse_admin_env_vars, strip_known_server_prefix, validate_mcp_server_name, @@ -1785,7 +1786,7 @@ class MCPServerManager: # Generate tool name (without prefix initially) operation_id = operation.get("operationId", f"{method}_{path.replace('/', '_')}") - base_tool_name = operation_id.replace(" ", "_").lower() + base_tool_name = openapi_tool_name(operation_id) # Add server prefix to tool name prefixed_tool_name = add_server_prefix_to_name(base_tool_name, server_prefix) diff --git a/litellm/proxy/_experimental/mcp_server/utils.py b/litellm/proxy/_experimental/mcp_server/utils.py index 4c0690082d5..698df147247 100644 --- a/litellm/proxy/_experimental/mcp_server/utils.py +++ b/litellm/proxy/_experimental/mcp_server/utils.py @@ -2,7 +2,10 @@ MCP Server Utilities """ +import hashlib +import importlib import json +import os import re from collections.abc import MutableMapping, MutableSequence from typing import ( @@ -17,10 +20,6 @@ from typing import ( Tuple, Union, ) - -import hashlib -import importlib -import os from urllib.parse import quote from litellm.types.mcp_server.mcp_server_manager import MCPServer @@ -339,34 +338,36 @@ def iter_known_tool_name_spellings(tool_name: str, server: MCPServer) -> Iterato yield add_server_prefix_to_name(tool_name, prefix) +def openapi_tool_name(operation_id: str) -> str: + """Return the tool name ``_register_openapi_tools`` registers ``operation_id`` under. + + The single transform between a spec's operationId and the name the gateway serves. + Policy recovers the link by replaying this exact function, which is what keeps it from + deciding for a tool it does not name: two operationIds that register as two tools + necessarily normalize to two names here, because this is the map that registered them. + """ + return operation_id.replace(" ", "_").lower() + + def match_known_tool_name(tool_name: str, server: MCPServer, names: Iterable[str]) -> str | None: """Return the entry of ``names`` that denotes ``tool_name`` on ``server``, else ``None``. The single question every tool-name-keyed site asks: the allow list, the deny list, ``allowed_params`` and the discovery filter, so discovery hides exactly what dispatch - refuses. It spans every spelling routing accepts and no more. A tool's identity is its - exact name, because routing dispatches two names differing only in case as two tools, - and folding case here would let one policy decide both. + refuses. It spans every spelling routing accepts and no more, because a tool's identity + is the exact name routing dispatches; anything looser lets one policy decide two tools. - OpenAPI servers are the exception, and not a fuzzy one. ``_register_openapi_tools`` - rewrites every operationId through ``sanitize_openapi_tool_name``, so configuration - holding the spec's own spelling never equals the registered name; replaying that exact - rewrite on the entry recovers the link. It cannot merge identities, because every name - it produces is lowercased, so no two tools on such a server differ only in case. + On an OpenAPI server the configured entry holds the spec's operationId while routing + holds :func:`openapi_tool_name` of it, so both sides go through that map first. Doing it + with the registering function rather than a lookalike is the whole safety argument: a + coarser one collapses operationIds that registration keeps apart. Callers read the returned entry rather than testing a container's values, which is what stops an explicitly empty ``allowed_params`` list from reading as "nothing configured". """ - from litellm.proxy._experimental.mcp_server.openapi_to_mcp_generator import ( - sanitize_openapi_tool_name, - ) - - spellings = set(iter_known_tool_name_spellings(tool_name, server)) - exact = next((name for name in names if name in spellings), None) - if exact is not None or not getattr(server, "spec_path", None): - return exact - sanitized = {sanitize_openapi_tool_name(spelling) for spelling in spellings} - return next((name for name in names if sanitize_openapi_tool_name(name) in sanitized), None) + normalize = openapi_tool_name if getattr(server, "spec_path", None) else str + spellings = {normalize(spelling) for spelling in iter_known_tool_name_spellings(tool_name, server)} + return next((name for name in names if normalize(name) in spellings), None) def split_server_prefix_from_name(prefixed_name: str) -> Tuple[str, str]: diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py index cdef50ad6b5..db5b64ef131 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py @@ -9511,6 +9511,16 @@ class TestServerToolListsHonorThePrefixBoundary: assert exc_info.value.status_code == 403 assert "include_internal" in exc_info.value.detail["error"] + @pytest.mark.asyncio + async def test_an_entry_does_not_decide_an_operation_id_registration_keeps_separate(self): + server = self._aliased_server(disallowed_tools=["foo/bar"], spec_path="/specs/petstore.yaml") + + with pytest.raises(HTTPException) as exc_info: + await self._run_check(server, "foo/bar") + + assert exc_info.value.status_code == 403 + await self._run_check(server, "foo.bar") + @pytest.mark.asyncio async def test_a_blocklist_entry_does_not_reach_a_case_variant_sibling_tool(self): server = self._aliased_server(disallowed_tools=["petstore-getPet"]) From 4d2b7224fd637c0304789df55384bcd64e14c364 Mon Sep 17 00:00:00 2001 From: tin-berri Date: Thu, 30 Jul 2026 22:38:54 -0700 Subject: [PATCH 15/92] fix(mcp): annotate connected-app reachability on the gateway connect page (#34867) * fix(mcp): annotate connected-app reachability on the gateway connect page The MCP connect page resolved its server grid through the dashboard identity (admin shortcut or view_all returns the whole registry) while the gateway DCR session it sets up resolves servers as an admitted subject through grant sources only, so the page showed servers and tool counts the session is never served. GET /v1/mcp/server now accepts connected_app_view=true and stamps each returned server with connected_app_reachable, computed by the same _reload_admitted_user + get_allowed_mcp_servers pair the live session uses. The connect page requests the flag in connect mode and renders unreachable servers dimmed with a label, excluded from the Connected count and tool-count fetches. Failure to build the admitted set marks everything unreachable, which matches what such a session would actually be served. Default behavior without the param is unchanged for every existing consumer. * fix(mcp): block connecting unavailable servers from the connect-mode detail view A server the connect page marks unavailable could still be added through its detail view Connect action, so the selection could contain servers the connected-app session is never served. The unavailability decision now lives in one predicate, connectUnavailabilityLabel, consumed by the card indicator, the detail view action area, the toggle-on path, the oauth auto-select effect, and the Connected count, so no interaction path can disagree with the label. This also closes the same pre-existing hole for servers marked not supported on this connection, whose detail view likewise offered Connect, and removes a grandfathered nested ternary, ratcheting the eslint suppressions baseline down * fix(mcp): hide unreachable servers on the connect page instead of dimming them Product decision: the connect page should only show what a connected-app session will actually be served, so annotated-unreachable servers are now filtered out of the connect-mode list at fetch time rather than rendered dimmed. Unsupported auth types keep their existing dimmed label since they are a property of the server, not the caller. A user with zero reachable servers gets an explanatory empty state pointing at grants. The list filter is the single source: counts, tabs, auto-select, detail view, and tool-count fetches all derive from the already-filtered state * fix(mcp): guarantee the connect view lists every session-reachable server The connect view's membership came from the dashboard resolver with the admitted-subject answer only annotated on top, so a server reachable by the session but missing from the dashboard list would be invisible on the page; an under-report, the mirror of the bug this PR fixes. The connect view now unions in any session-reachable server the dashboard resolver did not list, built from the registry and redacted through the same ladder, so page membership equals the admitted set by construction in both directions * fix(mcp): honor connected_app_view only for the dashboard UI session credential The reachability view resolves through the owning user's admitted identity, so a caller-passed virtual key could use the param to enumerate servers beyond its own scope (ids, names, descriptions of the owner's wider grants). The view is now gated on is_ui_session_credential, a predicate factored out of resolve_ui_session_team_ids so the two user-identity widening sites share one trust boundary: the SSO-minted dashboard session token acting as its user. Any other credential gets the param as a no-op and the admitted resolver is never consulted for it * fix(mcp): resolve UI sessions with the admitted-user context everywhere, not per endpoint The list endpoint unioned in session-reachable servers itself while tool counts, Connect actions, and credential endpoints still authorized through build_effective_auth_contexts, whose contexts carry team grants but never the user row's own object permission; a user-granted server could render on the connect page while every interaction on it failed. The admitted-user context (the same auth a gateway session resolves with) is now appended inside build_effective_auth_contexts for UI session credentials, so the page list and every per-server action endpoint answer identically, and the list endpoint's one-off union is deleted. Caller-passed keys are still never widened (is_ui_session_credential gate inside the context builder) and a reload failure falls back to team contexts only * fix(mcp): resolve non-admin dashboard sessions as the admitted subject on tool routes Server reachability on the REST tool routes came from the widened context union while tool permission checks ran on the bare session key, which carries no object permission, so a dashboard user could invoke tools their user-level grant excludes. Rather than bookkeeping which context granted which server, the routes now choose one principal at the boundary: acting_user_auth swaps a non-admin UI session for the admitted-subject auth, the same identity a gateway session resolves with, so reachability, per-source fail-closed tool ceilings, rate limits, and billing attribution all bind through the admitted arms that already exist downstream. Admin sessions keep their operator view and caller-passed credentials are never widened. One swap point per route, no per-server principal picking, no parallel permission logic * fix(mcp): derive the connect page's detail view from the reachable server list The detail view held its own copy of the server object, so it outlived the list it came from. When a refetch dropped that server as unreachable, the open detail view kept rendering it and its Connect action still ran: the guard looked the server back up by id or name in the current list, found nothing, and fell through, because a missing target read as "nothing to block" rather than "no longer connectable" Store the selected server's id and derive the row from the list instead. A server the list no longer carries cannot be the detail view's subject, so the stale render, the stale tools query and the guard bypass stop being reachable states rather than being blocked one at a time. handleToggle now takes the server it is toggling, which deletes the lookup that could miss at all * refactor(mcp): one owner for the identity a dashboard session acts as Three call sites reloaded the admitted subject independently, and the management endpoint carried its own copy of the reload, the HTTPException swallow and the logging. admitted_user_context is now the only place that answers "what user identity does this dashboard session act as", and the connected-app reachability helper reads it, which also drops its dead empty-user_id branch That owner now carries the request's tracing span onto the admitted principal. _reload_admitted_user builds a fresh auth from the user row and has no span of its own, so swapping it in on the REST tool routes silently detached every downstream lookup and the tool-call logging from the request's trace Toolset scoping and the acting-as-user swap are mutually exclusive, so they now share one owner on the tools list route. The admitted subject resolves per grant source and a team source deliberately carries none of the caller's object_permission, so a toolset narrowing layered on top would evaporate on every team-granted server: the request would be admitted through the toolset grant and then served tools from servers the toolset never named. A request carrying a toolset name stays on the caller's own credential, exactly as it did before the swap * fix(mcp): commit every async connect-page write against the list as it stands Three continuations in the panel decided against state captured before their await and committed after it, so a reachability refetch landing in between could not be seen handleToggle validated the server at click time and then, once listMCPTools resolved, wrote its name into the selection whatever the list had since become; a server the refresh had dropped was selected anyway. It now re-asks connectableNow at the commit, and that predicate resolves the id against the current list, so absence fails closed instead of reading as nothing to block The load pipeline was worse, because its cancel flag was shared across runs: the successor's effect body reset it to false before the predecessor's fetch resolved, so a superseded load could still run setServers and put the dropped server back on the page outright. The flag is now a per-effect local that only that run's cleanup can clear, which is also what makes unmount stop the chunked tool-count loop again. The load passes its own liveness check down to the tool-count and oauth-status writes rather than having them consult a flag they share with every other run * fix(mcp): write the connect-page server list to its ref as it is committed connectableNow resolves a server id against serversRef, but that ref was a mirror kept in step by a passive effect, so it lagged the state it mirrored by however long React took to render and flush. A continuation resolving inside that window read the previous list: the commit-time reachability check would find a server the refetch had already dropped, call it connectable, and select it, which is the mismatch the check exists to prevent The lag was the whole defect, so the mirror is gone. commitServers writes the ref and the state together, at the one point the list is ever replaced, and the ref is now never older than the last committed list. Readers that want the newest answer (connectableNow, the oauth auto-select effect) get it; rendering still derives from state, so what is on screen is unchanged Pinned by a test that resolves the refetch and the in-flight Connect in the same tick, with no render flushed between them, which is the interleaving the earlier regression could not reach. The two prop mirrors are deliberately untouched: their staleness is inherent to appending to a parent-owned list from an async callback rather than caused by the mirror, and no reachability decision reads them --- litellm/models/mcp_server.py | 1 + .../mcp_server/rest_endpoints.py | 17 +- .../mcp_server/ui_session_utils.py | 72 ++++- .../mcp_management_endpoints.py | 23 ++ .../mcp_server/test_rest_endpoints.py | 171 ++++++++++++ .../mcp_server/test_ui_session_utils.py | 138 ++++++++++ .../test_mcp_management_endpoints.py | 252 ++++++++++++++++++ ui/litellm-dashboard/eslint-suppressions.json | 2 +- .../src/components/chat/MCPAppsPanel.test.tsx | 209 ++++++++++++++- .../src/components/chat/MCPAppsPanel.tsx | 234 +++++++++------- .../src/components/mcp_tools/types.tsx | 1 + .../src/components/networking.tsx | 7 +- 12 files changed, 1015 insertions(+), 112 deletions(-) diff --git a/litellm/models/mcp_server.py b/litellm/models/mcp_server.py index 23b26bd8e89..e428d20f99d 100644 --- a/litellm/models/mcp_server.py +++ b/litellm/models/mcp_server.py @@ -102,6 +102,7 @@ class LiteLLM_MCPServerTable(LiteLLMPydanticObjectBase): byok_description: List[str] = Field(default_factory=list) byok_api_key_help_url: Optional[str] = None has_user_credential: Optional[bool] = None + connected_app_reachable: bool | None = None source_url: Optional[str] = None timeout: Optional[float] = None max_concurrent_requests: Optional[int] = None diff --git a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py index 9b51513f4ac..b8604917825 100644 --- a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py +++ b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py @@ -26,6 +26,7 @@ from litellm.proxy._experimental.mcp_server.faults.list_outcomes import ( list_fault_http_status, ) from litellm.proxy._experimental.mcp_server.ui_session_utils import ( + acting_user_auth, build_effective_auth_contexts, ) from litellm.proxy._experimental.mcp_server.utils import ( @@ -667,13 +668,19 @@ if MCP_AVAILABLE: """Coerce an Optional[str] Query param to str|None, dropping unresolved FastAPI defaults.""" return value if isinstance(value, str) else None - async def _resolve_toolset_scope( + async def _resolve_acting_auth( toolset_name: str | None, user_api_key_dict: UserAPIKeyAuth, ) -> UserAPIKeyAuth: - """Resolve ``toolset_name`` to its scoped ``UserAPIKeyAuth``, or return unchanged.""" + """The one credential this tools request acts as. + + A toolset name narrows the caller's own credential to that toolset; otherwise a dashboard + session is swapped for its admitted subject. The two are mutually exclusive by construction, + which is why they share an owner: the admitted subject resolves per grant source and a team + source deliberately carries none of the caller's ``object_permission``, so a toolset + narrowing layered on top would evaporate on every team-granted server.""" if not toolset_name: - return user_api_key_dict + return await acting_user_auth(user_api_key_dict) from litellm.proxy.utils import get_prisma_client_or_throw @@ -731,6 +738,7 @@ if MCP_AVAILABLE: try: mcp_server_name = _as_query_str(mcp_server_name) toolset_name = _as_query_str(toolset_name) + user_api_key_dict = await _resolve_acting_auth(toolset_name, user_api_key_dict) # The full catalog (allowlist filter skipped) is admin-only so the # REST endpoint can't be used to enumerate deliberately-disabled tools. @@ -738,8 +746,6 @@ if MCP_AVAILABLE: include_disabled_tools and user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN ) - user_api_key_dict = await _resolve_toolset_scope(toolset_name, user_api_key_dict) - if server_id is None: server_id = mcp_server_name @@ -928,6 +934,7 @@ if MCP_AVAILABLE: ) try: + user_api_key_dict = await acting_user_auth(user_api_key_dict) data = await request.json() tool_name = data.get("name") diff --git a/litellm/proxy/_experimental/mcp_server/ui_session_utils.py b/litellm/proxy/_experimental/mcp_server/ui_session_utils.py index 1b37b884987..d1d28574988 100644 --- a/litellm/proxy/_experimental/mcp_server/ui_session_utils.py +++ b/litellm/proxy/_experimental/mcp_server/ui_session_utils.py @@ -1,9 +1,11 @@ -"""Helpers to resolve real team contexts for UI session tokens.""" +"""Helpers to resolve the identity a dashboard UI session token acts as.""" from __future__ import annotations from typing import List +from fastapi import HTTPException + from litellm._logging import verbose_logger from litellm.constants import UI_SESSION_TOKEN_TEAM_ID from litellm.proxy._types import UserAPIKeyAuth @@ -23,12 +25,19 @@ def clone_user_api_key_auth_with_team( return cloned_auth +def is_ui_session_credential(user_api_key_auth: UserAPIKeyAuth) -> bool: + """Whether the caller is the dashboard's SSO-minted session token acting as its user, + the only credential shape allowed to widen a request to the owning user's identity.""" + + return user_api_key_auth.team_id == UI_SESSION_TOKEN_TEAM_ID and bool(user_api_key_auth.user_id) + + async def resolve_ui_session_team_ids( user_api_key_auth: UserAPIKeyAuth, ) -> List[str]: """Resolve the real team ids backing a UI session token.""" - if user_api_key_auth.team_id != UI_SESSION_TOKEN_TEAM_ID or not user_api_key_auth.user_id: + if not is_ui_session_credential(user_api_key_auth): return [] from litellm.proxy.auth.auth_checks import get_user_object @@ -68,12 +77,63 @@ async def resolve_ui_session_team_ids( return resolved_team_ids +async def admitted_user_context(user_api_key_auth: UserAPIKeyAuth) -> UserAPIKeyAuth | None: + """THE owner of "resolve this dashboard session's user identity": the same admitted-subject auth a + gateway OAuth session for this user resolves with, carrying the user row's own object permission, + on this request's tracing span. None for any other credential (a caller-passed key is never + widened) and on reload failure, which every caller reads as "no user-level identity available".""" + + user_id = user_api_key_auth.user_id + if not is_ui_session_credential(user_api_key_auth) or user_id is None: + return None + from litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp import ( + MCPRequestHandler, + ) + + try: + admitted = await MCPRequestHandler._reload_admitted_user(user_id) + except HTTPException as e: + verbose_logger.warning(f"MCP dashboard session: admitted-subject reload failed for {user_id}: {e.detail}") + return None + return admitted.model_copy(update={"parent_otel_span": user_api_key_auth.parent_otel_span}) + + +async def acting_user_auth(user_api_key_auth: UserAPIKeyAuth) -> UserAPIKeyAuth: + """The principal acting-as-user MCP routes resolve permissions with. A non-admin dashboard + session acts as the admitted subject, the same identity a gateway session resolves with, so + server reachability, per-source tool ceilings, rate limits, and billing bind identically on + both surfaces. An admin session keeps its operator view and any caller-passed credential is + returned unchanged, never widened. + + Do not combine this with a narrowing that rewrites a single credential's ``object_permission`` + (toolset scope): the admitted subject resolves per grant source and a team source deliberately + carries none of the caller's own grants, so the narrowing would silently evaporate on every + team-granted server. A request carrying such a scope keeps the caller's own credential.""" + + if not is_ui_session_credential(user_api_key_auth): + return user_api_key_auth + from litellm.proxy.management_endpoints.common_utils import _user_has_admin_view + + if _user_has_admin_view(user_api_key_auth): + return user_api_key_auth + admitted = await admitted_user_context(user_api_key_auth) + return admitted if admitted is not None else user_api_key_auth + + async def build_effective_auth_contexts( user_api_key_auth: UserAPIKeyAuth, ) -> List[UserAPIKeyAuth]: - """Return auth contexts that reflect the actual teams for UI session tokens.""" + """Every auth context a management or listing surface must resolve a UI session token through: + one per real team backing the session, plus the session user's own admitted identity, so a grant + made directly to the user row is as visible to the dashboard as it is to a gateway session.""" resolved_team_ids = await resolve_ui_session_team_ids(user_api_key_auth) - if resolved_team_ids: - return [clone_user_api_key_auth_with_team(user_api_key_auth, team_id) for team_id in resolved_team_ids] - return [user_api_key_auth] + team_contexts = ( + [clone_user_api_key_auth_with_team(user_api_key_auth, team_id) for team_id in resolved_team_ids] + if resolved_team_ids + else [user_api_key_auth] + ) + admitted_context = await admitted_user_context(user_api_key_auth) + if admitted_context is None: + return team_contexts + return [*team_contexts, admitted_context] diff --git a/litellm/proxy/management_endpoints/mcp_management_endpoints.py b/litellm/proxy/management_endpoints/mcp_management_endpoints.py index 64cc13a5543..dcc4dc36d83 100644 --- a/litellm/proxy/management_endpoints/mcp_management_endpoints.py +++ b/litellm/proxy/management_endpoints/mcp_management_endpoints.py @@ -148,7 +148,9 @@ if MCP_AVAILABLE: global_mcp_server_manager, ) from litellm.proxy._experimental.mcp_server.ui_session_utils import ( + admitted_user_context, build_effective_auth_contexts, + is_ui_session_credential, ) from litellm.proxy._types import ( LiteLLM_MCPServerTable, @@ -939,6 +941,16 @@ if MCP_AVAILABLE: aggregated.setdefault(server.server_id, server) return list(aggregated.values()) + async def _connected_app_reachable_server_ids(user_api_key_dict: UserAPIKeyAuth) -> frozenset[str]: + """Server ids a connected app authorized by this dashboard user is served on the aggregate + MCP endpoint, resolved through the one owner of the admitted subject so the page and the + session cannot drift. Empty when that identity cannot be built, which is the true answer: + the same user cannot open a gateway session either.""" + admitted = await admitted_user_context(user_api_key_dict) + if admitted is None: + return frozenset() + return frozenset(await global_mcp_server_manager.get_allowed_mcp_servers(admitted)) + @router.get( "/server", description="Returns the mcp server list with associated teams", @@ -953,6 +965,12 @@ if MCP_AVAILABLE: "servers the team has access to plus globally available (allow_all_keys) servers. " "Used by the Create Key UI to show team-scoped MCP servers.", ), + connected_app_view: bool = Query( + False, + description="Annotate each returned server with connected_app_reachable: whether a " + "connected app authorized by the calling user (a gateway OAuth session) is served " + "this server on the aggregate MCP endpoint.", + ), ): """ Get all of the configured mcp servers for the user in the db with their associated teams @@ -1009,6 +1027,11 @@ if MCP_AVAILABLE: servers = await _resolve_accessible_mcp_servers(user_api_key_dict) redacted_mcp_servers = _redact_mcp_credentials_list(servers) + if connected_app_view is True and is_ui_session_credential(user_api_key_dict): + reachable_ids = await _connected_app_reachable_server_ids(user_api_key_dict) + for server in redacted_mcp_servers: + server.connected_app_reachable = server.server_id in reachable_ids + # augment the mcp servers with public status if litellm.public_mcp_servers is not None: for server in redacted_mcp_servers: diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_rest_endpoints.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_rest_endpoints.py index cec62f79e33..329b6d5c45d 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_rest_endpoints.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_rest_endpoints.py @@ -769,8 +769,179 @@ class TestListToolsRestAPI: assert captured["server"] is stub_server assert result["tools"] == ["tool-1"] assert result["error"] is None + + async def test_non_admin_ui_session_resolves_as_admitted_subject(self, monkeypatch): + """LIT-4861: a non-admin dashboard session must act as the admitted subject on this + route, so server reachability AND tool ceilings bind to the user's grants exactly as + they do for a gateway session, never to the bare session key.""" + from litellm.constants import UI_SESSION_TOKEN_TEAM_ID + + session_auth = UserAPIKeyAuth( + team_id=UI_SESSION_TOKEN_TEAM_ID, user_id="grant-user", user_role="internal_user" + ) + admitted_auth = UserAPIKeyAuth(user_id="grant-user", org_id="admitted-org") + + async def fake_reload(user_id): + assert user_id == "grant-user" + return admitted_auth + + monkeypatch.setattr( + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.MCPRequestHandler._reload_admitted_user", + fake_reload, + ) + + seen_server_resolution_auths = [] + + async def fake_get_allowed_mcp_servers(user_api_key_auth=None, **kwargs): + seen_server_resolution_auths.append(user_api_key_auth) + return ["server-1"] + + class StubServer: + alias = "server-1" + server_name = "server-1" + name = "stub" + allowed_tools = None + mcp_info = {"server_name": "stub"} + available_on_public_internet = True + + stub_server = StubServer() + captured = {} + + async def fake_get_tools( + server, + server_auth_header, + raw_headers=None, + user_api_key_auth=None, + extra_headers=None, + apply_tool_filters=True, + ): + captured["user_api_key_auth"] = user_api_key_auth + return ["tool-1"] + + monkeypatch.setattr( + rest_endpoints.global_mcp_server_manager, + "get_allowed_mcp_servers", + fake_get_allowed_mcp_servers, + raising=False, + ) + monkeypatch.setattr( + rest_endpoints.global_mcp_server_manager, + "get_mcp_server_by_id", + lambda server_id: stub_server if server_id == "server-1" else None, + raising=False, + ) + monkeypatch.setattr( + rest_endpoints, + "_get_tools_for_single_server", + fake_get_tools, + raising=False, + ) + + request = _build_request(path="/mcp-rest/tools/list", method="GET") + result = await rest_endpoints.list_tool_rest_api( + request, + server_id="server-1", + user_api_key_dict=session_auth, + ) + + resolved = [*seen_server_resolution_auths, captured["user_api_key_auth"]] + assert seen_server_resolution_auths + assert all(a.org_id == "admitted-org" and a.team_id is None for a in resolved) + assert result["tools"] == ["tool-1"] assert result["message"] == "Successfully retrieved tools" + async def test_toolset_scoped_request_keeps_the_caller_credential(self, monkeypatch): + """LIT-4861: the admitted subject resolves per grant source and a team source deliberately + carries none of the caller's own object_permission, so a toolset narrowing layered on top + would evaporate on every team-granted server. A toolset-scoped request therefore stays on + the caller's own credential, exactly as it did before the acting-as-user swap.""" + from litellm.constants import UI_SESSION_TOKEN_TEAM_ID + from litellm.proxy._types import LiteLLM_ObjectPermissionTable + + session_auth = UserAPIKeyAuth( + team_id=UI_SESSION_TOKEN_TEAM_ID, user_id="grant-user", user_role="internal_user" + ) + scoped_auth = UserAPIKeyAuth( + object_permission=LiteLLM_ObjectPermissionTable( + object_permission_id="toolset-scope", + mcp_servers=["toolset-server-1"], + ) + ) + reload_calls: list[str] = [] + scope_inputs: list[UserAPIKeyAuth] = [] + + async def record_reload(user_id): + reload_calls.append(user_id) + return UserAPIKeyAuth(user_id=user_id) + + class StubToolset: + toolset_id = "toolset-1" + + class StubServer: + alias = "toolset-server-1" + server_name = "toolset-server-1" + name = "toolset-server-1" + allowed_tools = None + mcp_info = {"server_name": "toolset-server-1"} + available_on_public_internet = True + + stub_server = StubServer() + + async def fake_get_toolset_by_name_cached(prisma_client, toolset_name): + return StubToolset() + + async def fake_apply_toolset_scope(user_api_key_auth, toolset_id): + scope_inputs.append(user_api_key_auth) + return scoped_auth + + async def fake_get_allowed_mcp_servers(user_api_key_auth=None, **kwargs): + assert user_api_key_auth is scoped_auth + return ["toolset-server-1"] + + async def fake_get_tools(server, server_auth_header, *args, **kwargs): + return ["toolset-tool-1"] + + monkeypatch.setattr( + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.MCPRequestHandler._reload_admitted_user", + record_reload, + ) + monkeypatch.setattr( + "litellm.proxy.utils.get_prisma_client_or_throw", + lambda *args, **kwargs: MagicMock(), + ) + monkeypatch.setattr( + rest_endpoints.global_mcp_server_manager, + "get_toolset_by_name_cached", + fake_get_toolset_by_name_cached, + raising=False, + ) + monkeypatch.setattr(rest_endpoints, "_apply_toolset_scope", fake_apply_toolset_scope, raising=False) + monkeypatch.setattr( + rest_endpoints.global_mcp_server_manager, + "get_allowed_mcp_servers", + fake_get_allowed_mcp_servers, + raising=False, + ) + monkeypatch.setattr( + rest_endpoints.global_mcp_server_manager, + "get_mcp_server_by_id", + lambda server_id: stub_server if server_id == "toolset-server-1" else None, + raising=False, + ) + monkeypatch.setattr(rest_endpoints, "_get_tools_for_single_server", fake_get_tools, raising=False) + + request = _build_request(path="/mcp-rest/tools/list", method="GET") + result = await rest_endpoints.list_tool_rest_api( + request, + server_id=None, + toolset_name="research_tools", + user_api_key_dict=session_auth, + ) + + assert result["tools"] == ["toolset-tool-1"] + assert scope_inputs == [session_auth] + assert reload_calls == [] + async def test_include_disabled_tools_is_admin_only(self, monkeypatch): """include_disabled_tools skips the allowlist filter only for PROXY_ADMIN; a non-admin passing it stays filtered so the REST endpoint can't be used diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_ui_session_utils.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_ui_session_utils.py index 52120207f76..cd4cba51908 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_ui_session_utils.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_ui_session_utils.py @@ -3,6 +3,7 @@ from types import SimpleNamespace from unittest.mock import AsyncMock import pytest +from fastapi import HTTPException from litellm.constants import UI_SESSION_TOKEN_TEAM_ID from litellm.proxy._types import UserAPIKeyAuth @@ -120,3 +121,140 @@ async def test_build_effective_auth_contexts_handles_unpicklable_parent_span( assert contexts[0].team_id == "team-span" assert contexts[0].parent_otel_span is parent_span + + +@pytest.mark.asyncio +async def test_build_effective_auth_contexts_appends_admitted_user_context(monkeypatch): + """LIT-4861: the dashboard session must resolve with the user's admitted identity so the + page list and every per-server action endpoint see user-level grants the same way the + gateway session does.""" + user_auth = UserAPIKeyAuth(team_id=UI_SESSION_TOKEN_TEAM_ID, user_id="user-42") + admitted_auth = UserAPIKeyAuth(user_id="user-42") + + monkeypatch.setattr( + "litellm.proxy._experimental.mcp_server.ui_session_utils.resolve_ui_session_team_ids", + AsyncMock(return_value=["team-one"]), + ) + reload_mock = AsyncMock(return_value=admitted_auth) + monkeypatch.setattr( + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.MCPRequestHandler._reload_admitted_user", + reload_mock, + ) + + contexts = await build_effective_auth_contexts(user_auth) + + assert contexts[-1].user_id == "user-42" and contexts[-1].team_id is None + assert [ctx.team_id for ctx in contexts[:-1]] == ["team-one"] + reload_mock.assert_awaited_once_with("user-42") + + +@pytest.mark.asyncio +async def test_build_effective_auth_contexts_never_widens_caller_passed_keys(monkeypatch): + normal_user = UserAPIKeyAuth(team_id="regular-team", user_id="user-1") + reload_mock = AsyncMock() + monkeypatch.setattr( + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.MCPRequestHandler._reload_admitted_user", + reload_mock, + ) + + contexts = await build_effective_auth_contexts(normal_user) + + assert contexts == [normal_user] + reload_mock.assert_not_awaited() + + +@pytest.mark.asyncio +async def test_build_effective_auth_contexts_survives_admitted_reload_failure(monkeypatch): + user_auth = UserAPIKeyAuth(team_id=UI_SESSION_TOKEN_TEAM_ID, user_id="user-9") + + monkeypatch.setattr( + "litellm.proxy._experimental.mcp_server.ui_session_utils.resolve_ui_session_team_ids", + AsyncMock(return_value=["team-a"]), + ) + monkeypatch.setattr( + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.MCPRequestHandler._reload_admitted_user", + AsyncMock(side_effect=HTTPException(status_code=503, detail="db down")), + ) + + contexts = await build_effective_auth_contexts(user_auth) + + assert [ctx.team_id for ctx in contexts] == ["team-a"] + + +@pytest.mark.asyncio +async def test_acting_user_auth_returns_admitted_subject_for_non_admin_sessions(monkeypatch): + """LIT-4861: acting-as-user MCP routes must resolve a non-admin dashboard session as the + admitted subject so tool ceilings, reachability, and limits bind exactly as on /mcp.""" + from litellm.proxy._experimental.mcp_server.ui_session_utils import acting_user_auth + + user_auth = UserAPIKeyAuth(team_id=UI_SESSION_TOKEN_TEAM_ID, user_id="user-42", user_role="internal_user") + admitted_auth = UserAPIKeyAuth(user_id="user-42") + reload_mock = AsyncMock(return_value=admitted_auth) + monkeypatch.setattr( + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.MCPRequestHandler._reload_admitted_user", + reload_mock, + ) + + result = await acting_user_auth(user_auth) + + assert result.user_id == "user-42" and result.team_id is None + reload_mock.assert_awaited_once_with("user-42") + + +@pytest.mark.asyncio +async def test_acting_user_auth_keeps_admin_sessions_and_passed_keys_unchanged(monkeypatch): + from litellm.proxy._experimental.mcp_server.ui_session_utils import acting_user_auth + + reload_mock = AsyncMock() + monkeypatch.setattr( + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.MCPRequestHandler._reload_admitted_user", + reload_mock, + ) + + admin_session = UserAPIKeyAuth(team_id=UI_SESSION_TOKEN_TEAM_ID, user_id="admin-1", user_role="proxy_admin") + assert await acting_user_auth(admin_session) is admin_session + + passed_key = UserAPIKeyAuth(team_id="regular-team", user_id="user-1", user_role="internal_user") + assert await acting_user_auth(passed_key) is passed_key + + reload_mock.assert_not_awaited() + + +@pytest.mark.asyncio +async def test_acting_user_auth_falls_back_to_session_auth_on_reload_failure(monkeypatch): + from litellm.proxy._experimental.mcp_server.ui_session_utils import acting_user_auth + + user_auth = UserAPIKeyAuth(team_id=UI_SESSION_TOKEN_TEAM_ID, user_id="user-9", user_role="internal_user") + monkeypatch.setattr( + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.MCPRequestHandler._reload_admitted_user", + AsyncMock(side_effect=HTTPException(status_code=503, detail="db down")), + ) + + assert await acting_user_auth(user_auth) is user_auth + + +@pytest.mark.asyncio +async def test_admitted_user_context_carries_the_request_span(monkeypatch): + """Swapping the principal must not drop the request: the admitted subject is rebuilt from the + user row and carries no span of its own, so every consumer would otherwise lose trace linkage + for the resolution and logging it drives.""" + from litellm.proxy._experimental.mcp_server.ui_session_utils import acting_user_auth + + class DummySpan: + def __init__(self) -> None: + self._lock = threading.RLock() + + parent_span = DummySpan() + user_auth = UserAPIKeyAuth( + team_id=UI_SESSION_TOKEN_TEAM_ID, + user_id="user-42", + user_role="internal_user", + parent_otel_span=parent_span, + ) + monkeypatch.setattr( + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.MCPRequestHandler._reload_admitted_user", + AsyncMock(return_value=UserAPIKeyAuth(user_id="user-42")), + ) + + assert (await acting_user_auth(user_auth)).parent_otel_span is parent_span + assert (await build_effective_auth_contexts(user_auth))[-1].parent_otel_span is parent_span diff --git a/tests/test_litellm/proxy/management_endpoints/test_mcp_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_mcp_management_endpoints.py index 53dda8f6648..bf119c4fb2f 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_mcp_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_mcp_management_endpoints.py @@ -6138,3 +6138,255 @@ def test_bundled_openapi_registry_parses_and_entries_are_well_formed(): ) for tool in entry.get("key_tools", []): assert tool.get("name") and tool.get("description"), f"{entry['name']}: malformed key_tool" + + +class TestConnectedAppViewAnnotation: + """LIT-4861: GET /v1/mcp/server?connected_app_view=true must annotate each server with + whether the caller's gateway OAuth sessions (connected apps) are served it on /mcp. + The view is honored only for the dashboard's UI session credential; a caller-passed + virtual key must never be widened to its owning user's identity.""" + + def _ui_session_auth(self, user_role: LitellmUserRoles = LitellmUserRoles.PROXY_ADMIN) -> UserAPIKeyAuth: + from litellm.constants import UI_SESSION_TOKEN_TEAM_ID + + return generate_mock_user_api_key_auth(user_role=user_role, team_id=UI_SESSION_TOKEN_TEAM_ID) + + def _mock_manager(self, servers, reachable_ids): + mock_manager = MagicMock() + mock_manager.get_all_allowed_mcp_servers = AsyncMock(return_value=servers) + mock_manager.get_all_mcp_servers_unfiltered = AsyncMock(return_value=servers) + mock_manager.get_allowed_mcp_servers = AsyncMock(return_value=reachable_ids) + return mock_manager + + def _servers(self): + return [ + generate_mock_mcp_server_db_record(server_id="server-1", alias="Granted"), + generate_mock_mcp_server_db_record(server_id="server-2", alias="Ungranted"), + ] + + @pytest.mark.asyncio + async def test_connected_app_view_annotates_reachability_via_admitted_resolver(self): + caller_auth = self._ui_session_auth() + admitted_auth = UserAPIKeyAuth(user_id="test_user_id") + admitted_auth.mcp_admitted_user_subject = True + mock_manager = self._mock_manager(self._servers(), ["server-1"]) + reload_mock = AsyncMock(return_value=admitted_auth) + + with ( + patch( + "litellm.proxy.management_endpoints.mcp_management_endpoints.global_mcp_server_manager", + mock_manager, + ), + patch( + "litellm.proxy.management_endpoints.mcp_management_endpoints.build_effective_auth_contexts", + AsyncMock(return_value=[caller_auth]), + ), + patch( + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.MCPRequestHandler._reload_admitted_user", + reload_mock, + ), + ): + from litellm.proxy.management_endpoints.mcp_management_endpoints import ( + fetch_all_mcp_servers, + ) + + result = await fetch_all_mcp_servers(user_api_key_dict=caller_auth, connected_app_view=True) + + flags = {server.server_id: server.connected_app_reachable for server in result} + assert flags == {"server-1": True, "server-2": False} + reload_mock.assert_awaited_once_with("test_user_id") + mock_manager.get_allowed_mcp_servers.assert_awaited_once_with(admitted_auth) + + @pytest.mark.asyncio + async def test_connected_app_view_stamps_view_all_list_and_survives_non_admin_sanitizer(self): + """view_all preempts the manager's admin shortcut with a second whole-registry + shortcut; the annotation must still land, and must survive the non-admin sanitizer.""" + caller_auth = self._ui_session_auth(user_role=LitellmUserRoles.INTERNAL_USER) + mock_manager = self._mock_manager(self._servers(), ["server-2"]) + + with ( + patch( + "litellm.proxy.management_endpoints.mcp_management_endpoints._get_user_mcp_management_mode", + return_value="view_all", + ), + patch( + "litellm.proxy.management_endpoints.mcp_management_endpoints.global_mcp_server_manager", + mock_manager, + ), + patch( + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.MCPRequestHandler._reload_admitted_user", + AsyncMock(return_value=UserAPIKeyAuth(user_id="test_user_id")), + ), + ): + from litellm.proxy.management_endpoints.mcp_management_endpoints import ( + fetch_all_mcp_servers, + ) + + result = await fetch_all_mcp_servers(user_api_key_dict=caller_auth, connected_app_view=True) + + mock_manager.get_all_mcp_servers_unfiltered.assert_awaited_once() + flags = {server.server_id: server.connected_app_reachable for server in result} + assert flags == {"server-1": False, "server-2": True} + + @pytest.mark.asyncio + async def test_connected_app_view_lists_user_granted_servers_via_admitted_context(self): + """A server granted only through the user's own object permission must be listed and + flagged reachable: the REAL build_effective_auth_contexts appends the admitted-user + context, so the page and every action endpoint resolve it identically.""" + caller_auth = self._ui_session_auth(user_role=LitellmUserRoles.INTERNAL_USER) + admitted_auth = UserAPIKeyAuth(user_id="test_user_id", org_id="admitted-org") + listed_row = generate_mock_mcp_server_db_record(server_id="server-1", alias="TeamGranted") + user_granted_row = generate_mock_mcp_server_db_record(server_id="server-2", alias="UserGranted") + + async def per_context_servers(user_api_key_auth=None): + if user_api_key_auth is not None and user_api_key_auth.org_id == "admitted-org": + return [listed_row, user_granted_row] + return [listed_row] + + mock_manager = MagicMock() + mock_manager.get_all_allowed_mcp_servers = AsyncMock(side_effect=per_context_servers) + mock_manager.get_allowed_mcp_servers = AsyncMock(return_value=["server-1", "server-2"]) + + with ( + patch( + "litellm.proxy.management_endpoints.mcp_management_endpoints.global_mcp_server_manager", + mock_manager, + ), + patch( + "litellm.proxy._experimental.mcp_server.ui_session_utils.resolve_ui_session_team_ids", + AsyncMock(return_value=[]), + ), + patch( + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.MCPRequestHandler._reload_admitted_user", + AsyncMock(return_value=admitted_auth), + ), + ): + from litellm.proxy.management_endpoints.mcp_management_endpoints import ( + fetch_all_mcp_servers, + ) + + result = await fetch_all_mcp_servers(user_api_key_dict=caller_auth, connected_app_view=True) + + flags = {server.server_id: server.connected_app_reachable for server in result} + assert flags == {"server-1": True, "server-2": True} + + @pytest.mark.asyncio + async def test_connected_app_view_fails_closed_when_admitted_reload_fails(self): + caller_auth = self._ui_session_auth() + mock_manager = self._mock_manager(self._servers(), ["server-1"]) + + with ( + patch( + "litellm.proxy.management_endpoints.mcp_management_endpoints.global_mcp_server_manager", + mock_manager, + ), + patch( + "litellm.proxy.management_endpoints.mcp_management_endpoints.build_effective_auth_contexts", + AsyncMock(return_value=[caller_auth]), + ), + patch( + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.MCPRequestHandler._reload_admitted_user", + AsyncMock(side_effect=HTTPException(status_code=401, detail="expired")), + ), + ): + from litellm.proxy.management_endpoints.mcp_management_endpoints import ( + fetch_all_mcp_servers, + ) + + result = await fetch_all_mcp_servers(user_api_key_dict=caller_auth, connected_app_view=True) + + assert all(server.connected_app_reachable is False for server in result) + + @pytest.mark.asyncio + async def test_connected_app_view_off_leaves_field_unset(self): + caller_auth = generate_mock_user_api_key_auth() + mock_manager = self._mock_manager(self._servers(), ["server-1"]) + reload_mock = AsyncMock(return_value=UserAPIKeyAuth(user_id="test_user_id")) + + with ( + patch( + "litellm.proxy.management_endpoints.mcp_management_endpoints.global_mcp_server_manager", + mock_manager, + ), + patch( + "litellm.proxy.management_endpoints.mcp_management_endpoints.build_effective_auth_contexts", + AsyncMock(return_value=[caller_auth]), + ), + patch( + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.MCPRequestHandler._reload_admitted_user", + reload_mock, + ), + ): + from litellm.proxy.management_endpoints.mcp_management_endpoints import ( + fetch_all_mcp_servers, + ) + + result = await fetch_all_mcp_servers(user_api_key_dict=caller_auth) + + assert all(server.connected_app_reachable is None for server in result) + reload_mock.assert_not_awaited() + + @pytest.mark.asyncio + async def test_connected_app_view_userless_ui_credential_leaves_field_unset(self): + from litellm.constants import UI_SESSION_TOKEN_TEAM_ID + + caller_auth = UserAPIKeyAuth( + user_role=LitellmUserRoles.PROXY_ADMIN, api_key="test_api_key", team_id=UI_SESSION_TOKEN_TEAM_ID + ) + caller_auth.user_id = None + mock_manager = self._mock_manager(self._servers(), ["server-1"]) + reload_mock = AsyncMock(return_value=UserAPIKeyAuth(user_id="test_user_id")) + + with ( + patch( + "litellm.proxy.management_endpoints.mcp_management_endpoints.global_mcp_server_manager", + mock_manager, + ), + patch( + "litellm.proxy.management_endpoints.mcp_management_endpoints.build_effective_auth_contexts", + AsyncMock(return_value=[caller_auth]), + ), + patch( + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.MCPRequestHandler._reload_admitted_user", + reload_mock, + ), + ): + from litellm.proxy.management_endpoints.mcp_management_endpoints import ( + fetch_all_mcp_servers, + ) + + result = await fetch_all_mcp_servers(user_api_key_dict=caller_auth, connected_app_view=True) + + assert all(server.connected_app_reachable is None for server in result) + reload_mock.assert_not_awaited() + + @pytest.mark.asyncio + async def test_connected_app_view_ignored_for_caller_passed_virtual_keys(self): + """A virtual key the user passes themselves is never widened to the owning user's + identity: the view param is a no-op and the admitted resolver is never consulted.""" + caller_auth = generate_mock_user_api_key_auth(team_id="some-real-team") + mock_manager = self._mock_manager(self._servers(), ["server-1"]) + reload_mock = AsyncMock(return_value=UserAPIKeyAuth(user_id="test_user_id")) + + with ( + patch( + "litellm.proxy.management_endpoints.mcp_management_endpoints.global_mcp_server_manager", + mock_manager, + ), + patch( + "litellm.proxy.management_endpoints.mcp_management_endpoints.build_effective_auth_contexts", + AsyncMock(return_value=[caller_auth]), + ), + patch( + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.MCPRequestHandler._reload_admitted_user", + reload_mock, + ), + ): + from litellm.proxy.management_endpoints.mcp_management_endpoints import ( + fetch_all_mcp_servers, + ) + + result = await fetch_all_mcp_servers(user_api_key_dict=caller_auth, connected_app_view=True) + + assert all(server.connected_app_reachable is None for server in result) + reload_mock.assert_not_awaited() diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index 6819b2851f5..4cb3ebe7e16 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -2729,7 +2729,7 @@ "count": 1 }, "no-nested-ternary": { - "count": 6 + "count": 4 } }, "src/components/chat/MCPConnectPicker.tsx": { diff --git a/ui/litellm-dashboard/src/components/chat/MCPAppsPanel.test.tsx b/ui/litellm-dashboard/src/components/chat/MCPAppsPanel.test.tsx index 656ef157363..5fc3e75195b 100644 --- a/ui/litellm-dashboard/src/components/chat/MCPAppsPanel.test.tsx +++ b/ui/litellm-dashboard/src/components/chat/MCPAppsPanel.test.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { render, screen, fireEvent } from "@testing-library/react"; -import { describe, it, expect, vi, afterEach } from "vitest"; +import { render, screen, fireEvent, waitFor, act } from "@testing-library/react"; +import { describe, it, expect, vi, afterEach, beforeEach } from "vitest"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import MCPAppsPanel from "./MCPAppsPanel"; import { fetchMCPServers, listMCPTools } from "../networking"; @@ -86,3 +86,208 @@ describe("MCPAppsPanel logos", () => { expect(screen.getByAltText("local_logo logo").getAttribute("src")).toBe("/litellm/ui/assets/logos/github.svg"); }); }); + +const connectServers = [ + { + server_id: "s-reach", + server_name: "reachable_srv", + auth_type: "none", + connected_app_reachable: true, + }, + { + server_id: "s-unreach", + server_name: "unreachable_srv", + auth_type: "none", + connected_app_reachable: false, + }, +] as MCPServer[]; + +const renderConnectPanel = (connectMode: boolean, selectedServers: string[] = []) => + render( + + + , + ); + +describe("MCPAppsPanel connected-app reachability (LIT-4861)", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("requests the connected-app view and hides unreachable servers in connect mode", async () => { + vi.mocked(fetchMCPServers).mockResolvedValue(connectServers); + vi.mocked(listMCPTools).mockResolvedValue({ tools: [] }); + + renderConnectPanel(true, ["reachable_srv", "unreachable_srv"]); + + expect(await screen.findByText("reachable_srv")).toBeInTheDocument(); + expect(vi.mocked(fetchMCPServers)).toHaveBeenCalledWith("tok", undefined, true); + expect(screen.queryByText("unreachable_srv")).not.toBeInTheDocument(); + expect(screen.getByText("Connected (1)")).toBeInTheDocument(); + const toolCountFetchedIds = vi.mocked(listMCPTools).mock.calls.map((call) => call[1]); + expect(toolCountFetchedIds).toContain("s-reach"); + expect(toolCountFetchedIds).not.toContain("s-unreach"); + }); + + it("blocks connecting an unsupported server from the detail view in connect mode", async () => { + const detailServers = [ + ...connectServers, + { + server_id: "s-unsup", + server_name: "unsupported_srv", + auth_type: "oauth2_token_exchange", + connected_app_reachable: true, + }, + ] as MCPServer[]; + vi.mocked(fetchMCPServers).mockResolvedValue(detailServers); + vi.mocked(listMCPTools).mockResolvedValue({ tools: [] }); + + renderConnectPanel(true); + + fireEvent.click(await screen.findByText("unsupported_srv")); + expect(await screen.findByRole("heading", { name: "unsupported_srv" })).toBeInTheDocument(); + expect(screen.queryByRole("button", { name: "Connect" })).not.toBeInTheDocument(); + expect(screen.getByText("Not supported on this connection")).toBeInTheDocument(); + }); + + it("keeps the detail-view Connect action outside connect mode", async () => { + vi.mocked(fetchMCPServers).mockResolvedValue(connectServers); + vi.mocked(listMCPTools).mockResolvedValue({ tools: [] }); + + renderConnectPanel(false); + + fireEvent.click(await screen.findByText("unreachable_srv")); + expect(await screen.findByRole("heading", { name: "unreachable_srv" })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Connect" })).toBeInTheDocument(); + }); + + it("ignores the flag and skips no server outside connect mode", async () => { + vi.mocked(fetchMCPServers).mockResolvedValue(connectServers); + vi.mocked(listMCPTools).mockResolvedValue({ tools: [] }); + + renderConnectPanel(false, ["reachable_srv", "unreachable_srv"]); + + expect(await screen.findByText("unreachable_srv")).toBeInTheDocument(); + expect(vi.mocked(fetchMCPServers)).toHaveBeenCalledWith("tok", undefined, false); + expect(screen.queryByText("Not available to connected apps")).not.toBeInTheDocument(); + expect(screen.getByText("Connected (2)")).toBeInTheDocument(); + const toolCountFetchedIds = vi.mocked(listMCPTools).mock.calls.map((call) => call[1]); + expect(toolCountFetchedIds).toContain("s-unreach"); + }); + + const revocable = (reachable: boolean) => + [ + { server_id: "s-reach", server_name: "reachable_srv", auth_type: "none", connected_app_reachable: true }, + { server_id: "s-drop", server_name: "revoked_srv", auth_type: "none", connected_app_reachable: reachable }, + ] as MCPServer[]; + + const ConnectPanel = ({ + token, + onChange, + client, + }: { + token: string; + onChange: (servers: string[]) => void; + client: QueryClient; + }) => ( + + + + ); + + const newClient = () => new QueryClient({ defaultOptions: { queries: { retry: false } } }); + + it("drops an open detail view when a refetch removes that server from the reachable set", async () => { + vi.mocked(fetchMCPServers).mockResolvedValueOnce(revocable(true)).mockResolvedValueOnce(revocable(false)); + vi.mocked(listMCPTools).mockResolvedValue({ tools: [] }); + + const client = newClient(); + const { rerender } = render(); + + fireEvent.click(await screen.findByText("revoked_srv")); + expect(await screen.findByRole("heading", { name: "revoked_srv" })).toBeInTheDocument(); + + rerender(); + + await waitFor(() => expect(screen.queryByRole("heading", { name: "revoked_srv" })).not.toBeInTheDocument()); + expect(screen.queryByRole("button", { name: "Connect" })).not.toBeInTheDocument(); + expect(screen.queryByText("revoked_srv")).not.toBeInTheDocument(); + expect(screen.getByText("reachable_srv")).toBeInTheDocument(); + }); + + it("does not select a server whose Connect finishes after a refetch removed it", async () => { + vi.mocked(fetchMCPServers).mockResolvedValueOnce(revocable(true)).mockResolvedValueOnce(revocable(false)); + vi.mocked(listMCPTools).mockResolvedValue({ tools: [] }); + + const onChange = vi.fn(); + const client = newClient(); + const { rerender } = render(); + + fireEvent.click(await screen.findByText("revoked_srv")); + expect(await screen.findByRole("heading", { name: "revoked_srv" })).toBeInTheDocument(); + + let finishConnect: (result: { tools: never[] }) => void = () => {}; + vi.mocked(listMCPTools).mockImplementationOnce(() => new Promise((resolve) => (finishConnect = resolve))); + fireEvent.click(screen.getByRole("button", { name: "Connect" })); + + rerender(); + await waitFor(() => expect(screen.queryByRole("heading", { name: "revoked_srv" })).not.toBeInTheDocument()); + + await act(async () => { + finishConnect({ tools: [] }); + }); + + expect(onChange).not.toHaveBeenCalled(); + expect(screen.queryByText("revoked_srv")).not.toBeInTheDocument(); + expect(screen.getByText("Connected", { exact: false }).textContent).toBe("Connected"); + }); + + it("does not select a server when Connect resolves in the same tick the refetch drops it", async () => { + let finishRefetch: (servers: MCPServer[]) => void = () => {}; + vi.mocked(fetchMCPServers) + .mockResolvedValueOnce(revocable(true)) + .mockImplementationOnce(() => new Promise((resolve) => (finishRefetch = resolve))); + vi.mocked(listMCPTools).mockResolvedValue({ tools: [] }); + + const onChange = vi.fn(); + const client = newClient(); + const { rerender } = render(); + + fireEvent.click(await screen.findByText("revoked_srv")); + expect(await screen.findByRole("heading", { name: "revoked_srv" })).toBeInTheDocument(); + + let finishConnect: (result: { tools: never[] }) => void = () => {}; + vi.mocked(listMCPTools).mockImplementationOnce(() => new Promise((resolve) => (finishConnect = resolve))); + fireEvent.click(screen.getByRole("button", { name: "Connect" })); + + rerender(); + + await act(async () => { + finishRefetch(revocable(false)); + finishConnect({ tools: [] }); + }); + + expect(onChange).not.toHaveBeenCalled(); + expect(screen.queryByText("revoked_srv")).not.toBeInTheDocument(); + }); + + it("does not let a superseded list load overwrite the current reachable set", async () => { + let finishStaleLoad: (servers: MCPServer[]) => void = () => {}; + vi.mocked(fetchMCPServers) + .mockImplementationOnce(() => new Promise((resolve) => (finishStaleLoad = resolve))) + .mockResolvedValueOnce(revocable(false)); + vi.mocked(listMCPTools).mockResolvedValue({ tools: [] }); + + const client = newClient(); + const { rerender } = render(); + rerender(); + + expect(await screen.findByText("reachable_srv")).toBeInTheDocument(); + + await act(async () => { + finishStaleLoad(revocable(true)); + }); + + expect(screen.queryByText("revoked_srv")).not.toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/chat/MCPAppsPanel.tsx b/ui/litellm-dashboard/src/components/chat/MCPAppsPanel.tsx index 329e4ec99fa..7dbfc058a77 100644 --- a/ui/litellm-dashboard/src/components/chat/MCPAppsPanel.tsx +++ b/ui/litellm-dashboard/src/components/chat/MCPAppsPanel.tsx @@ -103,16 +103,17 @@ const MCPAppsPanel: React.FC = ({ accessToken, selectedServers, onChange, const [query, setQuery] = useState(""); const [activeTab, setActiveTab] = useState("all"); const [togglingOn, setTogglingOn] = useState>(new Set()); - const [detailServer, setDetailServer] = useState(null); + const [detailServerId, setDetailServerId] = useState(null); const [toolCounts, setToolCounts] = useState>({}); const [loadingCounts, setLoadingCounts] = useState(false); const [oauthConnected, setOauthConnected] = useState>(new Set()); const [oauthChecking, setOauthChecking] = useState>(new Set()); const serversRef = useRef([]); - useEffect(() => { - serversRef.current = servers; - }, [servers]); + const commitServers = useCallback((next: MCPServer[]) => { + serversRef.current = next; + setServers(next); + }, []); const selectedServersRef = useRef(selectedServers); useEffect(() => { selectedServersRef.current = selectedServers; @@ -124,13 +125,30 @@ const MCPAppsPanel: React.FC = ({ accessToken, selectedServers, onChange, const nameOf = (s: MCPServer) => s.server_name ?? s.alias ?? s.server_id; - const fetchLoadCancelledRef = useRef(false); + const detailServer = servers.find((s) => s.server_id === detailServerId); + + const connectUnavailabilityLabel = useCallback( + (s: MCPServer): string | null => { + if (!connectMode) return null; + if (isUnsupportedOnGatewayConnect(s.auth_type)) return "Not supported on this connection"; + return null; + }, + [connectMode], + ); + + const connectableNow = useCallback( + (serverId: string): MCPServer | undefined => { + const current = serversRef.current.find((s) => s.server_id === serverId); + return current !== undefined && connectUnavailabilityLabel(current) === null ? current : undefined; + }, + [connectUnavailabilityLabel], + ); const fetchToolCount = useCallback( - async (server: MCPServer) => { + async (server: MCPServer, isCurrentLoad: () => boolean) => { try { const toolsData = await listMCPTools(accessToken, server.server_id); - if (fetchLoadCancelledRef.current) return; + if (!isCurrentLoad()) return; const tools: MCPTool[] = Array.isArray(toolsData?.tools) ? toolsData.tools : []; setToolCounts((prev) => ({ ...prev, [nameOf(server)]: tools.length })); } catch { @@ -141,17 +159,17 @@ const MCPAppsPanel: React.FC = ({ accessToken, selectedServers, onChange, ); const checkOauthCredential = useCallback( - async (server: MCPServer) => { + async (server: MCPServer, isCurrentLoad: () => boolean) => { try { const status = await getMCPOAuthUserCredentialStatus(accessToken, server.server_id); - if (fetchLoadCancelledRef.current) return; + if (!isCurrentLoad()) return; if (status.has_credential && !status.is_expired) { setOauthConnected((prev) => new Set(prev).add(server.server_id)); } } catch { // ignore } finally { - if (!fetchLoadCancelledRef.current) { + if (isCurrentLoad()) { setOauthChecking((prev) => { const next = new Set(prev); next.delete(server.server_id); @@ -164,70 +182,77 @@ const MCPAppsPanel: React.FC = ({ accessToken, selectedServers, onChange, ); useEffect(() => { - fetchLoadCancelledRef.current = false; + let current = true; + const isCurrentLoad = () => current; - fetchMCPServers(accessToken) + fetchMCPServers(accessToken, undefined, connectMode) .then(async (serverData) => { - if (fetchLoadCancelledRef.current) return; + if (!isCurrentLoad()) return; const list: MCPServer[] = Array.isArray(serverData) ? serverData : serverData?.data ?? []; - const oauthServers = list.filter((s) => s.auth_type === AUTH_TYPE.OAUTH2); - setServers(list); + const reachable = connectMode ? list.filter((s) => s.connected_app_reachable !== false) : list; + const oauthServers = reachable.filter((s) => s.auth_type === AUTH_TYPE.OAUTH2); + commitServers(reachable); setOauthChecking(new Set(oauthServers.map((s) => s.server_id))); setLoading(false); - oauthServers.forEach((s) => checkOauthCredential(s)); + oauthServers.forEach((s) => checkOauthCredential(s, isCurrentLoad)); setLoadingCounts(true); - const chunks = Array.from({ length: Math.ceil(list.length / TOOLS_FETCH_CONCURRENCY) }, (_, i) => - list.slice(i * TOOLS_FETCH_CONCURRENCY, (i + 1) * TOOLS_FETCH_CONCURRENCY), + const chunks = Array.from({ length: Math.ceil(reachable.length / TOOLS_FETCH_CONCURRENCY) }, (_, i) => + reachable.slice(i * TOOLS_FETCH_CONCURRENCY, (i + 1) * TOOLS_FETCH_CONCURRENCY), ); for (const chunk of chunks) { - if (fetchLoadCancelledRef.current) return; - await Promise.allSettled(chunk.map((s) => fetchToolCount(s))); + if (!isCurrentLoad()) return; + await Promise.allSettled(chunk.map((s) => fetchToolCount(s, isCurrentLoad))); } - if (!fetchLoadCancelledRef.current) setLoadingCounts(false); + if (isCurrentLoad()) setLoadingCounts(false); }) .catch(() => { - if (!fetchLoadCancelledRef.current) { - setServers([]); + if (isCurrentLoad()) { + commitServers([]); setLoading(false); } }); return () => { - fetchLoadCancelledRef.current = true; + current = false; }; - }, [accessToken, fetchToolCount, checkOauthCredential]); + }, [accessToken, connectMode, commitServers, fetchToolCount, checkOauthCredential]); useEffect(() => { if (oauthConnected.size === 0) return; const namesToAdd = serversRef.current - .filter((s) => oauthConnected.has(s.server_id) && !selectedServersRef.current.includes(nameOf(s))) + .filter( + (s) => + oauthConnected.has(s.server_id) && + !selectedServersRef.current.includes(nameOf(s)) && + connectUnavailabilityLabel(s) === null, + ) .map(nameOf); if (namesToAdd.length > 0) { onChangeRef.current([...selectedServersRef.current, ...namesToAdd]); } - }, [oauthConnected]); + }, [oauthConnected, connectUnavailabilityLabel]); - const handleToggle = async (serverName: string, checked: boolean, serverId?: string) => { + const handleToggle = async (server: MCPServer, checked: boolean) => { + const serverName = nameOf(server); if (!checked) { onChange(selectedServers.filter((s) => s !== serverName)); - if (serverId) { - setOauthConnected((prev) => { - const next = new Set(prev); - next.delete(serverId); - return next; - }); - } + setOauthConnected((prev) => { + const next = new Set(prev); + next.delete(server.server_id); + return next; + }); return; } + if (connectableNow(server.server_id) === undefined) return; setTogglingOn((prev) => new Set(prev).add(serverName)); try { - const idToFetch = serverId ?? serverName; - const result = await listMCPTools(accessToken, idToFetch); + const result = await listMCPTools(accessToken, server.server_id); if (result?.error) { MessageManager.warning(`Could not load tools for ${serverName}`); return; } + if (connectableNow(server.server_id) === undefined) return; if (!selectedServersRef.current.includes(serverName)) { onChange([...selectedServersRef.current, serverName]); } @@ -243,11 +268,10 @@ const MCPAppsPanel: React.FC = ({ accessToken, selectedServers, onChange, }; const renderConnectionIndicator = (server: MCPServer) => { - if (connectMode && isUnsupportedOnGatewayConnect(server.auth_type)) { + const unavailabilityLabel = connectUnavailabilityLabel(server); + if (unavailabilityLabel !== null) { return ( - - Not supported on this connection - + {unavailabilityLabel} ); } if (server.auth_type === AUTH_TYPE.OAUTH2) { @@ -285,11 +309,23 @@ const MCPAppsPanel: React.FC = ({ accessToken, selectedServers, onChange, !query.trim() || name.toLowerCase().includes(query.toLowerCase()) || (s.description ?? "").toLowerCase().includes(query.toLowerCase()); - const matchesTab = activeTab === "all" || selectedServers.includes(name); + const matchesTab = + activeTab === "all" || (selectedServers.includes(name) && connectUnavailabilityLabel(s) === null); return matchesQuery && matchesTab; }); - const connectedCount = servers.filter((s) => selectedServers.includes(nameOf(s))).length; + const connectedCount = servers.filter( + (s) => selectedServers.includes(nameOf(s)) && connectUnavailabilityLabel(s) === null, + ).length; + + const emptyStateText = () => { + if (servers.length === 0) { + return connectMode + ? "No MCP servers are available to this connection yet. Ask an admin to grant your user or team access." + : "No MCP servers configured. Add servers in Tools -> MCP Servers."; + } + return activeTab === "connected" ? "No servers connected yet." : "No servers match your search."; + }; const totalTools = Object.values(toolCounts).reduce((sum, n) => sum + n, 0); if (detailServer) { @@ -298,12 +334,65 @@ const MCPAppsPanel: React.FC = ({ accessToken, selectedServers, onChange, const isTogglingOn = togglingOn.has(name); const color = getAvatarColor(name); + const renderDetailAction = () => { + const unavailabilityLabel = connectUnavailabilityLabel(detailServer); + if (unavailabilityLabel !== null) { + return {unavailabilityLabel}; + } + if (detailServer.auth_type !== AUTH_TYPE.OAUTH2) { + return ( + + ); + } + if (oauthConnected.has(detailServer.server_id)) { + return ( + + ); + } + return ( + { + setOauthConnected((prev) => new Set(prev).add(id)); + }} + variant="button" + /> + ); + }; + return (
- {detailServer.auth_type === AUTH_TYPE.OAUTH2 ? ( - oauthConnected.has(detailServer.server_id) ? ( - - ) : ( - { - setOauthConnected((prev) => new Set(prev).add(id)); - }} - variant="button" - /> - ) - ) : ( - - )} + {renderDetailAction()}

Information

@@ -494,13 +542,7 @@ const MCPAppsPanel: React.FC = ({ accessToken, selectedServers, onChange, ))} ) : filtered.length === 0 ? ( -
- {servers.length === 0 - ? "No MCP servers configured. Add servers in Tools -> MCP Servers." - : activeTab === "connected" - ? "No servers connected yet." - : "No servers match your search."} -
+
{emptyStateText()}
) : (
{filtered.map((server, idx) => { @@ -508,16 +550,16 @@ const MCPAppsPanel: React.FC = ({ accessToken, selectedServers, onChange, const color = getAvatarColor(name); const isLeftCol = idx % 2 === 0; const count = toolCounts[name]; - const unsupported = !!connectMode && isUnsupportedOnGatewayConnect(server.auth_type); + const unavailable = connectUnavailabilityLabel(server) !== null; return (
setDetailServer(server)} + onClick={() => setDetailServerId(server.server_id)} className={`flex items-center gap-3 p-4 bg-card cursor-pointer transition-colors hover:bg-accent/30 min-w-0 ${ isLeftCol ? "border-r" : "" } ${Math.floor(idx / 2) < Math.floor((filtered.length - 1) / 2) ? "border-b" : ""} ${ - unsupported ? "opacity-50" : "" + unavailable ? "opacity-50" : "" }`} > {server.mcp_info?.logo_url ? ( diff --git a/ui/litellm-dashboard/src/components/mcp_tools/types.tsx b/ui/litellm-dashboard/src/components/mcp_tools/types.tsx index de497d91afe..44ac25ea955 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/types.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/types.tsx @@ -436,6 +436,7 @@ export interface MCPServer { byok_description?: string[] | null; byok_api_key_help_url?: string | null; has_user_credential?: boolean | null; + connected_app_reachable?: boolean | null; /** GitHub / source repository URL */ source_url?: string | null; diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 1a0951db967..03cf0e9583c 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -4751,9 +4751,12 @@ export const fetchDiscoverableMCPServers = async (accessToken: string) => { } }; -export const fetchMCPServers = async (accessToken: string, teamId?: string | null) => { +export const fetchMCPServers = async (accessToken: string, teamId?: string | null, connectedAppView?: boolean) => { try { - return await apiClient.get(`/v1/mcp/server`, { accessToken, query: { team_id: teamId || undefined } }); + return await apiClient.get(`/v1/mcp/server`, { + accessToken, + query: { team_id: teamId || undefined, connected_app_view: connectedAppView || undefined }, + }); } catch (error) { console.error("Failed to fetch MCP servers:", error); throw error; From 3c2264cfacc3081492e41c8cb9bff5d7da2a7c4a Mon Sep 17 00:00:00 2001 From: tin-berri Date: Thu, 30 Jul 2026 23:21:23 -0700 Subject: [PATCH 16/92] feat(ui): expose classifier context window fields on Auto-Router screens (LIT-5036) (#35315) PR #35185 added classifier_context_window_size and classifier_context_per_turn_chars to ComplexityRouterConfig; they worked via config.yaml and the API but had no UI control on the Add Model or Edit Auto-Router screens. Wires the two fields into both, shown only when the LLM classifier is selected. --- .../add_model/ClassificationMethodConfig.tsx | 65 +++++++++++++++- .../add_model/ComplexityRouterConfig.test.tsx | 77 ++++++++++++++++++- .../add_model/ComplexityRouterConfig.tsx | 4 + .../add_model/add_auto_router_tab.tsx | 4 + .../build_complexity_router_config.test.ts | 48 ++++++++++++ .../build_complexity_router_config.ts | 14 ++++ ...d_updated_complexity_router_config.test.ts | 65 ++++++++++++++++ .../edit_auto_router_modal.test.tsx | 67 +++++++++++++++- .../edit_auto_router_modal.tsx | 23 +++++- 9 files changed, 359 insertions(+), 8 deletions(-) diff --git a/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx b/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx index 92df8029edc..75acf87a7d8 100644 --- a/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx @@ -1,7 +1,13 @@ import { InfoCircleOutlined } from "@ant-design/icons"; import { Select as AntdSelect, Card, InputNumber, Radio, Space, Tooltip, Typography } from "antd"; import React from "react"; -import { ClassifierType, ComplexityRouterConfigValue, DEFAULT_CLASSIFIER_TIMEOUT_MS } from "./ComplexityRouterConfig"; +import { + ClassifierType, + ComplexityRouterConfigValue, + DEFAULT_CLASSIFIER_CONTEXT_PER_TURN_CHARS, + DEFAULT_CLASSIFIER_CONTEXT_WINDOW_SIZE, + DEFAULT_CLASSIFIER_TIMEOUT_MS, +} from "./ComplexityRouterConfig"; const { Text } = Typography; @@ -26,14 +32,23 @@ const ClassificationMethodConfig: React.FC = ({ showValidationErrors && value.classifier_type === "llm" && !value.classifier_llm_config?.model; const handleClassifierTypeChange = (classifierType: ClassifierType) => { - onChange({ + const nextValue: ComplexityRouterConfigValue = { ...value, classifier_type: classifierType, classifier_llm_config: classifierType === "llm" ? value.classifier_llm_config ?? { model: "", timeout_ms: DEFAULT_CLASSIFIER_TIMEOUT_MS } : undefined, - }); + classifier_context_window_size: + classifierType === "llm" + ? value.classifier_context_window_size ?? DEFAULT_CLASSIFIER_CONTEXT_WINDOW_SIZE + : undefined, + classifier_context_per_turn_chars: + classifierType === "llm" + ? value.classifier_context_per_turn_chars ?? DEFAULT_CLASSIFIER_CONTEXT_PER_TURN_CHARS + : undefined, + }; + onChange(nextValue); }; const handleClassifierModelChange = (model: string) => { @@ -56,6 +71,20 @@ const ClassificationMethodConfig: React.FC = ({ }); }; + const handleClassifierContextWindowSizeChange = (windowSize: number | null) => { + onChange({ + ...value, + classifier_context_window_size: windowSize ?? DEFAULT_CLASSIFIER_CONTEXT_WINDOW_SIZE, + }); + }; + + const handleClassifierContextPerTurnCharsChange = (perTurnChars: number | null) => { + onChange({ + ...value, + classifier_context_per_turn_chars: perTurnChars ?? DEFAULT_CLASSIFIER_CONTEXT_PER_TURN_CHARS, + }); + }; + return ( <> = ({ response.
+
+ + Context Window Size + + + + Number of prior user turns (tool output and harness reminders excluded) sent to the classifier as context, + so a referring follow-up like "now do the same for the streaming path" is classified against + what it refers to. Set to 0 to send only the current message. + +
+
+ + Context Per-Turn Character Limit + + + + Prior turns longer than this are truncated. + +
)} diff --git a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.test.tsx b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.test.tsx index 6b3c1961468..a1e992bd46b 100644 --- a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.test.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.test.tsx @@ -99,11 +99,14 @@ describe("ComplexityRouterConfig", () => { fireEvent.click(screen.getByText("Advanced: Classification Method")); fireEvent.click(screen.getByText("LLM Classifier")); - expect(onChange).toHaveBeenCalledWith({ + const expectedValue: ComplexityRouterConfigValue = { ...defaultValue, classifier_type: "llm", classifier_llm_config: { model: "", timeout_ms: 3000 }, - }); + classifier_context_window_size: 3, + classifier_context_per_turn_chars: 200, + }; + expect(onChange).toHaveBeenCalledWith(expectedValue); }); it("should show classifier fields and use the configured values when classifier_type is llm", () => { @@ -111,6 +114,8 @@ describe("ComplexityRouterConfig", () => { ...defaultValue, classifier_type: "llm", classifier_llm_config: { model: "gpt-3.5-turbo", timeout_ms: 750 }, + classifier_context_window_size: 5, + classifier_context_per_turn_chars: 400, }; renderWithProviders(); @@ -119,6 +124,74 @@ describe("ComplexityRouterConfig", () => { expect(screen.getByText("Classifier Model")).toBeInTheDocument(); expect(screen.getByText("Timeout (ms)")).toBeInTheDocument(); expect(screen.getByDisplayValue("750")).toBeInTheDocument(); + expect(screen.getByText("Context Window Size")).toBeInTheDocument(); + expect(screen.getByDisplayValue("5")).toBeInTheDocument(); + expect(screen.getByText("Context Per-Turn Character Limit")).toBeInTheDocument(); + expect(screen.getByDisplayValue("400")).toBeInTheDocument(); + }); + + it("should default classifier context fields to 3 and 200 when llm is selected without explicit values", () => { + const llmValue: ComplexityRouterConfigValue = { + ...defaultValue, + classifier_type: "llm", + classifier_llm_config: { model: "gpt-3.5-turbo", timeout_ms: 3000 }, + }; + renderWithProviders(); + + fireEvent.click(screen.getByText("Advanced: Classification Method")); + + const windowSizeSection = screen.getByText("Context Window Size").closest("div") as HTMLElement; + expect(within(windowSizeSection).getByDisplayValue("3")).toBeInTheDocument(); + + const perTurnCharsSection = screen.getByText("Context Per-Turn Character Limit").closest("div") as HTMLElement; + expect(within(perTurnCharsSection).getByDisplayValue("200")).toBeInTheDocument(); + }); + + it("should hide classifier context fields when classifier_type is heuristic", () => { + renderWithProviders(); + fireEvent.click(screen.getByText("Advanced: Classification Method")); + expect(screen.queryByText("Context Window Size")).not.toBeInTheDocument(); + expect(screen.queryByText("Context Per-Turn Character Limit")).not.toBeInTheDocument(); + }); + + it("should call onChange with the updated classifier_context_window_size when edited", () => { + const onChange = vi.fn(); + const llmValue: ComplexityRouterConfigValue = { + ...defaultValue, + classifier_type: "llm", + classifier_llm_config: { model: "gpt-3.5-turbo", timeout_ms: 3000 }, + }; + renderWithProviders(); + fireEvent.click(screen.getByText("Advanced: Classification Method")); + + const windowSizeSection = screen.getByText("Context Window Size").closest("div") as HTMLElement; + const input = within(windowSizeSection).getByRole("spinbutton"); + fireEvent.change(input, { target: { value: "7" } }); + + expect(onChange).toHaveBeenCalledWith({ + ...llmValue, + classifier_context_window_size: 7, + }); + }); + + it("should call onChange with the updated classifier_context_per_turn_chars when edited", () => { + const onChange = vi.fn(); + const llmValue: ComplexityRouterConfigValue = { + ...defaultValue, + classifier_type: "llm", + classifier_llm_config: { model: "gpt-3.5-turbo", timeout_ms: 3000 }, + }; + renderWithProviders(); + fireEvent.click(screen.getByText("Advanced: Classification Method")); + + const perTurnCharsSection = screen.getByText("Context Per-Turn Character Limit").closest("div") as HTMLElement; + const input = within(perTurnCharsSection).getByRole("spinbutton"); + fireEvent.change(input, { target: { value: "500" } }); + + expect(onChange).toHaveBeenCalledWith({ + ...llmValue, + classifier_context_per_turn_chars: 500, + }); }); it("should render the custom technical keywords field", () => { diff --git a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx index 1f2edf697a9..de32d15d5a7 100644 --- a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx @@ -12,6 +12,8 @@ const { Text } = Typography; export const DEFAULT_CLASSIFIER_TIMEOUT_MS = 3000; export const DEFAULT_TIER_DISTANCE_PENALTY = 0.5; +export const DEFAULT_CLASSIFIER_CONTEXT_WINDOW_SIZE = 3; +export const DEFAULT_CLASSIFIER_CONTEXT_PER_TURN_CHARS = 200; export interface ComplexityTiers { SIMPLE: string[]; @@ -40,6 +42,8 @@ export interface ComplexityRouterConfigValue { tiers: ComplexityTiers; classifier_type: ClassifierType; classifier_llm_config?: ClassifierLLMConfig; + classifier_context_window_size?: number; + classifier_context_per_turn_chars?: number; adaptive?: boolean; adaptive_weights?: AdaptiveRouterWeights; tier_distance_penalty?: number; diff --git a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx index cd3294b347f..fb7eabd7110 100644 --- a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx +++ b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx @@ -99,6 +99,8 @@ const AddAutoRouterTab: React.FC = ({ tiers, classifier_type: classifierType, classifier_llm_config: classifierLlmConfig, + classifier_context_window_size: classifierContextWindowSize, + classifier_context_per_turn_chars: classifierContextPerTurnChars, adaptive = false, adaptive_weights: adaptiveWeights = DEFAULT_ADAPTIVE_WEIGHTS, tier_distance_penalty: tierDistancePenalty = DEFAULT_TIER_DISTANCE_PENALTY, @@ -142,6 +144,8 @@ const AddAutoRouterTab: React.FC = ({ tiers, classifierType, classifierLlmConfig, + classifierContextWindowSize, + classifierContextPerTurnChars, customTechnicalKeywords, keywordTierRules, semanticMatchingEnabled, diff --git a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts index b5973bf7101..e269a3c9028 100644 --- a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts +++ b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts @@ -16,6 +16,8 @@ const baseParams: BuildComplexityRouterConfigParams = { tiers, classifierType: "heuristic", classifierLlmConfig: undefined, + classifierContextWindowSize: undefined, + classifierContextPerTurnChars: undefined, customTechnicalKeywords: [], keywordTierRules: [], semanticMatchingEnabled: false, @@ -75,6 +77,52 @@ describe("buildComplexityRouterConfig", () => { expect(config.classifier_llm_config).toBeUndefined(); }); + it("includes classifier_context_window_size and classifier_context_per_turn_chars only when classifier_type is llm", () => { + const params: BuildComplexityRouterConfigParams = { + ...baseParams, + classifierType: "llm", + classifierLlmConfig: { model: "gpt-4o-mini", timeout_ms: 3000 }, + classifierContextWindowSize: 5, + classifierContextPerTurnChars: 300, + }; + const config = buildComplexityRouterConfig(params); + expect(config.classifier_context_window_size).toBe(5); + expect(config.classifier_context_per_turn_chars).toBe(300); + }); + + it("omits classifier_context_window_size and classifier_context_per_turn_chars when classifier_type is heuristic even if values linger in state", () => { + const params: BuildComplexityRouterConfigParams = { + ...baseParams, + classifierType: "heuristic", + classifierContextWindowSize: 5, + classifierContextPerTurnChars: 300, + }; + const config = buildComplexityRouterConfig(params); + expect(config.classifier_context_window_size).toBeUndefined(); + expect(config.classifier_context_per_turn_chars).toBeUndefined(); + }); + + it("omits classifier_context_window_size and classifier_context_per_turn_chars when classifier_type is llm but neither was set, leaving the backend default", () => { + const config = buildComplexityRouterConfig({ + ...baseParams, + classifierType: "llm", + classifierLlmConfig: { model: "gpt-4o-mini", timeout_ms: 3000 }, + }); + expect(config.classifier_context_window_size).toBeUndefined(); + expect(config.classifier_context_per_turn_chars).toBeUndefined(); + }); + + it("allows classifier_context_window_size of 0, distinct from unset, to send no prior-turn context", () => { + const params: BuildComplexityRouterConfigParams = { + ...baseParams, + classifierType: "llm", + classifierLlmConfig: { model: "gpt-4o-mini", timeout_ms: 3000 }, + classifierContextWindowSize: 0, + }; + const config = buildComplexityRouterConfig(params); + expect(config.classifier_context_window_size).toBe(0); + }); + it("sends keyword_tier_rules with their per-tier targeting preserved (not flattened)", () => { const params: BuildComplexityRouterConfigParams = { ...baseParams, diff --git a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts index 02be9b280aa..04a9b0f9bd4 100644 --- a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts +++ b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts @@ -12,6 +12,8 @@ export interface BuildComplexityRouterConfigParams { tiers: ComplexityTiers; classifierType: ClassifierType; classifierLlmConfig: ClassifierLLMConfig | undefined; + classifierContextWindowSize: number | undefined; + classifierContextPerTurnChars: number | undefined; customTechnicalKeywords: string[]; keywordTierRules: KeywordTierRule[]; semanticMatchingEnabled: boolean; @@ -29,6 +31,8 @@ export interface ComplexityRouterConfigPayload { tiers: ComplexityTiers; classifier_type: ClassifierType; classifier_llm_config?: ClassifierLLMConfig; + classifier_context_window_size?: number; + classifier_context_per_turn_chars?: number; custom_technical_keywords?: string[]; keyword_tier_rules?: { keywords: string[]; tier: KeywordTierRule["tier"] }[]; semantic_keyword_matching?: boolean; @@ -69,6 +73,8 @@ export const buildComplexityRouterConfig = ({ tiers, classifierType, classifierLlmConfig, + classifierContextWindowSize, + classifierContextPerTurnChars, customTechnicalKeywords, keywordTierRules, semanticMatchingEnabled, @@ -89,6 +95,14 @@ export const buildComplexityRouterConfig = ({ tiers, classifier_type: classifierType, ...(classifierType === "llm" && classifierLlmConfig && { classifier_llm_config: classifierLlmConfig }), + ...(classifierType === "llm" && + classifierContextWindowSize !== undefined && { + classifier_context_window_size: classifierContextWindowSize, + }), + ...(classifierType === "llm" && + classifierContextPerTurnChars !== undefined && { + classifier_context_per_turn_chars: classifierContextPerTurnChars, + }), ...(customTechnicalKeywords.length > 0 && { custom_technical_keywords: customTechnicalKeywords }), ...(cleanedKeywordTierRules.length > 0 && { keyword_tier_rules: cleanedKeywordTierRules }), escalation_keywords: cleanedEscalationKeywords, diff --git a/ui/litellm-dashboard/src/components/edit_auto_router/build_updated_complexity_router_config.test.ts b/ui/litellm-dashboard/src/components/edit_auto_router/build_updated_complexity_router_config.test.ts index e39a7b6e444..eef5e1e4d06 100644 --- a/ui/litellm-dashboard/src/components/edit_auto_router/build_updated_complexity_router_config.test.ts +++ b/ui/litellm-dashboard/src/components/edit_auto_router/build_updated_complexity_router_config.test.ts @@ -86,3 +86,68 @@ describe("buildUpdatedComplexityRouterConfig keyword matching", () => { expect(result.match_threshold).toBe(0.72); }); }); + +const STORED_LLM = { + tiers: { SIMPLE: ["gpt-4o-mini"], MEDIUM: [], COMPLEX: [], REASONING: [] }, + classifier_type: "llm", + classifier_llm_config: { model: "gpt-4o-mini", timeout_ms: 3000 }, + classifier_context_window_size: 5, + classifier_context_per_turn_chars: 300, +}; + +describe("buildUpdatedComplexityRouterConfig classifier context window", () => { + it("round-trips an untouched edit without changing the classifier context values", () => { + const formValue = { + tiers: STORED_LLM.tiers, + classifier_type: "llm" as const, + classifier_llm_config: STORED_LLM.classifier_llm_config, + classifier_context_window_size: 5, + classifier_context_per_turn_chars: 300, + }; + const result = buildUpdatedComplexityRouterConfig(STORED_LLM, formValue); + + expect(result.classifier_context_window_size).toBe(5); + expect(result.classifier_context_per_turn_chars).toBe(300); + }); + + it("persists an edited classifier context window size and per-turn char limit", () => { + const formValue = { + tiers: STORED_LLM.tiers, + classifier_type: "llm" as const, + classifier_llm_config: STORED_LLM.classifier_llm_config, + classifier_context_window_size: 10, + classifier_context_per_turn_chars: 500, + }; + const result = buildUpdatedComplexityRouterConfig(STORED_LLM, formValue); + + expect(result.classifier_context_window_size).toBe(10); + expect(result.classifier_context_per_turn_chars).toBe(500); + }); + + it("omits classifier context fields when classifier_type is heuristic even if values linger in state", () => { + const formValue = { + tiers: STORED_LLM.tiers, + classifier_type: "heuristic" as const, + classifier_context_window_size: 5, + classifier_context_per_turn_chars: 300, + }; + const result = buildUpdatedComplexityRouterConfig(STORED_LLM, formValue); + + expect(result.classifier_context_window_size).toBeUndefined(); + expect(result.classifier_context_per_turn_chars).toBeUndefined(); + }); + + it("does not resurrect a stale stored classifier_context_window_size once the form's own value is unset", () => { + // classifier_context_window_size is a MANAGED key: the form's value must win over whatever + // is still sitting in the stored config, never fall back to it through preservedConfig. + const formValue = { + tiers: STORED_LLM.tiers, + classifier_type: "llm" as const, + classifier_llm_config: STORED_LLM.classifier_llm_config, + }; + const result = buildUpdatedComplexityRouterConfig(STORED_LLM, formValue); + + expect(result.classifier_context_window_size).toBeUndefined(); + expect(result.classifier_context_per_turn_chars).toBeUndefined(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.test.tsx b/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.test.tsx index e47bbecf8bd..504234e8977 100644 --- a/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.test.tsx +++ b/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.test.tsx @@ -1,7 +1,7 @@ import userEvent from "@testing-library/user-event"; import { describe, expect, it, vi } from "vitest"; -import { renderWithProviders, screen, waitFor } from "@/../tests/test-utils"; +import { fireEvent, renderWithProviders, screen, waitFor, within } from "@/../tests/test-utils"; import NotificationsManager from "@/components/molecules/notifications_manager"; import EditAutoRouterModal from "./edit_auto_router_modal"; @@ -119,3 +119,68 @@ describe("EditAutoRouterModal keyword matching", () => { expect(modelPatchUpdateCall).not.toHaveBeenCalled(); }); }); + +describe("EditAutoRouterModal classifier context window", () => { + beforeEach(() => { + modelPatchUpdateCall.mockClear(); + }); + + const STORED_LLM_CONFIG = { + tiers: { SIMPLE: ["gpt-4o-mini"], MEDIUM: ["gpt-4o-mini"], COMPLEX: ["gpt-4o-mini"], REASONING: ["gpt-4o-mini"] }, + classifier_type: "llm", + classifier_llm_config: { model: "gpt-4o-mini", timeout_ms: 3000 }, + classifier_context_window_size: 5, + classifier_context_per_turn_chars: 300, + }; + + const renderLlmModal = () => + renderWithProviders( + , + ); + + // Hydration bugs are invisible to the payload-builder unit tests, which only exercise + // buildUpdatedComplexityRouterConfig with a form value the caller already assembled by hand. + // Only driving the real component through open, then save with nothing touched, catches a + // missing initializeForm hydration line. + it("shows the stored classifier context values and preserves them through an untouched open-and-save", async () => { + const user = userEvent.setup(); + renderLlmModal(); + + await user.click(await screen.findByText("Advanced: Classification Method")); + await screen.findByText("Context Window Size"); + expect(screen.getByDisplayValue("5")).toBeInTheDocument(); + expect(screen.getByDisplayValue("300")).toBeInTheDocument(); + + await user.click(screen.getByRole("button", { name: /save changes/i })); + + await waitFor(() => expect(modelPatchUpdateCall).toHaveBeenCalled()); + const config = savedConfig(); + expect(config.classifier_context_window_size).toBe(5); + expect(config.classifier_context_per_turn_chars).toBe(300); + }); + + it("persists an edited classifier context window size", async () => { + const user = userEvent.setup(); + renderLlmModal(); + + await user.click(await screen.findByText("Advanced: Classification Method")); + const windowSizeSection = (await screen.findByText("Context Window Size")).closest("div") as HTMLElement; + const input = within(windowSizeSection).getByRole("spinbutton"); + fireEvent.change(input, { target: { value: "8" } }); + + await user.click(screen.getByRole("button", { name: /save changes/i })); + + await waitFor(() => expect(modelPatchUpdateCall).toHaveBeenCalled()); + expect(savedConfig().classifier_context_window_size).toBe(8); + }); +}); diff --git a/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx b/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx index 8fbca822165..2686f99307e 100644 --- a/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx +++ b/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx @@ -33,6 +33,8 @@ const MANAGED_COMPLEXITY_ROUTER_KEYS = new Set([ "tiers", "classifier_type", "classifier_llm_config", + "classifier_context_window_size", + "classifier_context_per_turn_chars", "adaptive", "adaptive_weights", "tier_distance_penalty", @@ -86,6 +88,14 @@ export const buildUpdatedComplexityRouterConfig = ( tiers: value.tiers, classifier_type: value.classifier_type, ...(value.classifier_type === "llm" ? { classifier_llm_config: value.classifier_llm_config } : {}), + ...(value.classifier_type === "llm" && + value.classifier_context_window_size !== undefined && { + classifier_context_window_size: value.classifier_context_window_size, + }), + ...(value.classifier_type === "llm" && + value.classifier_context_per_turn_chars !== undefined && { + classifier_context_per_turn_chars: value.classifier_context_per_turn_chars, + }), ...(customTechnicalKeywords && customTechnicalKeywords.length > 0 && { custom_technical_keywords: customTechnicalKeywords, @@ -182,7 +192,7 @@ const EditAutoRouterModal: React.FC = ({ parsedConfig = JSON.parse(parsedConfig); } - setComplexityRouterConfig({ + const hydratedComplexityRouterConfig: ComplexityRouterConfigValue = { tiers: { SIMPLE: normalizeTierModels(parsedConfig.tiers?.SIMPLE), MEDIUM: normalizeTierModels(parsedConfig.tiers?.MEDIUM), @@ -191,12 +201,21 @@ const EditAutoRouterModal: React.FC = ({ }, classifier_type: parsedConfig.classifier_type || "heuristic", classifier_llm_config: parsedConfig.classifier_llm_config, + classifier_context_window_size: + typeof parsedConfig.classifier_context_window_size === "number" + ? parsedConfig.classifier_context_window_size + : undefined, + classifier_context_per_turn_chars: + typeof parsedConfig.classifier_context_per_turn_chars === "number" + ? parsedConfig.classifier_context_per_turn_chars + : undefined, adaptive: parsedConfig.adaptive || false, adaptive_weights: parsedConfig.adaptive_weights, tier_distance_penalty: parsedConfig.tier_distance_penalty, adaptive_eligible: parsedConfig.adaptive_eligible || "all", return_raw_model_name: parsedConfig.return_raw_model_name || false, - }); + }; + setComplexityRouterConfig(hydratedComplexityRouterConfig); setCustomTechnicalKeywords( Array.isArray(parsedConfig.custom_technical_keywords) ? parsedConfig.custom_technical_keywords : [], ); From ffd6ac52c5ddd1321b07761cd1f3d204ddb3cdc8 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Fri, 31 Jul 2026 00:08:20 -0700 Subject: [PATCH 17/92] fix(deps): raise aiohttp floor to 3.14.2 to clear pooled-connection timeouts aiohttp 3.14.0 and 3.14.1 re-arm the sock_read timer on a keep-alive connection after it has already been returned to the idle pool. The stray timer stamps a SocketTimeoutError on the pooled connection without closing it, so the pool keeps handing it out and the next request to pick it up fails instantly on an error left behind by an earlier, unrelated request. Because a single pool is shared across providers, the failures appear simultaneously across Vertex AI, Bedrock, Anthropic and OpenAI-compatible deployments as sub-millisecond "Connection timed out" errors. uv.lock resolved aiohttp 3.14.1 and the published images install via `uv sync --frozen`, so every image built from that lock shipped the regression. The wheel's own metadata declared `aiohttp>=3.10,<4.0`, which also left pip consumers free to resolve into the same broken window, so both the runtime floor and the uv constraint move to >=3.14.2. Upstream fixed this in aio-libs/aiohttp#12954, released in aiohttp 3.14.2; the lock now resolves 3.14.3. Raising the floor rather than capping below 3.14 keeps the advisories that the existing 3.14.1 floor cleared, so no osv-scanner ignores are needed. litellm requires Python >=3.10 and aiohttp 3.14.2 requires >=3.10, so no supported interpreter loses support. Both new tests fail on the previous pins and pass on these. --- pyproject.toml | 4 +- .../test_basic_python_version.py | 71 +++++ uv.lock | 246 +++++++++--------- 3 files changed, 196 insertions(+), 125 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 93fb32da464..678e7384a05 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ dependencies = [ "tokenizers>=0.21.0,<1.0", "click>=8.0.0,<9.0", "jinja2>=3.1.6,<4.0", - "aiohttp>=3.10,<4.0", + "aiohttp>=3.14.2,<4.0", "pydantic>=2.10.0,<3.0.0", "jsonschema>=4.0.0,<5.0", ] @@ -277,7 +277,7 @@ exclude = [ [tool.uv] constraint-dependencies = [ "tornado>=6.5.6", - "aiohttp>=3.14.1,<4.0", + "aiohttp>=3.14.2,<4.0", "packaging>=24.0", "soupsieve>=2.8.4", "httplib2>=0.32.0", diff --git a/tests/local_testing/test_basic_python_version.py b/tests/local_testing/test_basic_python_version.py index e31c3953714..1f260f86eeb 100644 --- a/tests/local_testing/test_basic_python_version.py +++ b/tests/local_testing/test_basic_python_version.py @@ -142,6 +142,77 @@ def test_cli_extra_is_a_thin_client_install(): assert not leaked, f"`cli` extra leaks proxy-server deps onto laptops: {leaked}" +AIOHTTP_POOL_POISONING_RANGE = ">=3.14.0,<3.14.2" +AIOHTTP_POOL_POISONING_RELEASES = ("3.14.0", "3.14.1") + + +def _load_toml(path): + try: + import tomllib as tomli + except ImportError: + try: + import tomli + except ImportError: + pytest.skip("tomli/tomllib not available - skipping dependency check") + + with open(path, "rb") as f: + return tomli.load(f) + + +def _declared_aiohttp_specifier(): + from packaging.requirements import Requirement + + pyproject = _load_toml(os.path.join(PROJECT_ROOT, "pyproject.toml")) + for requirement in pyproject["project"]["dependencies"]: + parsed = Requirement(requirement) + if parsed.name.lower() == "aiohttp": + return parsed.specifier + pytest.fail("aiohttp is no longer a declared runtime dependency of litellm") + + +def _locked_aiohttp_version(): + lock = _load_toml(os.path.join(PROJECT_ROOT, "uv.lock")) + for package in lock["package"]: + if package["name"].lower() == "aiohttp": + return package["version"] + pytest.fail("aiohttp is missing from uv.lock") + + +def test_declared_aiohttp_floor_excludes_pool_poisoning_releases(): + """aiohttp 3.14.0/3.14.1 re-arm the sock_read timer on a keep-alive connection + after it is back in the idle pool, so the next request to reuse it fails + instantly with a bogus timeout (aio-libs/aiohttp#12953, fixed in 3.14.2). + + The wheel's own metadata is what pip resolves against, so the floor declared + here - not just the lockfile - has to exclude that range. + """ + specifier = _declared_aiohttp_specifier() + + admitted = [v for v in AIOHTTP_POOL_POISONING_RELEASES if specifier.contains(v)] + assert not admitted, ( + f"litellm declares aiohttp{specifier}, which still admits {admitted}. " + "Those releases poison pooled keep-alive connections and cause " + "cross-provider sub-millisecond 'Connection timed out' failures; " + "keep the floor at >=3.14.2." + ) + + +def test_locked_aiohttp_version_is_not_pool_poisoning(): + """uv.lock is what the published Docker images install (uv sync --frozen), so a + lock that drifts back onto 3.14.0/3.14.1 ships the regression regardless of + what pyproject.toml declares. + """ + from packaging.specifiers import SpecifierSet + + locked = _locked_aiohttp_version() + + assert not SpecifierSet(AIOHTTP_POOL_POISONING_RANGE).contains(locked), ( + f"uv.lock resolves aiohttp {locked}, which is inside the pool-poisoning " + f"range {AIOHTTP_POOL_POISONING_RANGE} (aio-libs/aiohttp#12953). " + "Re-run `uv lock` against an aiohttp>=3.14.2 floor." + ) + + import os import subprocess import time diff --git a/uv.lock b/uv.lock index d30f2df0a0e..fa7652c67ec 100644 --- a/uv.lock +++ b/uv.lock @@ -10,7 +10,7 @@ resolution-markers = [ ] [options] -exclude-newer = "2026-07-27T18:40:42.08538Z" +exclude-newer = "2026-07-28T06:59:32.050819Z" exclude-newer-span = "P3D" [manifest] @@ -20,7 +20,7 @@ members = [ "litellm-proxy-extras", ] constraints = [ - { name = "aiohttp", specifier = ">=3.14.1,<4.0" }, + { name = "aiohttp", specifier = ">=3.14.2,<4.0" }, { name = "httplib2", specifier = ">=0.32.0" }, { name = "packaging", specifier = ">=24.0" }, { name = "setuptools", specifier = ">=83.0.0" }, @@ -82,7 +82,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.14.1" +version = "3.14.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -95,126 +95,126 @@ dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.13'" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/82/78/8ea7308cac6934de8c74a14f3d5f65d1c89287426688be79538d0e5c013d/aiohttp-3.14.1.tar.gz", hash = "sha256:307f2cff90a764d329e77040603fa032db89c5c24fdad50c4c15334cba744035", size = 7955794, upload-time = "2026-06-07T21:09:35.529Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/d9/22ce5786ac0c1653ae8b6c23bded02c1686d11f0dbb45b31ce128e0df985/aiohttp-3.14.3.tar.gz", hash = "sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc", size = 7971213, upload-time = "2026-07-23T01:57:27.037Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/67/58ded4b3f2e10f94972d8928050c85330e249a31dd45a0e5f3c0e9c3fa05/aiohttp-3.14.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8f6bb621e5863cfe8fe5ff5468002d200ec31f30f1280b259dc505b02595099e", size = 766140, upload-time = "2026-06-07T21:05:37.471Z" }, - { url = "https://files.pythonhosted.org/packages/18/68/4ae5b4e08943f316594bb68da89957d3baf5760588fa09509594bd777e4b/aiohttp-3.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f7215cb3933784f79ed20e5f050e15984f390424339b22375d5a53c933a0491", size = 519430, upload-time = "2026-06-07T21:05:40.751Z" }, - { url = "https://files.pythonhosted.org/packages/cb/c1/316c8f3549dbe5245f92bfd523ec6f32dd4d98cafe21df3f6a19b1184c75/aiohttp-3.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9d4e294455b23a68c9b8f042d0e8e377a265bcb15332753695f6e5b6819e0ce", size = 514406, upload-time = "2026-06-07T21:05:42.111Z" }, - { url = "https://files.pythonhosted.org/packages/5a/ee/fb0ac28684e8d753b83c8a4eebc19a5846912aa0a4daaabb6a9936363840/aiohttp-3.14.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b238af795833d5731d049d82bc84b768ae6f8f97f0495963b3ed9935c5901cc3", size = 1703649, upload-time = "2026-06-07T21:05:43.427Z" }, - { url = "https://files.pythonhosted.org/packages/3b/57/aa2beab673331f111885db8a7b69dfe3ab0e53e446a0ace18ca694b4dc58/aiohttp-3.14.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e4e5e0ae56914ecdbf446493addefc0159053dd53962cef37d7839f37f73d505", size = 1675126, upload-time = "2026-06-07T21:05:44.897Z" }, - { url = "https://files.pythonhosted.org/packages/47/ea/dad128abe365e79be03b16ed464198ac73e0d257e8260c6f7d6f31cbef26/aiohttp-3.14.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:092e4ce3619a7c6dee52a6bdabda973d9b34b66781f840ce93c7e0cec30cf521", size = 1771558, upload-time = "2026-06-07T21:05:46.405Z" }, - { url = "https://files.pythonhosted.org/packages/63/f3/b5b4e10327cb85d34d24232c6b71b64602f190b3ccb238a043ac6b187dac/aiohttp-3.14.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb33777ea21e8b7ecde0e6fc84f598be0a1192eab1a63bc746d75aa75d38e7bd", size = 1856631, upload-time = "2026-06-07T21:05:47.844Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9d/93294c3045775c708ac8310eb3d3622a11d2951345ad590d532d62a1faa4/aiohttp-3.14.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23119f8fd4f5d16902ed459b63b100bcd269628075162bddac56cc7b5273b3fb", size = 1714139, upload-time = "2026-06-07T21:05:49.982Z" }, - { url = "https://files.pythonhosted.org/packages/29/c4/93067c85a0373492ce8e577435203c5947c454af074ac48ed4f3a1b9dd4a/aiohttp-3.14.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:57fc6745a4b7d0f5a9eb4f40a69718be6c0bc1b8368cc9fe89e90118719f4f42", size = 1588321, upload-time = "2026-06-07T21:05:51.431Z" }, - { url = "https://files.pythonhosted.org/packages/c4/39/9ff91aaf02af8b7b8222a987466da539f154c3e01732c22b5f5a20a8ee66/aiohttp-3.14.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6fd35beba67c4183b09375c5fff9accb47524191a244a99f95fd4472f5402c2b", size = 1670375, upload-time = "2026-06-07T21:05:53.109Z" }, - { url = "https://files.pythonhosted.org/packages/aa/e4/77452a3676b8d99ac1375f77691d6bf65ea6e9f4b201b82ef77c916dc767/aiohttp-3.14.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:672b9d65f42eb877f5c3f234a4547e4e1a226ca8c2eed879bb34670a0ce51192", size = 1690933, upload-time = "2026-06-07T21:05:54.902Z" }, - { url = "https://files.pythonhosted.org/packages/7d/84/b0059a7c7fc05ea23f3bc1596ba91c12f79588b9450564a24cac37536d0a/aiohttp-3.14.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:24ba13339fed9251d9b1a1bec8c7ab84c0d1675d79d33501e11f94f8b9a84e05", size = 1740798, upload-time = "2026-06-07T21:05:56.458Z" }, - { url = "https://files.pythonhosted.org/packages/8f/3a/e2a513ecbfc362591caa51a7f7e011b3bfc8938b388ae44cd95560d36999/aiohttp-3.14.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:94da27378da0610e341c4d30de29a191672683cc82b8f9556e8f7c7212a020fe", size = 1576412, upload-time = "2026-06-07T21:05:57.953Z" }, - { url = "https://files.pythonhosted.org/packages/a1/10/08f1654f538f93d36dcac66310a06eefce4641cdafca83f9f0a5317be254/aiohttp-3.14.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:52cdac9432d8b4a719f35094a818d95adcae0f0b4fe9b9b921909e0c87de9e7d", size = 1750199, upload-time = "2026-06-07T21:05:59.488Z" }, - { url = "https://files.pythonhosted.org/packages/99/e4/d91b70c57d8b8e9611e4a2e52238ca3698d3dc1c2efe25b7a9bf594ac584/aiohttp-3.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:672ac254412a24d0d0cf00a9e6c238877e4be5e5fa2d188832c1244f45f31966", size = 1699356, upload-time = "2026-06-07T21:06:01.131Z" }, - { url = "https://files.pythonhosted.org/packages/3d/f1/15340176f35ff61b95dbe34020bcf43f9e624a2d7bbac934715ff97d2033/aiohttp-3.14.1-cp310-cp310-win32.whl", hash = "sha256:2fe3607e71acc6ebb0ec8e492a247bf7a291226192dc0084236dfc12478916f6", size = 458939, upload-time = "2026-06-07T21:06:02.86Z" }, - { url = "https://files.pythonhosted.org/packages/c3/c2/a2f1ec5b37f903109e43ae2862268cfe4a67a60c1b2cf43169fcdff5995f/aiohttp-3.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:30099eda75a53c32efb0920e9c33c195314d2cc1c680fbfd30894932ac5f27df", size = 482583, upload-time = "2026-06-07T21:06:04.666Z" }, - { url = "https://files.pythonhosted.org/packages/d0/7a/7b56f6732ef79530afaa72aa335d41b67c8d79b946995f0b11ad72985435/aiohttp-3.14.1-cp310-cp310-win_arm64.whl", hash = "sha256:5a837f49d901f9e368651b676912bff1104ed8c1a83b280bcd7b29adccef5c9c", size = 453470, upload-time = "2026-06-07T21:06:06.322Z" }, - { url = "https://files.pythonhosted.org/packages/26/dd/bf526e6f0a1120dd6f2df2e97bacfe4d358f13d17a0ff5847301a1375a51/aiohttp-3.14.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa00140699487bd435fde4342d85c94cb256b7cd3a5b9c3396c67f19922afda2", size = 765225, upload-time = "2026-06-07T21:06:07.957Z" }, - { url = "https://files.pythonhosted.org/packages/8f/e1/a2872aa55495a70f61310d411541c6ee23812d9a884e000c716e1bc3edbf/aiohttp-3.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c1af67559445498b502030c35c59db59966f47041ca9de5b4e707f86bd10b5f", size = 518743, upload-time = "2026-06-07T21:06:09.749Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e7/c60c7b209e509cc787de3cea0550a518538cfc08003e1c1e14c1c63fff71/aiohttp-3.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d44ec478e713ee7f29b439f7eb8dc2b9d4079e11ae114d2c2ac3d5daf30516c8", size = 514139, upload-time = "2026-06-07T21:06:11.26Z" }, - { url = "https://files.pythonhosted.org/packages/5b/8d/614ace2f579702c9840ab1e1447fd8509e35b0b904f7196418fa2f57b25d/aiohttp-3.14.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d3b1a184a9a8f548a6b73f1e26b96b052193e4b3175ed7342aaf1151a1f00a04", size = 1784088, upload-time = "2026-06-07T21:06:12.887Z" }, - { url = "https://files.pythonhosted.org/packages/49/e0/726e90f99542bf292f81a96a12cc4847deb86f3ccf62c6f4014a201f4d33/aiohttp-3.14.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5f2504bc0322437c9a1ff6d3333ca56c7477b727c995f036b976ae17b98372c8", size = 1737835, upload-time = "2026-06-07T21:06:14.564Z" }, - { url = "https://files.pythonhosted.org/packages/0b/4b/d176d5c4db9d33dacf0543102ea59503bc1d528af4cfd0b719949ca49389/aiohttp-3.14.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73f05ea02013e02512c3bf42714f1208c57168c779cc6fe23516e4543089d0a6", size = 1842801, upload-time = "2026-06-07T21:06:16.228Z" }, - { url = "https://files.pythonhosted.org/packages/dc/d6/5a99b563690ea0cbed912ae94a2ce33993a5709a651a3a4fe761e7dd973a/aiohttp-3.14.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:797457503c2d426bee06eef808d07b31ede30b65e054444e7de64cad0061b7af", size = 1929992, upload-time = "2026-06-07T21:06:17.947Z" }, - { url = "https://files.pythonhosted.org/packages/76/7f/a987b14a3859094b3cea3f4825219c3e5536242564af6e3f9c2f6c994eb2/aiohttp-3.14.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b821a1f7dedf7e37450654e620038ac3b2e81e8fa6ea269337e97101978ec730", size = 1786989, upload-time = "2026-06-07T21:06:19.677Z" }, - { url = "https://files.pythonhosted.org/packages/f1/1a/420e5c85a3e73349372ed22ce0b6af86bfa6ce16a4b20a64a2e94608c781/aiohttp-3.14.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4cd96b5ba05d67ed0cf00b5b405c8cd99586d8e3481e8ee0a831057591af7621", size = 1640129, upload-time = "2026-06-07T21:06:22.558Z" }, - { url = "https://files.pythonhosted.org/packages/a7/80/18a592ed3be0a402cc03670bd72ee1f8563ddbe1d8d5542dbf868f274136/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d459b98a932296c6f0e94f87511a0b1b90a8a02c30a50e60a297619cd5a58ee", size = 1756576, upload-time = "2026-06-07T21:06:24.8Z" }, - { url = "https://files.pythonhosted.org/packages/ec/0b/8b3d5713373858ff71a617daf6e3b0e81ad63e79d09a3cf2f6b6b983939c/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:764457a7be60825fb770a644852ff717bcbb5042f189f2bd16df61a81b3f6573", size = 1754668, upload-time = "2026-06-07T21:06:26.528Z" }, - { url = "https://files.pythonhosted.org/packages/9f/49/fd564575cf225821d7ba5a117cb8bc27213d8a7e1811162afb43ae077039/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f7a16ef45b081454ef844502d87a848876c490c4cb5c650c230f6ec79ed2c1e7", size = 1817019, upload-time = "2026-06-07T21:06:28.297Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1b/e850c9ae6fc91356552ae668bb6c51e93fa29c8aef13398a10b56678557f/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2fbc3ed048b3475b9f0cbcb9978e9d2d3511acd91ead203af26ed9f0056004cf", size = 1631638, upload-time = "2026-06-07T21:06:30.242Z" }, - { url = "https://files.pythonhosted.org/packages/eb/94/3c337ba72451a89806ace6f75bddc92bafc5b8d53d90115a512858024b63/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bedb0cd073cc2dc035e30aeb99444389d3cd2113afe4ef9fcd23d439f5bade85", size = 1835660, upload-time = "2026-06-07T21:06:31.943Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9c/9c18cf367a0498212d9ba7daf990b504a5e8ae064cda4b504e2647c89c03/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b6feea921016eb3d4e04d65fc4e9ca402d1a3801f562aef94989f54694917af3", size = 1775698, upload-time = "2026-06-07T21:06:33.72Z" }, - { url = "https://files.pythonhosted.org/packages/b5/63/a251a9d2a6cb45065b2ddc0bde2b3dd10108740a9a42f632c66405a761a2/aiohttp-3.14.1-cp311-cp311-win32.whl", hash = "sha256:313701e488100074ce99850404ee36e741abf6330179fec908a1944ecf570126", size = 458386, upload-time = "2026-06-07T21:06:35.279Z" }, - { url = "https://files.pythonhosted.org/packages/17/ca/69274c51dcd6e8947d77b2806cf47a4a15f2c846e2cbeb1882547d3da283/aiohttp-3.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:03ab4530fdcb3a543a122ba4b65ac9919da9fe9f78a03d328a6e38ff962f7aa5", size = 483406, upload-time = "2026-06-07T21:06:36.824Z" }, - { url = "https://files.pythonhosted.org/packages/2c/8a/c25904f77690c3688ec140f87591ef11a0cfe36bf3d5c0f1f38056fb62b3/aiohttp-3.14.1-cp311-cp311-win_arm64.whl", hash = "sha256:486f7d16ed54c39c2cbd7ca71fd8ba2b8bb7860df65bd7b6ed640bab96a38a8b", size = 452987, upload-time = "2026-06-07T21:06:38.371Z" }, - { url = "https://files.pythonhosted.org/packages/1d/21/151624b51cd92553d95424daf4bf19f19ce9be9002d19253e7e7ce67197b/aiohttp-3.14.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d35143e27778b4bb0fb189562d7f275bff79c62ab8e98459717c0ea617ff2480", size = 757402, upload-time = "2026-06-07T21:06:40.311Z" }, - { url = "https://files.pythonhosted.org/packages/c2/82/280619e0bd7bf2454987e19282616e84762255dd9c8468f62382e8c191f1/aiohttp-3.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bcfb80a2cc36fba2534e5e5b5264dc7ae6fcd9bf15256da3e53d2f499e6fa29d", size = 512310, upload-time = "2026-06-07T21:06:42.207Z" }, - { url = "https://files.pythonhosted.org/packages/55/b2/2aac325583aaa1353045f96dffa586d8a34e8322e14a7ba49cffeb103ab4/aiohttp-3.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27fd7c91e51729b4f7e1577865fa6d34c9adccbc39aabe9000285b48af9f0ec2", size = 512448, upload-time = "2026-06-07T21:06:43.813Z" }, - { url = "https://files.pythonhosted.org/packages/8a/72/a60607cb849faa8af8a356c9329ea2eb6f395d49e82cc82ccba1fd8deb8f/aiohttp-3.14.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:64c567bf9eaf664280116a8688f63016e6b32db2505908e2bdaca1b6438142f2", size = 1766854, upload-time = "2026-06-07T21:06:45.391Z" }, - { url = "https://files.pythonhosted.org/packages/b5/d3/d9fe1c9ec7557ab4d0d82bebaa728c6418f0b93295ec2f4ab015f7710cc7/aiohttp-3.14.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f5e6ff2bdbb8f4cd3fbe41f99e25bbcd58e3bf9f13d3dd31a11e7917251cc77a", size = 1740884, upload-time = "2026-06-07T21:06:47.413Z" }, - { url = "https://files.pythonhosted.org/packages/c1/dc/f2cecfaf9337ba3e63f181500814ff502aa3d00d9c7ec93a9d23d10a27b2/aiohttp-3.14.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2f73e01dc37122325caf079982621262f96d74823c179038a82fddfc50359264", size = 1810034, upload-time = "2026-06-07T21:06:50.165Z" }, - { url = "https://files.pythonhosted.org/packages/66/d7/2ff65c5e65c0d7476daf7e15c032e0805e36811185b9623e3238ad6c763e/aiohttp-3.14.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb2c0c80d431c0d03f2c7dbf125150fedd4f0de17366a7ca33f7ccb822391842", size = 1904054, upload-time = "2026-06-07T21:06:52.035Z" }, - { url = "https://files.pythonhosted.org/packages/20/9c/d445818389df371f56d141d881153ba23183c4735a03f7356ffb43f7757d/aiohttp-3.14.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e6fc1a85fa7194a1a7d19f44e8609180f4a8eb5fa4c7ed8b4355f080fad235c", size = 1790278, upload-time = "2026-06-07T21:06:54.049Z" }, - { url = "https://files.pythonhosted.org/packages/4d/aa/bf04cb4d865fc6101c2229a294ad744973b72e513fdc5a6b791e6983d72a/aiohttp-3.14.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:686b6c0d3911ec387b444ddf5dc62fb7f7c0a7d5186a7861626496a5ab4aff95", size = 1591795, upload-time = "2026-06-07T21:06:55.911Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b4/4dac0038960427ba832f6609dfb4ea5437d7fd80c72001b9e48f834f428b/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c6fa4dc7ad6f8109c70bb1499e589f76b0b792baf39f9b017eb92c8a81d0a199", size = 1728397, upload-time = "2026-06-07T21:06:57.777Z" }, - { url = "https://files.pythonhosted.org/packages/2b/f9/7cd4e8ad7aa3b75f17d56bb5498dd604a93d4e6eece822ba0568c413fff0/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:87a5eea1b2a5e21e1ebdbb33ad4165359189327e63fc4e4894693e7f821ac817", size = 1766504, upload-time = "2026-06-07T21:07:00.009Z" }, - { url = "https://files.pythonhosted.org/packages/f9/df/fc01d9fcad0f73fed3f3d361f1f94f975947b50dff82919f6dc2bf4316cc/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c1421eb01d4fd608d88cc8290211d177a58532b55ad94076fb349c5bf467f0a", size = 1777806, upload-time = "2026-06-07T21:07:02.064Z" }, - { url = "https://files.pythonhosted.org/packages/41/09/47e2d090bddcc8fb4ccb4c314aadc32d7c5d9bb55f50f6ad1c92fc15d501/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:34b257ec41345c1e8f2df68fa908a7952f5de932723871eb633ecbbff396c9a4", size = 1580707, upload-time = "2026-06-07T21:07:03.942Z" }, - { url = "https://files.pythonhosted.org/packages/3d/36/f1a4ce904ae0b6930cfe9afc96d0896f7ec1a620c400405d63783bb95a9c/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:de538791a80e5d862addbc183f70f0158ac9b9bb872bb147f1fd2a683691e087", size = 1798121, upload-time = "2026-06-07T21:07:05.987Z" }, - { url = "https://files.pythonhosted.org/packages/70/0a/e0075ce9ca0279ee1d4f0c0b85f54fea02ebc83c3007651a72bece658fec/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f71173be42d3241d428f760122febb748de0623f44308a6f120d0dd9ec572e3", size = 1767580, upload-time = "2026-06-07T21:07:07.873Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/a0c0a8f327a9c52095cdd8e312391b00d3ed64ab6c72bb5c33d8ec251cf7/aiohttp-3.14.1-cp312-cp312-win32.whl", hash = "sha256:ec8dc383ee57ea3e883477dcca3f11b65d58199f1080acaf4cd6ad9a99698be4", size = 452771, upload-time = "2026-06-07T21:07:09.669Z" }, - { url = "https://files.pythonhosted.org/packages/df/d9/ea367c75f16ac9c6cdc8febb25e8318fa21a2b1bc8d6514d4b2d890bface/aiohttp-3.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2aa92c87868cd13674989f9ee83e5f9f7ea4237589b728048e1f0c8f6caa3271", size = 479873, upload-time = "2026-06-07T21:07:11.538Z" }, - { url = "https://files.pythonhosted.org/packages/03/64/8d96784a7851156db8a4c6c3f6f91042fdf39fb15a4cc38c8b3c14833c45/aiohttp-3.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:2c840c90759922cb5e6dda94596e079a30fb5a5ba548e7e0dc00574703940847", size = 448073, upload-time = "2026-06-07T21:07:13.637Z" }, - { url = "https://files.pythonhosted.org/packages/bc/97/bd137012dd97e1649162b099135a80e1fd59aaa807b2430fc448d1029aff/aiohttp-3.14.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:b3a03285a7f9c7b016324574a6d92a1c895da6b978cb8f1deee3ac72bc6da178", size = 506882, upload-time = "2026-06-07T21:07:15.501Z" }, - { url = "https://files.pythonhosted.org/packages/ef/79/e5cc690e9d922a66887ceeaca53a8ffd5a7b0be3816142b7abc433742d89/aiohttp-3.14.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:2a73f487ab8ef5abbb24b7aa9b73e98eaba9e9e031804ff2416f02eca315ccaf", size = 515270, upload-time = "2026-06-07T21:07:17.53Z" }, - { url = "https://files.pythonhosted.org/packages/fe/22/a73ccbf9dbd6e26dda0b24d5fd5db7da92ee3383a79f47677ffb834c5c5b/aiohttp-3.14.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:915fbb7b41b115192259f8c9ae58f3ddc444d2b5579917270211858e606a4afd", size = 485841, upload-time = "2026-06-07T21:07:19.555Z" }, - { url = "https://files.pythonhosted.org/packages/3b/b9/57ed8eaf596321c2ad747bd480fb1700dbd7177c60dfc9e4c187f629662e/aiohttp-3.14.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:7fb4bdf95b0561a79f259f9d28fbc109728c5ee7f27aff6391f0ca703a329abe", size = 492088, upload-time = "2026-06-07T21:07:21.581Z" }, - { url = "https://files.pythonhosted.org/packages/78/c0/5ebe5270a7c140d7c6f79dcb018640225f14d406c149e4eec04a7d82fe71/aiohttp-3.14.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1b9748363260121d2927704f5d4fc498150669ca3ae93625986ee89c8f80dcd4", size = 501564, upload-time = "2026-06-07T21:07:23.388Z" }, - { url = "https://files.pythonhosted.org/packages/75/7f/8cdaa24fc7983865e0915153b96a9ac5bcdd3548d64c5a27d17cecccad2d/aiohttp-3.14.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:86a6dab78b0e43e2897a3bbe15745aa60dc5423ca437b7b0b164c069bf91b876", size = 751998, upload-time = "2026-06-07T21:07:25.046Z" }, - { url = "https://files.pythonhosted.org/packages/b2/f4/c4227aacfacc5cb0cc2d119b65301d177912a6842cd64e120c47af76064f/aiohttp-3.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4dfd6e47d3c44c2279907607f73a4240b88c69eb8b90da7e2441a8045dfd21da", size = 510918, upload-time = "2026-06-07T21:07:27.28Z" }, - { url = "https://files.pythonhosted.org/packages/ab/01/a2d5f96cd4e74424864d30bc0a7e44d0a12dacdcfa91b5b2d1bd3dca6bf3/aiohttp-3.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:317acd9f8602858dc7d59679812c376c7f0b97bcbbf16e0d6237f54141d8a8a6", size = 508657, upload-time = "2026-06-07T21:07:29.252Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ed/3c0fb5c500fdd8e7ebc10d1889c04384fffa1a9163eac1356088ca9da1b1/aiohttp-3.14.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd869c427324e5cb15195793de951295710db28be7d818247f3097b4ab5d4b96", size = 1757907, upload-time = "2026-06-07T21:07:31.03Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ab/d4c924d9bd5be3050c226612413ce68cb54c70d2c31b661bfc8d9a5b6a70/aiohttp-3.14.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93b032b5ec3255473c143627d21a69ac74ae12f7f33974cb587c564d11b1066f", size = 1737565, upload-time = "2026-06-07T21:07:33.031Z" }, - { url = "https://files.pythonhosted.org/packages/19/2a/37326821ff779084020cdc33224d20b19f42f4183a500ff92022a739eda7/aiohttp-3.14.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f234b4deb12f3ad59127e037bc57c40c21e45b45282df7d3a55a0f409f595296", size = 1799018, upload-time = "2026-06-07T21:07:35.003Z" }, - { url = "https://files.pythonhosted.org/packages/b3/4f/6e947ba73e4ce09070761c05ed3a8ceb7c21f5e46798671d8b2aac0e4626/aiohttp-3.14.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9af6779bfb46abf124068327abcdf9ce95c9ef8287a3e8da76ccf2d0f16c28fa", size = 1894416, upload-time = "2026-06-07T21:07:36.956Z" }, - { url = "https://files.pythonhosted.org/packages/9d/6e/dbf1d0625dc711fb2851f4f3c3055c39ed58bae92082d8c627dbe6013736/aiohttp-3.14.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:faccab372e66bc76d5731525e7f1143c922271725b9d38c9f97edcc66266b451", size = 1783881, upload-time = "2026-06-07T21:07:39.063Z" }, - { url = "https://files.pythonhosted.org/packages/44/c2/5e25098a67268ed369483ae7d1a58bd0a13d03aab860d2a0e4a6eb25b046/aiohttp-3.14.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f380468b09d2a81633ee863b0ec5648d364bd17bb8ecfb8c2f387f7ac1faf42c", size = 1587572, upload-time = "2026-06-07T21:07:41.058Z" }, - { url = "https://files.pythonhosted.org/packages/2a/bd/cf9cee17e140f942a3de73e658a543aa8fbf35a5fc67a9d2538d52d77f0b/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:97e704dcd26271f5bda3fa07c3ce0fb76d6d3f8659f4baa1a24442cc9ba177ca", size = 1722137, upload-time = "2026-06-07T21:07:43.014Z" }, - { url = "https://files.pythonhosted.org/packages/89/6d/5684f8c59045c96f81a18cefbc1fbbd79d25b88f1c622f2a5c5c08fcb632/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:269b76ac5394092b95bc4a098f4fc6c191c083c3bd12775d1e30e663132f6a09", size = 1755953, upload-time = "2026-06-07T21:07:45.933Z" }, - { url = "https://files.pythonhosted.org/packages/a8/40/35caf3170f8359760740a7d9aa0fff2e344bef98e1d1186f5a0f6dec17e6/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0b3e614340c889d575451696374c9d17affd54cd607ca0babed8f8c37b9397", size = 1766479, upload-time = "2026-06-07T21:07:48.047Z" }, - { url = "https://files.pythonhosted.org/packages/6d/a1/b0c61e7a137f0d81de49a82023a6df73c3c16d6fefb0f8e4a93d21639002/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5663ee9257cfa1add7253a7da3035a02f31b6600ec48261585e1800a81533080", size = 1580077, upload-time = "2026-06-07T21:07:50.069Z" }, - { url = "https://files.pythonhosted.org/packages/0b/41/194ea4623693009fcefebef7aef63c141754f153e9cd0d39d3b9e36c175c/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:603a2c834142172ffddc054067f5ec0ca65d57a0aa98a71bc81952573208e345", size = 1791688, upload-time = "2026-06-07T21:07:52.106Z" }, - { url = "https://files.pythonhosted.org/packages/ba/45/4de841f005cfe1fd63e2a2fe011262c515e2a62aa6994b15947e7d717ac9/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cb21957bb8aca671c1765e32f58164cf0c50e6bf41c0bbbd16da20732ecaf588", size = 1761094, upload-time = "2026-06-07T21:07:54.113Z" }, - { url = "https://files.pythonhosted.org/packages/e4/ae/dbce10533d3896d544d5053939ed75b7dc31a1b0973d959b1b5ae21028d6/aiohttp-3.14.1-cp313-cp313-win32.whl", hash = "sha256:e509a55f681e6158c20f70f102f9cf61fb20fbc382272bc6d94b7343f2582780", size = 452662, upload-time = "2026-06-07T21:07:56.06Z" }, - { url = "https://files.pythonhosted.org/packages/7b/d9/0bf1a19362c32f06229da5e7ddfcec91f93474d6307f7a2d3135e9c674dc/aiohttp-3.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:1ac8531b638959718e18c2207fbfe297819875da46a740b29dfa29beba64355a", size = 479748, upload-time = "2026-06-07T21:07:58.319Z" }, - { url = "https://files.pythonhosted.org/packages/22/0a/62e7232dc9484fbec112ceb32efb6a624cc7994ec6e2b019286f17c4e8f2/aiohttp-3.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:250d14af67f6b6a1a4a811049b1afa69d61d617fca6bf33149b3ab1a6dbcf7b8", size = 447723, upload-time = "2026-06-07T21:08:00.154Z" }, - { url = "https://files.pythonhosted.org/packages/c4/a1/5fafa04e1ca91ddb47608699d60649c1c6db3cf41c99e78fc4056f9513db/aiohttp-3.14.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:7c106c26852ca1c2047c6b80384f17100b4e439af276f21ef3d4e2f450ae7e15", size = 508531, upload-time = "2026-06-07T21:08:02.093Z" }, - { url = "https://files.pythonhosted.org/packages/fa/2e/bfa02f699d87ffc86d5959270b28f1cb410add3ccaced8ed2e0b8a5238fc/aiohttp-3.14.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:20205f7f5ade7aaec9f4b500549bbc071b046453aed72f9c06dcab87896a83e8", size = 514718, upload-time = "2026-06-07T21:08:04.476Z" }, - { url = "https://files.pythonhosted.org/packages/85/a5/9594ad6289eebbc97d167c44213d557807f90e59115caad24de21ad2c3b1/aiohttp-3.14.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:62a759436b29e677181a9e76bab8b8f689a29cb9c535f45f7c48c9c830d3f8c3", size = 487918, upload-time = "2026-06-07T21:08:06.377Z" }, - { url = "https://files.pythonhosted.org/packages/b4/61/16a32c36c3c49edec122a3dc811f2057df2f94d3b14aa107c8017d981618/aiohttp-3.14.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:2964cbf553df4d7a57348da44d961d871895fc1ee4e8c322b2a95612c7b17fba", size = 494014, upload-time = "2026-06-07T21:08:08.263Z" }, - { url = "https://files.pythonhosted.org/packages/9b/89/3ebcf96ed99c05bec9c434aaac6963fd3cbab4a786ae739908a144d9ce44/aiohttp-3.14.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:237651caadc3a59badd39319c54642b5299e9cc98a3a194310e55d5bb9f5e397", size = 502398, upload-time = "2026-06-07T21:08:10.244Z" }, - { url = "https://files.pythonhosted.org/packages/fd/3d/b74870a0c2d40c355928cd5b96c7a11fa821b8a40fc41365e64479b151fb/aiohttp-3.14.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:896e12dfdbbab9d8f7e16d2b28c6769a60126fa92095d1ebf9473d02593a2448", size = 758018, upload-time = "2026-06-07T21:08:12.447Z" }, - { url = "https://files.pythonhosted.org/packages/d3/66/f42f5c984d99e49c6cff5f26f590750f2e2f7ef1fcfb99966ab5be1b632e/aiohttp-3.14.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d03f281ed22579314ba00821ce20115a7c0ac430660b4cc05704a3f818b3e004", size = 512462, upload-time = "2026-06-07T21:08:14.624Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a7/248e1aebe0c7810b0271e021a0f2a5eb6e78a051885b3c9df49f42a5802d/aiohttp-3.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07eabb979d236335fed927e137a928c9adfb7df3b9ec7aa31726f133a62be983", size = 512824, upload-time = "2026-06-07T21:08:16.572Z" }, - { url = "https://files.pythonhosted.org/packages/26/97/2aa0e5ba0727dc3bd5aaebb7ccbc510f7dfb7fb961ec87497cd496635ab1/aiohttp-3.14.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4fe1f1087cbadb280b5e1bb054a4f00d1423c74d6626c5e48400d871d34ecefe", size = 1749898, upload-time = "2026-06-07T21:08:18.635Z" }, - { url = "https://files.pythonhosted.org/packages/00/8d/e97f6c96c891d457c8479d92a514ba194d0412f981d72c70341ee18488ed/aiohttp-3.14.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:367a9314fdc79dab0fac96e216cb41dd73c85bdca85306ce8999118ba7e0f333", size = 1710114, upload-time = "2026-06-07T21:08:20.892Z" }, - { url = "https://files.pythonhosted.org/packages/6f/e6/aa8d7e863048c8fceb5cd6ce74017311cec3ead07847387e12265fb4444e/aiohttp-3.14.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a24f677ebe83749039e7bdf862ff0bbb16818ae4193d4ef96505e269375bcce0", size = 1802541, upload-time = "2026-06-07T21:08:23.044Z" }, - { url = "https://files.pythonhosted.org/packages/83/a8/72193137de57fda4ebfae4563182d082c8856e3b6e9871d0b46f028fb369/aiohttp-3.14.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c83afe0ba876be7e943d2e0ba645809ad441575d2840c895c21ee5de93b9377a", size = 1875776, upload-time = "2026-06-07T21:08:25.288Z" }, - { url = "https://files.pythonhosted.org/packages/a0/18/938441025db6769a3464596b2410af3afde0b21eb2f204c6f766f68af4bd/aiohttp-3.14.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:634e385930fb6d2d479cf3aa66515955863b77a5e3c2b5894ca259a25b308602", size = 1760329, upload-time = "2026-06-07T21:08:27.363Z" }, - { url = "https://files.pythonhosted.org/packages/60/29/bf2496b4065e76e09fe48015aaffe5ce161d8f089b06ac6982070f653076/aiohttp-3.14.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeea07c4397bbc57719c4eed8f9c284874d4f175f9b6d57f7a1546b976d455ca", size = 1587293, upload-time = "2026-06-07T21:08:29.805Z" }, - { url = "https://files.pythonhosted.org/packages/49/a2/2136674d52123b1354bd05dd5753c318db47dc0c927cc70b27bab3755456/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:335c0cc3e3545ce98dcb9cfcb836f40c3411f43fa03dab757597d80c89af8a35", size = 1714756, upload-time = "2026-06-07T21:08:32.094Z" }, - { url = "https://files.pythonhosted.org/packages/a7/b9/e5fd2e6f915503081c0f9b1e8540947037929c70c191da2e4d54b31a21a1/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:ae6be797afdef264e8a84864a85b196ca06045586481b3df8a967322fd2fa844", size = 1721052, upload-time = "2026-06-07T21:08:34.167Z" }, - { url = "https://files.pythonhosted.org/packages/63/5a/2833e324a2263e104e31e2e91bc5bbee81bc499afd32203faee048a883f0/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:8560b4d712474335d08907db7973f71912d3a9a8f1dee992ec06b5d2fe359496", size = 1766888, upload-time = "2026-06-07T21:08:36.95Z" }, - { url = "https://files.pythonhosted.org/packages/57/fa/dea6511870913162f3b2e8c42a7614eb203a4540b8c2da43e0bfb0548f3c/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7edd08e0a5deb1e8564a2fcd8f4561014a3f05252334671bbf55ddd47db0e5", size = 1581679, upload-time = "2026-06-07T21:08:39.292Z" }, - { url = "https://files.pythonhosted.org/packages/14/bd/3cf0d55e71784b33534e9710a67d382d900598b4787fbce6cc7317f8c42a/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:b6ff7fcee63287ae57b5df3e4f5957ce032122802509246dec1a5bcc55904c95", size = 1782021, upload-time = "2026-06-07T21:08:41.407Z" }, - { url = "https://files.pythonhosted.org/packages/c1/af/14bb5843eccbe234f4dfb78ab73e549d99727247e62ae5d62cbd22eaf5b0/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6ffbb2f4ec1ceaff7e07d43922954da26b223d188bf30658e561b98e23089444", size = 1742574, upload-time = "2026-06-07T21:08:43.795Z" }, - { url = "https://files.pythonhosted.org/packages/f2/1e/fbeb7af9210a67ac0f9c9bec0f8f4568497924e33137a3d5b48e1cf85f3f/aiohttp-3.14.1-cp314-cp314-win32.whl", hash = "sha256:a9875b46d910cff3ea2f5962f9d266b465459fe634e22556ab9bd6fc1192eea0", size = 457773, upload-time = "2026-06-07T21:08:46.168Z" }, - { url = "https://files.pythonhosted.org/packages/f0/2b/13e8d741a9ec5db7d900c060554cf8352ab85e44e2a4469ebb9d377bda17/aiohttp-3.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:af8b4b81a960eeaf1234971ac3cd0ba5901f3cd42eae42a46b4d089a8b492719", size = 485001, upload-time = "2026-06-07T21:08:48.401Z" }, - { url = "https://files.pythonhosted.org/packages/df/30/491acfa2c4d6c3ff59c49a14fc1b50be3241e25bbb0c84c09e2da4d11395/aiohttp-3.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:cf4491381b1b57425c315a56a439251b1bdac07b2275f19a8c44bc57744532ec", size = 453809, upload-time = "2026-06-07T21:08:50.7Z" }, - { url = "https://files.pythonhosted.org/packages/34/e3/19dbe1a1f4cc6230eb9e314de7fe68053b0992f9302b27d12141a0b5db53/aiohttp-3.14.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:819c054312f1af92947e6a55883d1b66feefab11531a7fc45e0fb9b63880b5c2", size = 793320, upload-time = "2026-06-07T21:08:52.775Z" }, - { url = "https://files.pythonhosted.org/packages/7f/20/1b7182219ba1b108430d6e4dc53d25ae02dcfcf5a045b33af4e8c5167527/aiohttp-3.14.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10ee9c1753a8f706345b22496c79fbddb5be0599e0823f3738b1534058e25340", size = 529077, upload-time = "2026-06-07T21:08:55Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c8/14ce60ec31a2e5f5274bb17d383a6f7a3aabca31ac04eee05585bbadab16/aiohttp-3.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1601cc37baf5750ccacae618ec2daf020769581695550e3b654a911f859c563d", size = 532476, upload-time = "2026-06-07T21:08:57.176Z" }, - { url = "https://files.pythonhosted.org/packages/7e/02/9ac85e081e53da2e061b02fa7758fe0a12d17b8ce2d1f5e6c7cb76730328/aiohttp-3.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d6e0ac9da31c9c04c84e1c0182ad8d6df35965a85cae29cd71d089621b3ae94", size = 1922347, upload-time = "2026-06-07T21:08:59.563Z" }, - { url = "https://files.pythonhosted.org/packages/c0/3e/d3ba07a0ab38b5389e10bec4362d21e10a4f667cba2d79ba30837b3a5059/aiohttp-3.14.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9e8f2d660c350b3d0e259c7a7e3d9b7fc8b41210cbcc3d4a7076ff0a5e5c2fdc", size = 1786465, upload-time = "2026-06-07T21:09:01.909Z" }, - { url = "https://files.pythonhosted.org/packages/0b/cb/e2ee978a00cfb2df829704a69528b18154eba5939f45bc1efa8f33aee4c5/aiohttp-3.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4691802dda97be727f79d86818acaad7eb8e9252626a1d6b519fedbb92d5e251", size = 1909423, upload-time = "2026-06-07T21:09:04.357Z" }, - { url = "https://files.pythonhosted.org/packages/73/5d/1430334858b1022b58ae50399a918f0bd6fe8fa7fa183598d657ff61e040/aiohttp-3.14.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c389c482a7e9b9dc3ee2701ac46c4125297a3818875b9c305ddb603c04828fd1", size = 2001906, upload-time = "2026-06-07T21:09:06.722Z" }, - { url = "https://files.pythonhosted.org/packages/66/4e/560c7472d3d198a23aa5c8b19a5115bf6a9b77b7d3e4bb363da320430ad2/aiohttp-3.14.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc0cacab7ba4e56f0f81c82a98c09bed2f39c940107b03a34b168bdf7597edd3", size = 1877095, upload-time = "2026-06-07T21:09:09.011Z" }, - { url = "https://files.pythonhosted.org/packages/0d/f1/4745806578d447db4a784a8591e2dae3afdfc2bcb96f8f81271b13df6543/aiohttp-3.14.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:979ed4717f59b8bb12e3963378fa285d93d367e15bcd66c721311826d3c44a6c", size = 1676222, upload-time = "2026-06-07T21:09:11.461Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c9/48255813cca749a229ef0ab476004ec623728ad79a9c0840616f6c076325/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:38e1e7daaea81df51c952e18483f323d878499a1e2bfe564790e0f9701d6f203", size = 1842922, upload-time = "2026-06-07T21:09:14.118Z" }, - { url = "https://files.pythonhosted.org/packages/3d/c0/bbd054e2bee909f529523a5af3891052606af5143c09f5f183ec3b234676/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:4132e72c608fe9fecb8f409113567605915b83e9bdd3ea56538d2f9cd35002f1", size = 1825035, upload-time = "2026-06-07T21:09:16.447Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ae/90395d4376deceb74e09ec26b6adf7d2015a6f8802d6d84446af860fef04/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:eefd9cc9b6d4a2db5f00a26bc3e4f9acf71926a6ec557cd56c9c6f27c290b665", size = 1849512, upload-time = "2026-06-07T21:09:18.742Z" }, - { url = "https://files.pythonhosted.org/packages/93/bd/fb25f3049957553d4ce0ba6ae480aa2f592a6985497fca590837d16c1be0/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b165790117eea512d7f3fb22f1f6dad3d55a7189571993eb015591c1401276d1", size = 1668571, upload-time = "2026-06-07T21:09:21.458Z" }, - { url = "https://files.pythonhosted.org/packages/3f/22/7f73303d64dd567ff3addca90b556690ed1233a47b8f55d242fb90af3681/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ed09c7eb1c391271c2ed0314a51903e72a3acb653d5ccfc264cdf3ef11f8269d", size = 1881159, upload-time = "2026-06-07T21:09:23.813Z" }, - { url = "https://files.pythonhosted.org/packages/44/be/0474c5a8b5640e1e4aa1923430a91f4151be82e511373fe764189b89aef5/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:99abd37084b82f5830c635fddd0b4993b9742a66eb746dacf433c8590e8f9e3c", size = 1841409, upload-time = "2026-06-07T21:09:26.207Z" }, - { url = "https://files.pythonhosted.org/packages/7b/3c/bb4a7cba26956cb3da4553cc2056cf67be5b5ff6e6d8fa4fbdff73bfb7ae/aiohttp-3.14.1-cp314-cp314t-win32.whl", hash = "sha256:47ddf841cdecc810749921d25606dee45857d12d2ad5ddb7b5bd7eab12e4b365", size = 494166, upload-time = "2026-06-07T21:09:28.505Z" }, - { url = "https://files.pythonhosted.org/packages/8a/84/ec80c2c1f66a952555a9f86df6b33af65108a6febfa0471b69013a12f807/aiohttp-3.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5e78b522b7a6e27e0b25d19b247b75039ac4c94f99823e3c9e53ae1603a9f7e9", size = 530255, upload-time = "2026-06-07T21:09:30.843Z" }, - { url = "https://files.pythonhosted.org/packages/2a/71/6e22be134a4061ada85a92951b842f2657f17d926b727f3f94c56ae963d6/aiohttp-3.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:90d53f1609c29ccc2193945ef732428382a28f78d0456ae4d3daf0d48b74f0f6", size = 469640, upload-time = "2026-06-07T21:09:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4d/4a99fb425c5e0cad715eea7bd190aff46f38b959a0a2dadb993705d34b26/aiohttp-3.14.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eb0495d778817619273c108784292be161a924b9f5ae5cbbc70a2caa6838250b", size = 765848, upload-time = "2026-07-23T01:52:08.217Z" }, + { url = "https://files.pythonhosted.org/packages/74/e8/43b85dc55b8e950dc644babe762add781319ea881b57b33d2cce12017d12/aiohttp-3.14.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c3c200cf9757edd785051dc699c7ecbec22110dbfcb3fefc7a9f9695eda8ea7a", size = 517476, upload-time = "2026-07-23T01:52:10.846Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9e/73b582c4dbbc3c12ef4473822475effaabf1f934b56f14f5b03fe5d3a2af/aiohttp-3.14.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd51ebf9d3a00c074df4ede271023f4d2dba289bcc740b88191872716014e3c5", size = 515334, upload-time = "2026-07-23T01:52:12.636Z" }, + { url = "https://files.pythonhosted.org/packages/79/03/e98c3c9e05a5bdf97defe5ff9169baba4f0ec9a901f2d60e0f060c2f051e/aiohttp-3.14.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:134ac5ddcf61c6fad984b9a5727d83492ada43d63471db20fb73042c13fca62f", size = 1708830, upload-time = "2026-07-23T01:52:14.538Z" }, + { url = "https://files.pythonhosted.org/packages/d7/2c/26e60b694844dfd2176c57f913a22d0cd6a16f9ff202cbda7580d0328b98/aiohttp-3.14.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:70c987b27534f9ae1a723f47ae921571d616da21d3208282bf4c52af5164ac43", size = 1674012, upload-time = "2026-07-23T01:52:16.486Z" }, + { url = "https://files.pythonhosted.org/packages/38/65/672df92e3172cd876aacfa97a952ac560877eb169384b2991ac5b273de4c/aiohttp-3.14.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1b59533861b70a2185c8f4f350f791f39d64358ef6944ce71c5240c9ec0982c9", size = 1767015, upload-time = "2026-07-23T01:52:18.28Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c5/228dec7bfec1c373cc2217cdeb47d6456dcd7a13a4c55144930a75ae3851/aiohttp-3.14.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1c5281acc88b92396f88c7e1e2748f8466689df22b80170e4f51efa712fb47a8", size = 1858700, upload-time = "2026-07-23T01:52:20.08Z" }, + { url = "https://files.pythonhosted.org/packages/bd/ff/cb36724e8c8d17f90ada567a9ff3efe1d6e9b549fba697a242aece180f21/aiohttp-3.14.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48d67b87db6279c044760787eb01f6413032c2e6f3ba1cafaa492b1c8e578479", size = 1714075, upload-time = "2026-07-23T01:52:22.071Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3a/296a4135c6366376263aeef54b15caca1f07676c2ae0c525d7832f2f808a/aiohttp-3.14.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f53bcd52f585e1ac3e590d61434eb61f9a88c38df041b4ea126d97144344a77b", size = 1588234, upload-time = "2026-07-23T01:52:23.757Z" }, + { url = "https://files.pythonhosted.org/packages/7d/81/9d5d853ef892dc066d1eb6db0e87a47348b920c1c879aa554612fdbd9d79/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0fdea2281997af69da84c77ffa6f5938a0285f21fb3887c249d67419ca865b3d", size = 1677300, upload-time = "2026-07-23T01:52:25.861Z" }, + { url = "https://files.pythonhosted.org/packages/68/96/021d386ae32d9b26d4b88df2e794546232ff56bb6be952bf6be227c0bbc7/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cda5fd5c95ad7a125a2e8464acc78b98b94c475a3780d6aa0aa157c93f470f4d", size = 1691501, upload-time = "2026-07-23T01:52:28Z" }, + { url = "https://files.pythonhosted.org/packages/29/9f/af66adce26a14af135c003cbd0f44ccaa68cebd30ff8ac99ca47fb4958f7/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:6debfa7312ff9d4c124dc71d72e9a0a4b9e0879e48ba6fcb42bef5c3300289e2", size = 1735113, upload-time = "2026-07-23T01:52:29.995Z" }, + { url = "https://files.pythonhosted.org/packages/2f/90/28c390d4c9851effe52ac25b5a2e1d92246acd00728b4fc7975dafb67484/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:f4e05329faa0ea1a404b37de4f034fd2c2defcca06a68dc6745e4e56c88e8a48", size = 1577486, upload-time = "2026-07-23T01:52:31.937Z" }, + { url = "https://files.pythonhosted.org/packages/db/c2/00e23a1bf2abb70dd353f6987db7e7f2491d0261f7363997738c71c98f95/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a3a8296e7ab5c295f53f1041487cb088e1480775aafbf7fe545d93b770a0f96f", size = 1751353, upload-time = "2026-07-23T01:52:33.688Z" }, + { url = "https://files.pythonhosted.org/packages/6e/7d/d51a706a8cbfa57f0611127daf61ab3ae02ab8420b0407412079227d1c65/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5373dc80ad1aa2fb9ad95c83f24eef418bbda3a61375f128e5b0192e4f3f9b32", size = 1698681, upload-time = "2026-07-23T01:52:38.167Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b0/90bd5cd9fdd9787cb4211d284d1fb8401339a933cb0227a15b71e789232f/aiohttp-3.14.3-cp310-cp310-win32.whl", hash = "sha256:a3e22975f905b89a55a488c2a08f2fdb2186175349e917d48985cc468a3d4c6e", size = 456733, upload-time = "2026-07-23T01:52:41.823Z" }, + { url = "https://files.pythonhosted.org/packages/d8/15/fe5b8f6a71ae112bc677163d0b0701bda5dc15005249582258ede0eb88c7/aiohttp-3.14.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdd0e2834dce1a26c1bbe26464861e16bbe217042cbff619247c11594472518c", size = 480460, upload-time = "2026-07-23T01:52:43.905Z" }, + { url = "https://files.pythonhosted.org/packages/54/00/45e98b6645cd7f00a4b78b749ebd309094b0eaeb2d2e96157eadbc0d0050/aiohttp-3.14.3-cp310-cp310-win_arm64.whl", hash = "sha256:eac645b09bcfdf73df7536331f0678c1086ea250981118ddb5199e17ccef72bb", size = 453479, upload-time = "2026-07-23T01:52:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5c/b3e4ff8ad43a8afef9602c5e90285936da1beaea8b029016b793891f03c3/aiohttp-3.14.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e568e14940c09955aa51f4e645b6daa18a581c5dcfcd73744dcc86a856e3ced3", size = 764250, upload-time = "2026-07-23T01:52:48.525Z" }, + { url = "https://files.pythonhosted.org/packages/0e/da/f1b384465e51449d844056b75070461da03a9a23e6c1747003695bf4172a/aiohttp-3.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:54cfcdee2770dac994417cbb0ee1f3eb0e7cb6b30c79bf44f2c02ff79ec5124a", size = 516281, upload-time = "2026-07-23T01:52:51.047Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3f/01264f820ee2e3712a827892b1cd6ff80f3300c1fcbffbb45714a915d47a/aiohttp-3.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:21c016079415ed3fd676963e9793700a566d85dbbd6bfc564b9b2d209147dcc8", size = 514742, upload-time = "2026-07-23T01:52:53.779Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8d/a71c6f2db52ac1ed142b133f7feddaa6b70539c3f4de24d7e226c95b794c/aiohttp-3.14.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6088ec9894113802bddb3c09e974929aed2c7b3a8c456219b8aab4481f1a239", size = 1780613, upload-time = "2026-07-23T01:52:56.948Z" }, + { url = "https://files.pythonhosted.org/packages/a5/11/3dd9b3fb3a170f6ec9011b5291d876a6fab4086714c9e158600edf01b4fd/aiohttp-3.14.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:16ea7e24c309fb7c0bbd505d149abe4fe4dccfb8db911db7dbec0921bc889a6f", size = 1737688, upload-time = "2026-07-23T01:52:59.294Z" }, + { url = "https://files.pythonhosted.org/packages/6d/3e/834c26918be7d88068822b40e0db30fca50b5f4fe79104aa16a93f1d74e6/aiohttp-3.14.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56f355e79f71aef2a85c80305cc915f894b170dba76de5fe84f6351939b83c06", size = 1845742, upload-time = "2026-07-23T01:53:01.641Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c9/49ab8572df7d66bc13d11e31f781292badb04180dd87ba98733066c6aed7/aiohttp-3.14.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:18c441d0a8fca6de8d1f546849b9f0ab20d435993e2c5b59562b2fae6be2f929", size = 1928412, upload-time = "2026-07-23T01:53:04.018Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b9/2b8f0c0ce09c87a1daf80fd483431b56b1435d3f62789bc86f572e1245de/aiohttp-3.14.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53e7b4ce82b54a8bcc71b3b67a5cbd177ca1d7f592cbc92cd38b7349f73482db", size = 1786220, upload-time = "2026-07-23T01:53:06.481Z" }, + { url = "https://files.pythonhosted.org/packages/85/00/9c45f81de11710460edfa1dc81317b6e882703b160926c879a9d20da9fcc/aiohttp-3.14.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f55119f7bf25f49ed210f6096090715da24f2943c62102448915fde3c62877ce", size = 1637231, upload-time = "2026-07-23T01:53:10.258Z" }, + { url = "https://files.pythonhosted.org/packages/19/ce/967d628e910756f3539c6107cb7844a1b69440dcb3029a5ee7871b09ab63/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9aa6e61fdf20105c4144e755bd586008ff450791d67b1c8146fdc15959c4d51c", size = 1753161, upload-time = "2026-07-23T01:53:13.817Z" }, + { url = "https://files.pythonhosted.org/packages/11/b2/0c3d4114f0aee4f580f5b3b4eb71b24d7a23b834ea506a4dfebe76513f35/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ccd4893707b3e2a13e39c90d43cf80edf2e4d0457935bcc103bf2346214c3f15", size = 1756356, upload-time = "2026-07-23T01:53:16.211Z" }, + { url = "https://files.pythonhosted.org/packages/63/5d/99e7d91c82f1399d1ae2a854e080bd1493fbc31e5e959dbc4ec33dac3bec/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b2466434105a4e03113c36ec775cc2ebe6676b62eae326fa670bb607ef788c1c", size = 1819846, upload-time = "2026-07-23T01:53:18.289Z" }, + { url = "https://files.pythonhosted.org/packages/ad/05/d5e1cb6480eeffd3f901d40a2c5e2d1e7effdc797837da3b490272699f13/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ba59d59aba08ac02fc03b0c8983ccd5ee39a199d0552ce9e6d2b4845b34d59ae", size = 1628531, upload-time = "2026-07-23T01:53:23.86Z" }, + { url = "https://files.pythonhosted.org/packages/c9/90/b934682bcaefae18a9e04f3dff5b68522ba810906358ae5029b68110ea3b/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ed099d105449c4f9e84f24af203cd131349d4761d8813fa7e02c32e7128cd910", size = 1832712, upload-time = "2026-07-23T01:53:27.551Z" }, + { url = "https://files.pythonhosted.org/packages/21/df/6061679faaf81fac746e7307c7adb71e858071a5d34c27583afefc64f543/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:152516815ef926786a0b6ae2b8f1fd2e0c71582dee0b435636865316fd4891b7", size = 1775014, upload-time = "2026-07-23T01:53:30.223Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1d/f854878bbc69b88faefe924b619a34a6f59ec05fd387c77690667eaa75eb/aiohttp-3.14.3-cp311-cp311-win32.whl", hash = "sha256:a4af35c443e0b1a1bd6a8af3f3485d7fda15c142751a00f3ff8090f0b93346fa", size = 456006, upload-time = "2026-07-23T01:53:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/73/0c/2af9d1674baccd1dbd47282a93d660a22e57ef6167c856deb24b4214fbab/aiohttp-3.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:e1e74298bab6ee0d6e749ed4fd1901c7e604bdda32c03d787a2cc71c46d0433d", size = 481069, upload-time = "2026-07-23T01:53:39.673Z" }, + { url = "https://files.pythonhosted.org/packages/8e/76/88401ff3fc95e85c5fc38d588f36f55e61ecb64343b2bc8d69326f453cc0/aiohttp-3.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:03cd2bde3d7f085b64e549c985f4bb928cad7e8ecf5323bfca320db548d81b39", size = 453021, upload-time = "2026-07-23T01:53:43.749Z" }, + { url = "https://files.pythonhosted.org/packages/18/d4/eb96299230e20acf2efae207cb8d69051f1f68e357e5ea5e479bf6fb097a/aiohttp-3.14.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5", size = 754690, upload-time = "2026-07-23T01:53:47.332Z" }, + { url = "https://files.pythonhosted.org/packages/88/11/e7a70a209eb9a067c0d3212b518a0134e3484f5178c7533878b6b514d469/aiohttp-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228", size = 509484, upload-time = "2026-07-23T01:53:51.159Z" }, + { url = "https://files.pythonhosted.org/packages/30/07/4bbc222cc8dbe31d4c3e8a5baad2286e4d42026ac0c570027b89afce6344/aiohttp-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee", size = 511949, upload-time = "2026-07-23T01:53:55.083Z" }, + { url = "https://files.pythonhosted.org/packages/54/b9/42e74c46b7b7c794b995bbc1f573fb48950c38b19d8600c62a6804ee2d67/aiohttp-3.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f631fe87a6f30df5fbe6d79640b25e4cffb38c31c7fb6f10871517b84b0f8c1a", size = 1765282, upload-time = "2026-07-23T01:53:59.662Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ed/62bc4d74363ad346d518e0720363a949f63e2e23439a79eb5813d4d29bb3/aiohttp-3.14.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a94dbaae5ae27bd849c93570669bff91e0510f33a80805738e3de72a7be0447b", size = 1741511, upload-time = "2026-07-23T01:54:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9f/181e8a8bc79e47d13c7fc4540bd7a3b729d9505609c61f392a8dd2fbfe55/aiohttp-3.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8f2f1c4c032c7cedd7d8da6f54c97b70266c6570c3108d3fdffee7188bb70529", size = 1810680, upload-time = "2026-07-23T01:54:09.882Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9a/dec94d6ad694552fe3424e3f1928d7a606a5d9d9433a04e7ecdd9d38ae7f/aiohttp-3.14.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea05e1f97ceea523942d9b2a7d7c0359d781d683d6b043f5943a602b14da4787", size = 1905646, upload-time = "2026-07-23T01:54:13.475Z" }, + { url = "https://files.pythonhosted.org/packages/52/b7/7cd31f29d6055bd711ae6e669367fba6f5ae9de463910a793e30556a8db7/aiohttp-3.14.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:543906c127fb1d929b95076db19b83fa2d46751006ff1e23b093aa5ac4d8db42", size = 1792122, upload-time = "2026-07-23T01:54:15.752Z" }, + { url = "https://files.pythonhosted.org/packages/66/73/10b1ef93afa61f4963c746257b70ced619cf31a4798671de5fdb2608501d/aiohttp-3.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0a5ff2dfbb9ce645fa5b8ef3e02c6c0b9cc3f6030ff863d0c51fffc50cb5541b", size = 1591127, upload-time = "2026-07-23T01:54:19.489Z" }, + { url = "https://files.pythonhosted.org/packages/49/ed/3b203fa6de1b338c14acdc06bf6ca9b043b7944f005966958c2ced932cde/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:041badb8f84396357c4d3ad26de6afd7a32b112f43d3c63045c0c8278cfd2043", size = 1725210, upload-time = "2026-07-23T01:54:24.129Z" }, + { url = "https://files.pythonhosted.org/packages/28/b7/1c2aab8c706436dcc28598452488ac9cd7c409da815237c28c27d58993e6/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:530125ee1163c4219af35dc3aa1206e541e7b31b6efc1a3f93b70a136f65d427", size = 1764848, upload-time = "2026-07-23T01:54:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/54/50/94c28f08b131c4bf10984ea2c7a536c9920608bb2d6e7f95642c30cc87b7/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8653fd547c93a61aadc612007790f5555cdd18946fa48cf45e26d8ea4ea473d", size = 1777102, upload-time = "2026-07-23T01:54:31.775Z" }, + { url = "https://files.pythonhosted.org/packages/13/d4/e7d09ba7d345fb2d74440fd2fa033c5e079fac05552927705986f41a364f/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:89176250f686cb9853c0fb7ead90e639e915b84a6f43eedc2a4e7ec21f1037f0", size = 1580205, upload-time = "2026-07-23T01:54:34.518Z" }, + { url = "https://files.pythonhosted.org/packages/a3/84/072a91d68e1e1eb587985b54baab94221277f877e8ef274fc213a0ceae28/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3a26434dafe408229ff3403458ca58de24fb51936504decac49ce6755f77e59d", size = 1797219, upload-time = "2026-07-23T01:54:36.995Z" }, + { url = "https://files.pythonhosted.org/packages/e0/eb/aad34e897e668424d6e995da5dff8a4a09af93363d3392488772957a63aa/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d1558173930a5a8d3069cee5c92fc91c87c4dbcb099debbb3622053717145a19", size = 1768629, upload-time = "2026-07-23T01:54:40.103Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2b/6bb88ddba0fecd9122aa3ebcad25996cf6c083a4a7040dbb3a4f97972af6/aiohttp-3.14.3-cp312-cp312-win32.whl", hash = "sha256:16100ad3ab8d649fdfbee87602d9d2dcdca9df0b9eda8a1b5fdc0d41f96da559", size = 451481, upload-time = "2026-07-23T01:54:42.547Z" }, + { url = "https://files.pythonhosted.org/packages/76/9b/f2f8f108da17ecef2cc3efc424e8b7ad3782b1a8360f7b8eae8ced84f6ea/aiohttp-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:33a2d7c28d33797a2e99923dffa63f83d908a19b6bf26cfe80fa790aa5e1a75a", size = 476845, upload-time = "2026-07-23T01:54:44.853Z" }, + { url = "https://files.pythonhosted.org/packages/3e/44/28dac80a8941b604f4da10ce21097614ca1bf905ce93dca28d8d7de9c1e7/aiohttp-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:362a3fd481769cac1a824514bcd86fda51c65e8fe6e051099e008fddde6db17c", size = 448050, upload-time = "2026-07-23T01:54:47.087Z" }, + { url = "https://files.pythonhosted.org/packages/57/be/5afd201cc0ab139029aadb75392efe85a293403d9dd3a3226161c21ce00c/aiohttp-3.14.3-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:2e9878ae68e4a5f1c0abe4dd497dbc3d51946f5837b56759e2a02e78fa90ef86", size = 506269, upload-time = "2026-07-23T01:54:49.075Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/dec8189d62b45ade009f6792a2264b942a90cb88aeaf181239933cd72c3c/aiohttp-3.14.3-cp313-cp313-android_21_x86_64.whl", hash = "sha256:f3d2669fe7dec7fc359ecdb5984b29b50d85d5d00f8c1cb61de4f4a24ee42627", size = 515166, upload-time = "2026-07-23T01:54:51.894Z" }, + { url = "https://files.pythonhosted.org/packages/28/24/2854869d29ed8a8b19d74f9ec6629515f7e04d02dd329d9d179201e58e47/aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:cc7cb243a68167172f48c1fd43cee91ec4b1d40cefd190edd43369d1a6bc9c82", size = 486263, upload-time = "2026-07-23T01:54:54.223Z" }, + { url = "https://files.pythonhosted.org/packages/d4/dd/57187c8be2a35aea65eaee3bd2c3dcbbcf0204f5106c89637e3610380cd1/aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:78253b573e6ffab5028924fc98bc281aae05445969982a10864bc360dea2016c", size = 492299, upload-time = "2026-07-23T01:54:56.236Z" }, + { url = "https://files.pythonhosted.org/packages/b9/11/06ae6ed8f0d414edf4068861e233d8fe23ee699bfd4b3ceb8663db948a62/aiohttp-3.14.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7041d52c3a7fa20c9e8c182b534704abb19502c8bdcbde7ab23bfda6f642394f", size = 502235, upload-time = "2026-07-23T01:54:58.377Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a3/559639c34a345d2cf7c52dff6838119f2eaf29eb508227b5b83f573af813/aiohttp-3.14.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac74facc01463f138b0da5580329cfcc82818dea5656e83ddcd11268fc12ff80", size = 750883, upload-time = "2026-07-23T01:55:00.65Z" }, + { url = "https://files.pythonhosted.org/packages/91/cd/41e131f13afd1e7b0172a9d9eda085ef90eb8439f41f0d279db81ed3ae60/aiohttp-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d6218d92e450824e9b4881f44e8c09f1853b490f9a64130801024a4793b1b3b0", size = 508473, upload-time = "2026-07-23T01:55:02.945Z" }, + { url = "https://files.pythonhosted.org/packages/bc/6b/e7f13410d391c6e55b4c007a8de024355389d7d459e3d64c42b2d33617e5/aiohttp-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:11fb37ef075669eee52ab1928fbf6e1741fada40409fa309ebde9607a962aebf", size = 509190, upload-time = "2026-07-23T01:55:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/97/21/6464573e53d69672cc1eada3e5c5cb2d2efa82701e8305a0f2047a576967/aiohttp-3.14.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55bdcc472aafe2de4a253045cc128007a64f1e0264fb675791e132ea5edaa3bd", size = 1761478, upload-time = "2026-07-23T01:55:07.383Z" }, + { url = "https://files.pythonhosted.org/packages/1a/81/d217043a4c17fbce360905e3b2bdd20139ebc9a2de836d035d179c4da006/aiohttp-3.14.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c39846c3aad97a8530c89d7a3869a8f8e9e3762c6ac0504481e5c80948f7e807", size = 1735092, upload-time = "2026-07-23T01:55:09.803Z" }, + { url = "https://files.pythonhosted.org/packages/a1/66/e13a02d0eeb1a9a502402a977abb4e4abff9fe4051c26f80558c57a7c975/aiohttp-3.14.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5895ef58c4620afe02fa16044f023dc4dafec08158f9d08874a46a7dbc0341b8", size = 1800546, upload-time = "2026-07-23T01:55:12.012Z" }, + { url = "https://files.pythonhosted.org/packages/26/5e/57d42fca1d18cb5acc1cad945d017fabc5d6ae71d8a08ad66be8dc3ee544/aiohttp-3.14.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa9467a8113aa69d3d7c55a70ef0b7c636010a40993f3df9d9d0d73b3eb7ef24", size = 1895250, upload-time = "2026-07-23T01:55:14.357Z" }, + { url = "https://files.pythonhosted.org/packages/ca/1c/7da8d08e74d56f00070822f9638ff3f1c563f8ad87d1efa996c87bfc8644/aiohttp-3.14.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7d2deec16eeedf55f2c7cf75b521ea3856a5177e123844f8fd0f114ce252cb5", size = 1789289, upload-time = "2026-07-23T01:55:16.668Z" }, + { url = "https://files.pythonhosted.org/packages/cd/0f/cf16bcf56896981c1a0319f5d5db9337994b5165730c48a8fa07e9b34be6/aiohttp-3.14.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dd54d0e8717de95939766febac482ac0474d8ac3b048115f9f2b1d23a16e7db4", size = 1586706, upload-time = "2026-07-23T01:55:18.913Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6f/76eac12a7f2480e1e304f842efdb07db33256b0d9165b866b6ef0806c202/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:df82f3787c940c94986b34222d59c9e38843fba85139f36e85255a82ad5355a9", size = 1724652, upload-time = "2026-07-23T01:55:21.296Z" }, + { url = "https://files.pythonhosted.org/packages/39/b6/19c8c592baeeb94b75f966547d40c02ac7590902306ec5863d5c027cf506/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:42a67efc36300d052fb4508a53e8b6901b9284b599ae63945c377569c5fcc1e1", size = 1756239, upload-time = "2026-07-23T01:55:23.705Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c9/4e9383150296f97f873b680c4de8fb2cd88608fb9f48c79edcb111611abc/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7a75aa63cbf9b21cfaf60dc2657e19df2c2867d91707d653fee171ffeedd1371", size = 1769161, upload-time = "2026-07-23T01:55:26.082Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1e/147bdc6cc5de5f3ab011be8bf5d6e786633249f22c20bae06f85e45f5387/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e92eb8acc45eb6a9f4935071a77edf5b85cc6f8dfad5cd99e97653c26593cdde", size = 1578759, upload-time = "2026-07-23T01:55:28.846Z" }, + { url = "https://files.pythonhosted.org/packages/fd/31/78388a9d6040ece2e11df62ea229a822cf5e52d238374b220ae9975b2623/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b014a6ed7cf912e787149fdc529166d3ceabac23f26efeea3158c9aba2354e7e", size = 1792025, upload-time = "2026-07-23T01:55:31.457Z" }, + { url = "https://files.pythonhosted.org/packages/03/51/a3d29fdf2c25d796746af8ad6fe56a45d6256c38b0a8a2ed752e1160b3a2/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3d4f72af88ac2474bb5bca640030320e3d38a0163a1d7533500e87be458eef71", size = 1768477, upload-time = "2026-07-23T01:55:33.87Z" }, + { url = "https://files.pythonhosted.org/packages/29/a6/442e18b5afeade534d877a2dc3c3e392aff8d49787890b0cf84790410267/aiohttp-3.14.3-cp313-cp313-win32.whl", hash = "sha256:5f08ec777f35ee70720233b8b9811d3bb5d728137f30ac91b7457709c3261ac0", size = 451069, upload-time = "2026-07-23T01:55:36.121Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/3d876ac02659f271cf7f6769f14a8e3de5b6e888ed8b5a7e998086a4cec8/aiohttp-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:dff9461ec275f22135650d5ba4b4931a11f3958df7dfbb8db630000d4dee0883", size = 476518, upload-time = "2026-07-23T01:55:38.303Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0e/50d6e6471cd31edce8b282bdec59375a3a69124d8a989a0b1313355cae52/aiohttp-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:ddcac3c6b382e81f1dd0499199d4136b877beb4cb5ef770bbbfba56c4b8f55d2", size = 447676, upload-time = "2026-07-23T01:55:40.451Z" }, + { url = "https://files.pythonhosted.org/packages/c8/20/887fdcf832326571b370ffc347b3e70abe101096f3720126aac161b1d872/aiohttp-3.14.3-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:49f7325beb0f85ef4aef5f48f490269575f83e6e2acad00a1d80b807eb027062", size = 509067, upload-time = "2026-07-23T01:55:42.618Z" }, + { url = "https://files.pythonhosted.org/packages/ad/a3/92cec936f78cc4bf0fa5554ebe593b73459d94e3c62303e1902a4cccb6f7/aiohttp-3.14.3-cp314-cp314-android_24_x86_64.whl", hash = "sha256:e3be98a7c30b8c25d573dafba7171d66dfb05ee6a9070fc46535464ff97700a6", size = 514774, upload-time = "2026-07-23T01:55:44.937Z" }, + { url = "https://files.pythonhosted.org/packages/29/ba/2a0c38df3fc557620b6a5acd98364af050053b6285b4dc7ee74100c63c18/aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:614c61d478b83953e261d02bb2df750f17227cd33ef8002945bf5aebbde21919", size = 488134, upload-time = "2026-07-23T01:55:47.135Z" }, + { url = "https://files.pythonhosted.org/packages/48/d6/d51b7d4bf309af3693940d8ffd2b9ed0b682434ef85959b7c9c137f60cf8/aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:1caa7b0d05f3e3a36f87788c59e970a7ee1cefcfcbb924a9f138c4a6551c9cb7", size = 494201, upload-time = "2026-07-23T01:55:49.451Z" }, + { url = "https://files.pythonhosted.org/packages/3f/5a/8f624384e5f1efabb5229b94157eb966b021e97bdb188c62860c2ae243c2/aiohttp-3.14.3-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:dfa68deb2a443bdaa3ea5297b0699c1464f08aef3812b486d1348eee61b07dc0", size = 502766, upload-time = "2026-07-23T01:55:51.656Z" }, + { url = "https://files.pythonhosted.org/packages/a6/26/4ff0164370deec18fb19254ee4ab10b7a73304ac0c860b13f5f84663759b/aiohttp-3.14.3-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e72ee89e28d907a18f46959b4eb0bb06701cc7f8cf4366e00029e2ccfaaf5924", size = 756557, upload-time = "2026-07-23T01:55:53.964Z" }, + { url = "https://files.pythonhosted.org/packages/97/a3/7056b86dc0d9ec709ea9777eae3b0161428f943372f8b98c01c11593b682/aiohttp-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ad4c8b7488d745d2ca4838ebd8ae5ba9b56341d30b1da43640e4ce87f9f49646", size = 510168, upload-time = "2026-07-23T01:55:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/85/ed/0357a015892fd68058bf2d39d3fd1958e459b997a7db30aaa6aaa434ae96/aiohttp-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:db332af25642007330fca8be5c4d194caf2bea7a7fc84415aff3497af5dfee6b", size = 512957, upload-time = "2026-07-23T01:55:58.437Z" }, + { url = "https://files.pythonhosted.org/packages/47/d1/8aba53f15ccb2238405f5e9d30e2a8ca44f93878c26e7165ade00d374b1c/aiohttp-3.14.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25bd2708db6bdf6a6630dd37bdcdfcb47c4434d22ac69c64665b802910140b30", size = 1750149, upload-time = "2026-07-23T01:56:00.856Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/40c3fee327529284375c6701cbb0fa4600cc2e8432af1378f897e2ef7d3a/aiohttp-3.14.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cef89a58e628c4efcac3275c2d68083f82426dcdc89c1492a6f654f9f7ea6ab9", size = 1707685, upload-time = "2026-07-23T01:56:03.371Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a3/ca0cc6724cca8114b05694abd916060758c79894c3aa5b012cdadc1bc28e/aiohttp-3.14.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c23ec8ee9d5ab2f5421f9c7fffce208435607af27fd46d4a44e031954352838f", size = 1803911, upload-time = "2026-07-23T01:56:05.817Z" }, + { url = "https://files.pythonhosted.org/packages/95/b5/85b099c299c3ffd38ad9b3e43694c8a346934e4a30c88c4fd5a841234f77/aiohttp-3.14.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e2667f0bbe7eb6c74eae5e9691441ad186e5845ca3cff63230fc09c4e7514f5d", size = 1876929, upload-time = "2026-07-23T01:56:08.413Z" }, + { url = "https://files.pythonhosted.org/packages/d5/b7/1da684a04175473fa4cddbf9a2f572e79514c3fd27a74597f43057d4f3da/aiohttp-3.14.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18cb43369747b2ae007bd2655fb8e63a099c2ff1d207962943636dac989b3147", size = 1761112, upload-time = "2026-07-23T01:56:10.918Z" }, + { url = "https://files.pythonhosted.org/packages/d1/16/bc4b55e3e5cb175fd69c53c90d60d2f47797cb343da5106e23863dc4dba4/aiohttp-3.14.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d77640cc618c1d99fc4f8589c0f24a730adfa54eb1e57ef7bf0c8dfb78da898c", size = 1583500, upload-time = "2026-07-23T01:56:13.613Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e8/13a9d957a1ee40837f46aa30f0f4c657e673ad86a2e6362a9f9be20d26d9/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:53e5179d8abb5710f8e83ba207c41c8d1261fcffd4616500e15ca2b7a33be10a", size = 1713940, upload-time = "2026-07-23T01:56:15.969Z" }, + { url = "https://files.pythonhosted.org/packages/38/05/d33c680c1bcf1c7e130f9cbfc1fc02fe8bb0c4af2a94a53dd5fb56131e5c/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:cd817772b2fcf2b8c0905795318485f9ec16eae60b29feb7f4c77085311637f0", size = 1724413, upload-time = "2026-07-23T01:56:18.591Z" }, + { url = "https://files.pythonhosted.org/packages/85/1d/af798d306f7a74b6a632dbcabcf62a4c91391b7582d2a8c6d7712e2cc54e/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4e3ac92d90e92773b2362d506068e9a948192bd553e743c5b2429e28527c8661", size = 1770748, upload-time = "2026-07-23T01:56:21.074Z" }, + { url = "https://files.pythonhosted.org/packages/a8/92/ad720d472556a995049206867765e9410969684f86ee09423ff9969044c1/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3f42e9b78301f11c8f861746175d8b9c1ccef713fcad9eab396e2f6db8ed4a22", size = 1577564, upload-time = "2026-07-23T01:56:23.475Z" }, + { url = "https://files.pythonhosted.org/packages/60/ad/0ed7586cbef7a884e23a752fa2bb987a122e6a5dd50dab109258d0a95193/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9d9edccfe496b476db5f398d97b865e9a6752bcf8aec4eef8390ce20fb64bb41", size = 1782080, upload-time = "2026-07-23T01:56:25.994Z" }, + { url = "https://files.pythonhosted.org/packages/97/ea/dbaed0d73e8a69aad653b045dab451c67c2454bb731a37b45a86593e9422/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c5ec8fb1bcc31a8466f74aaf26c345d5c386fa4bd08a3f0eb9c7a4a3fe8b5bf", size = 1745813, upload-time = "2026-07-23T01:56:28.604Z" }, + { url = "https://files.pythonhosted.org/packages/81/1b/6893d4bc57e434fc93a6c9217c637d967a0b651d989f6e3265179375754a/aiohttp-3.14.3-cp314-cp314-win32.whl", hash = "sha256:38901a84da3ce22249f6e860bf8f90d141bcab7da090cc398f8bb58c0e44b7da", size = 455872, upload-time = "2026-07-23T01:56:31.031Z" }, + { url = "https://files.pythonhosted.org/packages/f5/8b/c7baa1ba1eda4db6989baefe5de6d99834921b84ebd7918624febcb9f290/aiohttp-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:8b3b60de05f3dcb6f6a00f818bb2ec781cee4de0645f59ccaf99b1d1823b6100", size = 481030, upload-time = "2026-07-23T01:56:33.365Z" }, + { url = "https://files.pythonhosted.org/packages/22/8c/c29d067df825a2df88ca432db848aa2fe8199598359cc06c12b09320cac9/aiohttp-3.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:1576145bdceeb92382d899751e12743a3a5b8e460a841e3e50543859e54864dc", size = 453669, upload-time = "2026-07-23T01:56:35.731Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a4/9c033beb355d39b6147980597ec9645e4729243f686ee4dc73945de72030/aiohttp-3.14.3-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8800c996b01c2772a783e3e46f3e1abd5823029adca0df54231960de9bfefa5b", size = 791403, upload-time = "2026-07-23T01:56:37.972Z" }, + { url = "https://files.pythonhosted.org/packages/80/ca/87c32a0a7704583cfc49660bd817889bae5b830bf53b5dcb4e92145ac2da/aiohttp-3.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ebe8e504f058fe91223351cecd2d9d6946c9d241bb0250d898ffbdf584cc72b0", size = 526413, upload-time = "2026-07-23T01:56:40.523Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d8/8ec0e471248c500acdce2be3f46db8fb62b5eb60efef072529cc85ee1d26/aiohttp-3.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:30402d03a7c0ff52bce290b57e564e9079fd9d0cb545c8aba73f86a103162d2e", size = 532135, upload-time = "2026-07-23T01:56:42.876Z" }, + { url = "https://files.pythonhosted.org/packages/fe/45/f8919fd936e8b79fcd9bda7b6d8e62613462a713f4f17987fd7c34399142/aiohttp-3.14.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9fc7b5bfec6573f3ae844f457fdde5adeb713f8b8e4a81ad64fc207b49383716", size = 1922742, upload-time = "2026-07-23T01:56:45.528Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ec/9ca76b28a27525b0cc53e20842e0228b022f301ce1f436b7d814b4aaf2df/aiohttp-3.14.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8a5fd34f7f7410d1730d5c2ba873cacb2eed3fede366feb268a70ba22581ed8f", size = 1787371, upload-time = "2026-07-23T01:56:48.045Z" }, + { url = "https://files.pythonhosted.org/packages/b1/04/6acdbf17315f7b55f1937e3387acb89a3cddeb4995689553d064af8e92ab/aiohttp-3.14.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:270d3dace9ca2f10f0da5d8ebe519b7a310fc6112ed916e32df5866df0888553", size = 1912623, upload-time = "2026-07-23T01:56:50.605Z" }, + { url = "https://files.pythonhosted.org/packages/86/e6/438b0c79ca6f45eb9fd9817dd4c01a91919a38c0de5ee9e05e2b4dc0ece7/aiohttp-3.14.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3ae5b3a59436d089b5395d910121a390feed4d00578eb95a0fd1a329fe963100", size = 2005515, upload-time = "2026-07-23T01:56:53.153Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6b/62cbd6577758699525f5c712d1ddef57d9875fbab0ae8d5f5a202fd598f8/aiohttp-3.14.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2498f0fe69ead802f9675beca44a7c21c62fdaa4ec5145ea1c3ad6edbee29f85", size = 1879906, upload-time = "2026-07-23T01:56:55.818Z" }, + { url = "https://files.pythonhosted.org/packages/00/95/18bcbf830a21dc3aae24d8f6b6feaf3db1d2090242d00a7868db2ffb0b67/aiohttp-3.14.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a0dc483c00da8b673abbb367eb6f8d8f4bcec30eb58529ea13cb42e7fd2dfa33", size = 1675849, upload-time = "2026-07-23T01:56:58.861Z" }, + { url = "https://files.pythonhosted.org/packages/a9/19/47f4968659c5e23606c3790c80fc624e691c153d036148449ee84d31b287/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c7d3a97c678d34fc5b59da671ee9cd630096ddc643e7b5a30d54a2a6f3574d3f", size = 1843496, upload-time = "2026-07-23T01:57:01.591Z" }, + { url = "https://files.pythonhosted.org/packages/64/af/38c33c4dd82fddcb4e56c4653b6f1072a8edbc6b7fa15809f14932c41e2d/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f8fb78a83c9e5f741ca3a68cfb455c1f5bb83b4e7249a3848b3cd78d0a8563b0", size = 1827746, upload-time = "2026-07-23T01:57:05.131Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9d/0537cda4885ac8f5b7053d164dd06312f4c483a4edcb8ee5b8aaf2a989bf/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:74ab5b6a9fb13e873e5a90946588baecaf488745e1db1a4a5c433f971f035098", size = 1853810, upload-time = "2026-07-23T01:57:08.043Z" }, + { url = "https://files.pythonhosted.org/packages/19/fe/26f9c5e6458385aa86497836b0dea6fb2f027827d63f37c7856cce9286ee/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd52f811e65f6fb634b1047159657c98f52b407f8efec907bcfc09da9a4c0a25", size = 1668895, upload-time = "2026-07-23T01:57:10.837Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4c/618b1db9b9ba079b8875d2cdf78e7c4a3bf72903bd5850fee7dd9544600a/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f0f177d1b195b9e06376cfd7d308d8a1b920909a609d03ac82a8c73bbb16d3b9", size = 1883833, upload-time = "2026-07-23T01:57:13.672Z" }, + { url = "https://files.pythonhosted.org/packages/94/c6/bd959bd1e4771f9fd944e9e436224c48c77b018b73b519b5aad346335bcc/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:498c6c623134f8e09a3c4e60bcd607a0b4590dd7dbf08dd40851b27cbb520ccb", size = 1844251, upload-time = "2026-07-23T01:57:16.593Z" }, + { url = "https://files.pythonhosted.org/packages/5e/19/08d41839658bdd44a0ed2480f3891705ecb487ce28c0dde62c9040c997e0/aiohttp-3.14.3-cp314-cp314t-win32.whl", hash = "sha256:b304db572b4368edd8dda8a2274f73156fe15558fca4a917cb8a09fc47af5963", size = 474180, upload-time = "2026-07-23T01:57:19.306Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/3cd6ef0a2b2851f7ab913b5b079334781bd50ff56a323e4454063377a080/aiohttp-3.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b20032766aedf6261c7a566585a40867d092ac03a0d81592d5370ef9b054f99b", size = 500528, upload-time = "2026-07-23T01:57:21.762Z" }, + { url = "https://files.pythonhosted.org/packages/a4/37/cfd1ed540a4d318da025590d96b728e63713c09e9377950fc655dadeb856/aiohttp-3.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:2e1161602f45a54de2ce0905243a95f58cb42dcd378402f3697f5e0b21e9d2e7", size = 469280, upload-time = "2026-07-23T01:57:24.241Z" }, ] [[package]] @@ -4327,7 +4327,7 @@ proxy-dev = [ [package.metadata] requires-dist = [ { name = "a2a-sdk", marker = "extra == 'extra-proxy'", specifier = ">=1.1.0,<2.0" }, - { name = "aiohttp", specifier = ">=3.10,<4.0" }, + { name = "aiohttp", specifier = ">=3.14.2,<4.0" }, { name = "anthropic", extras = ["vertex"], marker = "extra == 'proxy-runtime'", specifier = ">=0.84.0,<1.0" }, { name = "apscheduler", marker = "extra == 'proxy'", specifier = ">=3.11.2,<4.0" }, { name = "audioread", marker = "extra == 'stt-nvidia-riva'", specifier = ">=3.0.1" }, From 67969303fceb053184039c6792131a77f09df4f5 Mon Sep 17 00:00:00 2001 From: mgeorgaklis Date: Fri, 31 Jul 2026 14:48:40 +0000 Subject: [PATCH 18/92] refactor(gemini): simplify thought signature collection --- .../llms/vertex_ai/gemini/transformation.py | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/litellm/llms/vertex_ai/gemini/transformation.py b/litellm/llms/vertex_ai/gemini/transformation.py index f465a54b265..1f568496041 100644 --- a/litellm/llms/vertex_ai/gemini/transformation.py +++ b/litellm/llms/vertex_ai/gemini/transformation.py @@ -657,10 +657,11 @@ def _collect_tool_call_thought_signatures( tool_calls = assistant_msg.get("tool_calls") if isinstance(tool_calls, list): for tool in tool_calls: - if isinstance(tool, dict): - signature = _get_thought_signature_from_tool(tool) - if signature: - signatures += (signature,) + if not isinstance(tool, dict): + continue + signature = _get_thought_signature_from_tool(tool) + if signature: + signatures += (signature,) function_call = assistant_msg.get("function_call") if isinstance(function_call, dict): @@ -669,15 +670,20 @@ def _collect_tool_call_thought_signatures( signatures += (signature,) provider_specific_fields = assistant_msg.get("provider_specific_fields") - if isinstance(provider_specific_fields, dict): - invocations = provider_specific_fields.get("server_side_tool_invocations") - if isinstance(invocations, list): - for invocation in invocations: - if isinstance(invocation, dict): - for key in ("thought_signature", "response_thought_signature"): - invocation_signature = invocation.get(key) - if isinstance(invocation_signature, str) and invocation_signature: - signatures += (invocation_signature,) + if not isinstance(provider_specific_fields, dict): + return frozenset(signatures) + + invocations = provider_specific_fields.get("server_side_tool_invocations") + if not isinstance(invocations, list): + return frozenset(signatures) + + for invocation in invocations: + if not isinstance(invocation, dict): + continue + for key in ("thought_signature", "response_thought_signature"): + invocation_signature = invocation.get(key) + if isinstance(invocation_signature, str) and invocation_signature: + signatures += (invocation_signature,) return frozenset(signatures) From 473f43dfbf08797c2239c645147bd722030373be Mon Sep 17 00:00:00 2001 From: Yassin Kortam Date: Fri, 31 Jul 2026 08:49:22 -0700 Subject: [PATCH 19/92] fix(mcp): deny MCP access when a named entitlement cannot be read (#35160) An MCP permission level answers which servers and tools it permits, and a level that answers nothing places no restriction. Key auth was reading a lookup FAULT as that same answer, so the end user, agent and org ceilings quietly disappeared for as long as one lasted, while the keyless gateway-admitted path failed closed on the very same fault. Those levels now separate the two fault classes the user level already did. A principal row that NAMES an object_permission_id whose contents cannot be read is a known entitlement with unknown contents, so it denies. A lookup that fails before we can tell whether the principal is entitled at all still places no ceiling, that being the state which existed before the level did; denying there would refuse MCP to the majority of callers, who have no such entitlement configured. The keyless path is unchanged. Resolves LIT-4960 --- .../mcp_server/auth/user_api_key_auth_mcp.py | 337 ++++++++++++------ .../auth/test_user_api_key_auth_mcp.py | 174 +++++++++ 2 files changed, 404 insertions(+), 107 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py b/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py index dc796837277..81983cc62fd 100644 --- a/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py +++ b/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py @@ -67,6 +67,18 @@ def _as_list(values: Sequence[str] | None) -> list[str] | None: # mutable-ok: r return None if values is None else list(values) +class UnloadableEntitlementError(Exception): + """A principal's row NAMES an ``object_permission_id`` whose contents could not be read. + + Raised only where there is POSITIVE evidence an entitlement exists, so every caller must DENY + rather than fall back to "this level places no restriction": a ceiling we know exists but cannot + read would otherwise silently widen the caller for as long as the fault lasts. + + Deliberately distinct from a lookup that fails before the principal's entitlement is known at + all. Not knowing whether someone is entitled is the state that existed before the level did, so + it places no ceiling; denying there would refuse MCP to every caller during a cold-cache fault.""" + + def _parse_mcp_server_names_from_path(path: str, mcp_servers_header: Optional[List[str]] = None) -> Optional[List[str]]: """Resolve the single MCP server name a cold-start passthrough bypass may target. Delegates parsing to @@ -292,6 +304,22 @@ class MCPRequestHandler: 3. Header extraction and validation Utilizes the main `user_api_key_auth` function to validate authentication + + Entitlement-fault contract (``get_allowed_mcp_servers`` / ``get_allowed_tools_for_server``) + ------------------------------------------------------------------------------------------ + Every level (key, team, end user, agent, org) answers "which servers/tools does this level + permit", and a level that answers nothing places no restriction. A lookup FAULT is not that + answer, and the two callers resolve it differently on purpose: + + - A keyless gateway-admitted subject fails CLOSED on any fault at any level. Each of its grant + sources is resolved independently and unioned, so a fault that returned "no restriction" would + win the union as allow-all, and its per-source org ceiling is the ONLY org bound it has. + - Key auth fails closed only where there is POSITIVE evidence an entitlement exists: a principal + row that NAMES an ``object_permission_id`` we cannot load is a known entitlement with unknown + contents (``UnloadableEntitlementError`` -> deny). A fault so early we cannot tell whether the + principal is entitled at all leaves no ceiling, because that is the state that existed before + the level did; denying there would refuse MCP to every caller, most of whom have no entitlement + configured, for the duration of a cold-cache or DB fault. """ LITELLM_API_KEY_HEADER_NAME_PRIMARY = SpecialHeaders.custom_litellm_api_key.value @@ -1348,6 +1376,9 @@ class MCPRequestHandler: has an explicit MCP server list, the combined key/team/end_user/agent result is capped to that list. If the org has no list, no extra restriction is applied. + A level that cannot answer is NOT a level that permits everything; see the class docstring + for how each caller shape resolves an entitlement fault. + Returns: List[str]: List of allowed MCP servers by server id """ @@ -1478,7 +1509,12 @@ class MCPRequestHandler: return list(set(allowed_mcp_servers)) except Exception as e: - verbose_logger.warning(f"Failed to get allowed MCP servers: {str(e)}") + if isinstance(e, UnloadableEntitlementError): + # A ceiling we KNOW exists and cannot read. Denying is the only answer that does not + # widen this caller past what an operator configured, for both caller shapes. + verbose_logger.warning(f"Denying MCP access, entitlement unreadable: {str(e)}") + else: + verbose_logger.warning(f"Failed to get allowed MCP servers: {str(e)}") return [] @staticmethod @@ -1491,11 +1527,15 @@ class MCPRequestHandler: """Cap the resolved server list by this caller's org ceiling: an explicit org list intersects lower-level restrictions (else becomes the ceiling); no org or an empty list leaves it unchanged. - ``keyless_source`` governs both divergences for a keyless admitted source. An UNRESOLVABLE ceiling - fails CLOSED for it (its only org bound is this ceiling, so dropping it on a fault would escalate a - cross-org user) while a key stays fail-open. And an org list may only ever INTERSECT a source (the - admitted model unions grants, so a ceiling must not become one), whereas for a key it may - substitute, that being the key ceiling model.""" + ``keyless_source`` governs both divergences for a keyless admitted source. An INDETERMINATE ceiling + (we cannot tell whether the org restricts at all) fails CLOSED for it (its only org bound is this + ceiling, so dropping it on a fault would escalate a cross-org user) while a key stays fail-open. And + an org list may only ever INTERSECT a source (the admitted model unions grants, so a ceiling must not + become one), whereas for a key it may substitute, that being the key ceiling model. + + The fail-open arm is reached only for an INDETERMINATE fault: a ceiling the org NAMES but that + cannot be read raises out of ``_get_allowed_mcp_servers_for_org`` and never arrives here as + ``None``, so key auth cannot silently shed a ceiling an operator did configure.""" if not (user_api_key_auth and user_api_key_auth.org_id): return allowed_mcp_servers allowed_mcp_servers_for_org = await MCPRequestHandler._get_allowed_mcp_servers_for_org(user_api_key_auth) @@ -1900,12 +1940,19 @@ class MCPRequestHandler: ) except Exception as e: - verbose_logger.warning(f"Failed to get allowed tools for server: {str(e)}") + # An entitlement known to exist but unreadable denies for BOTH caller shapes, so [] rather + # than the None (allow-all) key auth gets for an indeterminate fault. + unreadable_entitlement = isinstance(e, UnloadableEntitlementError) + if unreadable_entitlement: + verbose_logger.warning(f"Denying MCP tools, entitlement unreadable: {str(e)}") + else: + verbose_logger.warning(f"Failed to get allowed tools for server: {str(e)}") # Fail CLOSED for a keyless admitted subject: ANY error must deny the server's tools ([]), # not collapse to allow-all (None); key/JWT auth keeps its prior allow-all-on-error. Both # keyless_source AND the marker are needed: each source resolves through an UNMARKED auth, so # without keyless_source a fault under a source returns None and wins the union as allow-all. - return [] if (keyless_source or _is_mcp_admitted_user_subject(user_api_key_auth)) else None + deny_all = unreadable_entitlement or keyless_source or _is_mcp_admitted_user_subject(user_api_key_auth) + return [] if deny_all else None @staticmethod async def _apply_agent_and_org_tool_ceilings( @@ -1944,7 +1991,9 @@ class MCPRequestHandler: try: org_obj_perm = await MCPRequestHandler._get_org_object_permission(user_api_key_auth) except Exception as e: # noqa: BLE001 # unresolvable org ceiling, decided per caller shape - if keyless_source: + # A ceiling the org NAMES but that cannot be read denies at every caller shape; only an + # INDETERMINATE fault (we cannot tell whether a ceiling exists) keeps key auth open. + if keyless_source or isinstance(e, UnloadableEntitlementError): raise verbose_logger.warning( f"MCP org tool ceiling unresolvable for org_id={user_api_key_auth.org_id!r}; " @@ -2275,18 +2324,54 @@ class MCPRequestHandler: verbose_logger.warning(f"Failed to get allowed MCP servers for team: {str(e)}") return [] + @staticmethod + async def _load_named_object_permission( + principal: str, + object_permission_id: str, + prisma_client: "PrismaClient", + user_api_key_auth: UserAPIKeyAuth, + ) -> LiteLLM_ObjectPermissionTable: + """Load the object permission a principal's row NAMES, or raise ``UnloadableEntitlementError``. + + The single place that fault is minted, so end user, agent and org cannot drift on what counts + as "known entitlement, unknown contents". ``get_object_permission`` answers None for both an + absent row and a failed read, and neither is evidence the principal is unrestricted: the link + proves an entitlement was configured, so both must deny.""" + from litellm.proxy.auth.auth_checks import get_object_permission + from litellm.proxy.proxy_server import proxy_logging_obj, user_api_key_cache + + unloadable = UnloadableEntitlementError( + f"{principal} names object_permission_id {object_permission_id!r} which could not be loaded" + ) + try: + object_permission = await get_object_permission( + object_permission_id=object_permission_id, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + parent_otel_span=user_api_key_auth.parent_otel_span, + proxy_logging_obj=proxy_logging_obj, + ) + except Exception as e: # noqa: BLE001 # a named entitlement we cannot read denies, whatever the read failed with + raise unloadable from e + if object_permission is None: + raise unloadable + return object_permission + @staticmethod async def _get_org_object_permission( user_api_key_auth: Optional[UserAPIKeyAuth] = None, - ): + ) -> LiteLLM_ObjectPermissionTable | None: """ Get org object_permission via the established ``get_org_object`` / ``get_object_permission`` helpers so MCP requests share the same ``user_api_key_cache`` entries as the rest of the proxy. + + ``None`` means the org places NO ceiling: no ``org_id``, no DB, or an org row naming no + permission. A row that NAMES one it cannot load raises ``UnloadableEntitlementError``; + every other lookup failure propagates as itself, leaving the ceiling merely unresolved. """ from litellm.proxy.auth.auth_checks import ( OrganizationNotFoundError, - get_object_permission, get_org_object, ) from litellm.proxy.proxy_server import ( @@ -2322,31 +2407,29 @@ class MCPRequestHandler: if org_obj is None or not org_obj.object_permission_id: return None - # The org NAMES a permission; failing to read it is INDETERMINATE and must not collapse into the - # None that means "no ceiling". Raise and let each caller pick fail-open or fail-closed. - object_permission = await get_object_permission( + # The org NAMES a permission; failing to read it is a KNOWN ceiling with unknown contents and + # must not collapse into the None that means "no ceiling". Raising denies at every caller shape. + return await MCPRequestHandler._load_named_object_permission( + principal=f"org {user_api_key_auth.org_id!r}", object_permission_id=org_obj.object_permission_id, prisma_client=prisma_client, - user_api_key_cache=user_api_key_cache, - parent_otel_span=user_api_key_auth.parent_otel_span, - proxy_logging_obj=proxy_logging_obj, + user_api_key_auth=user_api_key_auth, ) - if object_permission is None: - raise ValueError( - f"org {user_api_key_auth.org_id!r} names object_permission_id " - f"{org_obj.object_permission_id!r} which could not be loaded" - ) - return object_permission @staticmethod async def _get_allowed_mcp_servers_for_org( user_api_key_auth: Optional[UserAPIKeyAuth] = None, - ) -> List[str]: + ) -> list[str] | None: """ Get allowed MCP servers for an organization. Returns the MCP servers from the org's object_permission. - An empty result means the org places no restriction (allow-all from this level). + An empty result means the org places no restriction (allow-all from this level), ``None`` + that the ceiling could not be resolved, which the caller decides per shape. + + A ceiling the org NAMES but we cannot read is neither: it raises out of here so both caller + shapes deny, because dropping a ceiling known to exist is exactly the silent widening the + level is there to prevent. """ try: object_permissions = await MCPRequestHandler._get_org_object_permission(user_api_key_auth) @@ -2374,34 +2457,28 @@ class MCPRequestHandler: except Exception as e: # None = ceiling UNRESOLVED, distinct from [] = org places no restriction. Collapsing them # let a DB fault silently drop a ceiling; the caller picks fail-open/closed from this signal. + # A NAMED-but-unreadable ceiling is a stronger fact than "unresolved" and denies everywhere. + if isinstance(e, UnloadableEntitlementError): + raise verbose_logger.warning(f"Failed to get allowed MCP servers for org: {str(e)}") return None @staticmethod - async def _get_allowed_mcp_servers_for_end_user( - user_api_key_auth: Optional[UserAPIKeyAuth] = None, - ) -> List[str]: - """ - Get allowed MCP servers for an end user. + async def _get_end_user_object_permission( + user_api_key_auth: UserAPIKeyAuth, + prisma_client: "PrismaClient", + ) -> LiteLLM_ObjectPermissionTable | None: + """The end user's own object_permission, or ``None`` when this level places no restriction. - Returns the MCP servers from the end_user's object_permission. - """ + ``None`` covers an end user row that is absent or names no permission, and an end user we + could not resolve at all (``get_end_user_object`` answers None for an absent row AND for a + failed read, so this level genuinely cannot tell those apart). A row that DOES name a + permission we cannot load raises ``UnloadableEntitlementError``: the link is positive + evidence of an entitlement, so its contents may not be assumed empty.""" from litellm.proxy.auth.auth_checks import get_end_user_object - from litellm.proxy.proxy_server import ( - prisma_client, - proxy_logging_obj, - user_api_key_cache, - ) - - if not user_api_key_auth or not user_api_key_auth.end_user_id: - return [] - - if prisma_client is None: - verbose_logger.debug("prisma_client is None") - return [] + from litellm.proxy.proxy_server import proxy_logging_obj, user_api_key_cache try: - # Use optimized get_end_user_object function with caching end_user_obj = await get_end_user_object( end_user_id=user_api_key_auth.end_user_id, prisma_client=prisma_client, @@ -2410,29 +2487,65 @@ class MCPRequestHandler: proxy_logging_obj=proxy_logging_obj, route="/mcp", ) + except Exception as e: # noqa: BLE001 # entitlement unknown, not known-absent: no ceiling, as before this level + verbose_logger.warning(f"Failed to resolve end_user for MCP permissions: {str(e)}") + return None - if end_user_obj is None or end_user_obj.object_permission is None: - return [] + if end_user_obj is None: + return None + if end_user_obj.object_permission is not None: + return end_user_obj.object_permission + if not end_user_obj.object_permission_id: + return None + # The row NAMES a permission the relation did not carry. One shared (cached) lookup decides + # whether it is readable; an unreadable one denies rather than reading as "no restriction". + return await MCPRequestHandler._load_named_object_permission( + principal=f"end user {user_api_key_auth.end_user_id!r}", + object_permission_id=end_user_obj.object_permission_id, + prisma_client=prisma_client, + user_api_key_auth=user_api_key_auth, + ) + @staticmethod + async def _get_allowed_mcp_servers_for_end_user( + user_api_key_auth: Optional[UserAPIKeyAuth] = None, + ) -> List[str]: + """ + Get allowed MCP servers for an end user. + + Returns the MCP servers from the end_user's object_permission; an empty result means this + level places no restriction. An entitlement the end user row NAMES but that cannot be read + raises ``UnloadableEntitlementError`` out of here so the resolver denies. + """ + from litellm.proxy.proxy_server import prisma_client + + if not user_api_key_auth or not user_api_key_auth.end_user_id: + return [] + + if prisma_client is None: + verbose_logger.debug("prisma_client is None") + return [] + + object_permission = await MCPRequestHandler._get_end_user_object_permission(user_api_key_auth, prisma_client) + if object_permission is None: + return [] + + try: # Permission entries may be server_ids OR names/aliases — expand to ids. from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( global_mcp_server_manager, ) - direct_mcp_servers = global_mcp_server_manager.expand_permission_list( - end_user_obj.object_permission.mcp_servers or [] - ) + direct_mcp_servers = global_mcp_server_manager.expand_permission_list(object_permission.mcp_servers or []) # Get MCP servers from access groups access_group_servers = await MCPRequestHandler._get_mcp_servers_from_access_groups( - end_user_obj.object_permission.mcp_access_groups or [] + object_permission.mcp_access_groups or [] ) # servers referenced in tool permissions should also be accessible tool_perm_servers = list( - global_mcp_server_manager.expand_tool_permissions( - end_user_obj.object_permission.mcp_tool_permissions - ).keys() + global_mcp_server_manager.expand_tool_permissions(object_permission.mcp_tool_permissions).keys() ) # Combine all lists @@ -2643,22 +2756,51 @@ class MCPRequestHandler: # don't re-query the DB on every MCP request for that agent. _AGENT_NO_PERMISSION_SENTINEL = "__agent_no_mcp_permission__" + @staticmethod + async def _agent_object_permission_id(agent_id: str, prisma_client: "PrismaClient") -> str | None: + """The permission row this agent's row links to, or ``None`` when it links none. + + Caches the link (with a sentinel for "links none") so an agent without an entitlement costs + no DB read per MCP request. A read that fails also answers ``None``: not knowing whether the + agent is entitled is the state that existed before this level, so it places no ceiling. Only + a link we DID resolve can make the caller deny.""" + from litellm.proxy.proxy_server import user_api_key_cache + + cache_key = f"agent_object_permission_id:{agent_id}" + try: + cached: object = await user_api_key_cache.async_get_cache(key=cache_key) + if cached == MCPRequestHandler._AGENT_NO_PERMISSION_SENTINEL: + return None + if isinstance(cached, str) and cached: + return cached + agent_row = await AgentsRepository(prisma_client).table.find_unique(where={"agent_id": agent_id}) + linked: object = getattr(agent_row, "object_permission_id", None) if agent_row is not None else None + object_permission_id = linked if isinstance(linked, str) and linked else None + await user_api_key_cache.async_set_cache( + key=cache_key, + value=object_permission_id or MCPRequestHandler._AGENT_NO_PERMISSION_SENTINEL, + ttl=get_management_object_ttl(user_api_key_cache), + ) + return object_permission_id + except Exception as e: # noqa: BLE001 # entitlement unknown, not known-absent: no ceiling, as before this level + verbose_logger.warning(f"Failed to resolve object_permission_id for agent {agent_id!r}: {str(e)}") + return None + @staticmethod async def _get_agent_object_permission( user_api_key_auth: Optional[UserAPIKeyAuth] = None, - ): + ) -> LiteLLM_ObjectPermissionTable | None: """ Get agent object_permission via the established ``get_object_permission`` helper. Caches the ``agent_id -> object_permission_id`` mapping so we avoid re-reading the agent row on every request, and reuses the shared ``object_permission_id`` cache populated by the org / team / key paths. + + ``None`` means the agent places NO restriction: no ``agent_id``, no DB, or an agent linking + no permission. An agent that LINKS one we cannot load raises ``UnloadableEntitlementError``, + since a known entitlement with unknown contents must deny rather than read as unrestricted. """ - from litellm.proxy.auth.auth_checks import get_object_permission - from litellm.proxy.proxy_server import ( - prisma_client, - proxy_logging_obj, - user_api_key_cache, - ) + from litellm.proxy.proxy_server import prisma_client if not user_api_key_auth or not user_api_key_auth.agent_id: return None @@ -2668,40 +2810,17 @@ class MCPRequestHandler: return None agent_id = user_api_key_auth.agent_id - cache_key = f"agent_object_permission_id:{agent_id}" - - try: - object_permission_id: Optional[str] = await user_api_key_cache.async_get_cache(key=cache_key) - - if object_permission_id == MCPRequestHandler._AGENT_NO_PERMISSION_SENTINEL: - return None - - if object_permission_id is None: - agent_row = await AgentsRepository(prisma_client).table.find_unique( - where={"agent_id": agent_id}, - ) - object_permission_id = ( - getattr(agent_row, "object_permission_id", None) if agent_row is not None else None - ) - await user_api_key_cache.async_set_cache( - key=cache_key, - value=object_permission_id or MCPRequestHandler._AGENT_NO_PERMISSION_SENTINEL, - ttl=get_management_object_ttl(user_api_key_cache), - ) - if not object_permission_id: - return None - - return await get_object_permission( - object_permission_id=object_permission_id, - prisma_client=prisma_client, - user_api_key_cache=user_api_key_cache, - parent_otel_span=user_api_key_auth.parent_otel_span, - proxy_logging_obj=proxy_logging_obj, - ) - except Exception as e: - verbose_logger.warning(f"Failed to get agent object permission: {str(e)}") + object_permission_id = await MCPRequestHandler._agent_object_permission_id(agent_id, prisma_client) + if object_permission_id is None: return None + return await MCPRequestHandler._load_named_object_permission( + principal=f"agent {agent_id!r}", + object_permission_id=object_permission_id, + prisma_client=prisma_client, + user_api_key_auth=user_api_key_auth, + ) + @staticmethod async def _get_allowed_mcp_servers_for_agent( user_api_key_auth: Optional[UserAPIKeyAuth] = None, @@ -2711,7 +2830,9 @@ class MCPRequestHandler: Get allowed MCP servers for an agent (from the agent's object_permission). Returns the MCP servers from the agent's object_permission. - If agent has no object_permission, returns [] (no extra restriction). + If agent has no object_permission, returns [] (no extra restriction). An entitlement the + agent LINKS but that cannot be read raises ``UnloadableEntitlementError`` out of here so the + resolver denies. Args: user_api_key_auth: User auth with agent_id @@ -2721,13 +2842,13 @@ class MCPRequestHandler: if not user_api_key_auth or not user_api_key_auth.agent_id: return [] - try: - obj_perm = agent_object_permission - if obj_perm is None: - obj_perm = await MCPRequestHandler._get_agent_object_permission(user_api_key_auth) - if obj_perm is None: - return [] + obj_perm = agent_object_permission + if obj_perm is None: + obj_perm = await MCPRequestHandler._get_agent_object_permission(user_api_key_auth) + if obj_perm is None: + return [] + try: direct_mcp_servers = getattr(obj_perm, "mcp_servers", None) or [] if isinstance(direct_mcp_servers, str): direct_mcp_servers = [] @@ -2757,7 +2878,9 @@ class MCPRequestHandler: ) -> Optional[List[str]]: """ Get allowed tool names for a server from the agent's object_permission. - Returns None if agent has no tool restrictions for this server. + Returns None if agent has no tool restrictions for this server. An entitlement the agent + LINKS but that cannot be read raises ``UnloadableEntitlementError`` out of here, which the + tool resolver turns into deny-all for the server rather than an unrestricted tool list. Args: server_id: Server ID to check permissions for @@ -2768,13 +2891,13 @@ class MCPRequestHandler: if not user_api_key_auth or not user_api_key_auth.agent_id: return None - try: - obj_perm = agent_object_permission - if obj_perm is None: - obj_perm = await MCPRequestHandler._get_agent_object_permission(user_api_key_auth) - if obj_perm is None: - return None + obj_perm = agent_object_permission + if obj_perm is None: + obj_perm = await MCPRequestHandler._get_agent_object_permission(user_api_key_auth) + if obj_perm is None: + return None + try: mcp_tool_permissions = getattr(obj_perm, "mcp_tool_permissions", None) if not mcp_tool_permissions or not isinstance(mcp_tool_permissions, dict): return None diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/auth/test_user_api_key_auth_mcp.py b/tests/test_litellm/proxy/_experimental/mcp_server/auth/test_user_api_key_auth_mcp.py index 89b8f018e5c..0b95a497882 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/auth/test_user_api_key_auth_mcp.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/auth/test_user_api_key_auth_mcp.py @@ -8191,3 +8191,177 @@ class TestGetUserObjectPermission: async def test_no_user_id_places_no_ceiling(self): assert await MCPRequestHandler._get_user_object_permission(UserAPIKeyAuth(api_key="sk-test")) is None assert await MCPRequestHandler._get_user_object_permission(None) is None + + +def _key_auth_reaching(server, *, tools=None, **fields): + """A key-authenticated caller whose OWN key grant reaches ``server`` (and optionally its ``tools``). + + The key grant is the thing an upper-level entitlement fault must not silently hand back: every + test below asserts against what this key reaches when the level under test cannot be resolved. + """ + return UserAPIKeyAuth( + api_key="sk-hash", + user_id="u1", + object_permission=LiteLLM_ObjectPermissionTable( + object_permission_id="op-key", + mcp_servers=[server], + mcp_tool_permissions={server: tools} if tools else None, + ), + **fields, + ) + + +def _agent_prisma(object_permission_id=None, side_effect=None): + prisma_client = MagicMock() + prisma_client.db.litellm_agentstable.find_unique = AsyncMock( + return_value=MagicMock(object_permission_id=object_permission_id), + side_effect=side_effect, + ) + return prisma_client + + +@contextlib.contextmanager +def _entitlement_fault_globals(prisma_client=None): + from litellm.caching.dual_cache import DualCache + + with ( + patch("litellm.proxy.proxy_server.prisma_client", prisma_client or MagicMock()), + patch("litellm.proxy.proxy_server.user_api_key_cache", DualCache()), + patch("litellm.proxy.proxy_server.proxy_logging_obj", MagicMock()), + ): + yield + + +@pytest.mark.asyncio +class TestEntitlementFaultSemantics: + """Each entitlement level distinguishes two fault classes for a KEY-authenticated caller. + + A principal row that NAMES an object_permission we cannot load is a known entitlement with + unknown contents, so the level denies rather than handing back the wider key scope. A lookup + that fails before we can tell whether the principal is entitled at all leaves no ceiling, which + is the state that existed before the level did; denying there would refuse MCP to the majority + of callers, who have no such entitlement configured, for the duration of a cold-cache fault. + """ + + async def test_end_user_named_but_unloadable_permission_denies(self): + end_user = MagicMock(object_permission=None, object_permission_id="op-eu") + auth = _key_auth_reaching("srv1", end_user_id="eu-1") + with _entitlement_fault_globals(): + with ( + patch("litellm.proxy.auth.auth_checks.get_end_user_object", AsyncMock(return_value=end_user)), + patch("litellm.proxy.auth.auth_checks.get_object_permission", AsyncMock(return_value=None)), + ): + allowed = await MCPRequestHandler.get_allowed_mcp_servers(auth) + assert allowed == [], "an end-user entitlement we know exists but cannot read must deny" + + async def test_end_user_without_an_entitlement_places_no_ceiling(self): + """The three shapes that are NOT evidence of an entitlement: an end user row linking no + permission, no end user row at all, and a lookup that blew up before answering either.""" + auth = _key_auth_reaching("srv1", end_user_id="eu-1") + linked_none = MagicMock(object_permission=None, object_permission_id=None) + for lookup, shape in ( + (AsyncMock(return_value=linked_none), "row links no permission"), + (AsyncMock(return_value=None), "no end user row"), + (AsyncMock(side_effect=RuntimeError("connection reset by peer")), "lookup failed"), + ): + with _entitlement_fault_globals(): + with patch("litellm.proxy.auth.auth_checks.get_end_user_object", lookup): + allowed = await MCPRequestHandler.get_allowed_mcp_servers(auth) + assert set(allowed) == {"srv1"}, f"{shape}: no evidence of an entitlement, so no ceiling" + + async def test_agent_named_but_unloadable_permission_denies(self): + auth = _key_auth_reaching("srv1", agent_id="agent-unloadable") + with _entitlement_fault_globals(_agent_prisma(object_permission_id="op-agent")): + with patch("litellm.proxy.auth.auth_checks.get_object_permission", AsyncMock(return_value=None)): + allowed = await MCPRequestHandler.get_allowed_mcp_servers(auth) + assert allowed == [], "an agent entitlement we know exists but cannot read must deny" + + async def test_agent_without_an_entitlement_places_no_ceiling(self): + """An agent row linking no permission, and an agent row we could not read at all.""" + for prisma_client, agent_id, shape in ( + (_agent_prisma(object_permission_id=None), "agent-unlinked", "agent links no permission"), + (_agent_prisma(side_effect=RuntimeError("connection reset by peer")), "agent-unread", "row read failed"), + ): + auth = _key_auth_reaching("srv1", agent_id=agent_id) + with _entitlement_fault_globals(prisma_client): + allowed = await MCPRequestHandler.get_allowed_mcp_servers(auth) + assert set(allowed) == {"srv1"}, f"{shape}: no evidence of an entitlement, so no ceiling" + + async def test_agent_named_but_unloadable_permission_denies_tools(self): + """The tools axis denies with [] rather than the None (allow-all) key auth gets for an + indeterminate fault, so an unreadable agent entitlement cannot widen the key's tool scope.""" + auth = _key_auth_reaching("srv1", tools=["tool_a"], agent_id="agent-tools-unloadable") + with _entitlement_fault_globals(_agent_prisma(object_permission_id="op-agent")): + with patch("litellm.proxy.auth.auth_checks.get_object_permission", AsyncMock(return_value=None)): + tools = await MCPRequestHandler.get_allowed_tools_for_server("srv1", auth) + assert tools == [], "an agent entitlement we know exists but cannot read must deny its tools" + + async def test_org_named_but_unloadable_ceiling_denies(self): + auth = _key_auth_reaching("srv1", org_id="org-a") + org = MagicMock(object_permission_id="op-org") + with _entitlement_fault_globals(): + with ( + patch("litellm.proxy.auth.auth_checks.get_org_object", AsyncMock(return_value=org)), + patch("litellm.proxy.auth.auth_checks.get_object_permission", AsyncMock(return_value=None)), + ): + allowed = await MCPRequestHandler.get_allowed_mcp_servers(auth) + assert allowed == [], "an org ceiling we know exists but cannot read must deny, key auth included" + + async def test_org_named_but_unloadable_ceiling_denies_tools(self): + auth = _key_auth_reaching("srv1", tools=["tool_a"], org_id="org-a") + org = MagicMock(object_permission_id="op-org") + with _entitlement_fault_globals(): + with ( + patch("litellm.proxy.auth.auth_checks.get_org_object", AsyncMock(return_value=org)), + patch("litellm.proxy.auth.auth_checks.get_object_permission", AsyncMock(return_value=None)), + ): + tools = await MCPRequestHandler.get_allowed_tools_for_server("srv1", auth) + assert tools == [], "an org tool ceiling we know exists but cannot read must deny its tools" + + async def test_org_without_a_resolvable_entitlement_places_no_ceiling(self): + """A deleted org and an org lookup that failed are both cases where we cannot point at a + ceiling; key auth keeps its long-standing fail-open behavior for them.""" + from litellm.proxy.auth.auth_checks import OrganizationNotFoundError + + auth = _key_auth_reaching("srv1", org_id="org-a") + for lookup, shape in ( + (AsyncMock(return_value=MagicMock(object_permission_id=None)), "org names no permission"), + (AsyncMock(side_effect=OrganizationNotFoundError("Organization doesn't exist in db.")), "org deleted"), + (AsyncMock(side_effect=RuntimeError("connection reset by peer")), "org lookup failed"), + ): + with _entitlement_fault_globals(): + with patch("litellm.proxy.auth.auth_checks.get_org_object", lookup): + allowed = await MCPRequestHandler.get_allowed_mcp_servers(auth) + assert set(allowed) == {"srv1"}, f"{shape}: no ceiling we can point at, so key auth stays open" + + async def test_keyless_org_ceiling_denies_on_either_fault_class(self): + """The keyless gateway-admitted path is untouched: it already denied on ANY org-ceiling + fault, and still denies on both classes, because a per-source org ceiling is the only org + bound a keyless subject has and an unbounded source would win the union.""" + auth = _make_admitted_subject("sso-user", org_id="org-a", own_servers=["srv1"]) + org = MagicMock(object_permission_id="op-org") + with _entitlement_fault_globals(): + with patch("litellm.proxy.auth.auth_checks.get_org_object", AsyncMock(return_value=org)): + with patch("litellm.proxy.auth.auth_checks.get_object_permission", AsyncMock(return_value=None)): + named_unloadable = await MCPRequestHandler.get_allowed_mcp_servers(auth) + with patch( + "litellm.proxy.auth.auth_checks.get_org_object", + AsyncMock(side_effect=RuntimeError("connection reset by peer")), + ): + indeterminate = await MCPRequestHandler.get_allowed_mcp_servers(auth) + assert named_unloadable == [] and indeterminate == [] + + async def test_keyless_source_never_consults_the_end_user_or_agent_levels(self): + """A keyless subject's grant sources carry neither end_user_id nor agent_id, so neither + level runs for it and neither new deny can reach its union. Pinned because a source that + DID consult them would fail closed on a fault and silently drop a team's grants.""" + auth = _make_admitted_subject("sso-user", own_servers=["srv1"]) + auth.end_user_id = "eu-1" + auth.agent_id = "agent-unloadable" + with _entitlement_fault_globals(_agent_prisma(object_permission_id="op-agent")): + with ( + patch("litellm.proxy.auth.auth_checks.get_end_user_object", AsyncMock(side_effect=AssertionError)), + patch("litellm.proxy.auth.auth_checks.get_object_permission", AsyncMock(return_value=None)), + ): + allowed = await MCPRequestHandler.get_allowed_mcp_servers(auth) + assert set(allowed) == {"srv1"} From 416e39815474b35acdfa02d18f619d84f8101582 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Fri, 31 Jul 2026 09:26:30 -0700 Subject: [PATCH 20/92] feat(proxy): add generic list handler for /management/v1 (#35308) * feat(proxy): add generic list handler for /management/v1 Adds the ListSpec/QueryPlan machinery the control-plane list endpoints are meant to share, so a resource declares what it exposes instead of hand-rolling its own paging, sorting and filter parsing. build_query_plan is pure: it turns query parameters into a QueryPlan or an RFC 9457 problem without any I/O, which is what lets the plan be asserted as a value. The database half is a ListExecutor protocol injected by the caller, so this module has no Prisma dependency at all. Four things the framework guarantees rather than leaving to each resource: the spec's unique tiebreaker is always the final sort key, so pages cannot repeat rows when the leading column is all nulls; ordering is NULLS LAST in both directions, since Postgres otherwise floats empty values to the top the moment the sort direction flips; the scope predicate is a separate conjunct ahead of every caller filter, so a filter on a scoped column cannot widen it; and a denied scope is a 403 problem rather than a 200 with an empty list. No route and no consumer yet; budgets registers against it next. The facet endpoint's has_more shapes are untouched, and a test pins them so page mode cannot quietly absorb them. * fix(proxy): accept the bare filter[field] form in the list framework Section 5 of the design doc spells equality without an operator bracket (`?filter[status]=active`, and `/management/v1/keys?filter[team_id]=` in the sub-resource paragraph); only the other operators carry a second bracket. The parser only understood `filter[field][op]`, so the canonical spelling came back as an unknown query parameter. `filter[field]` now resolves to the field's `eq` operator, which means it still goes through the declared operator set rather than around it: a field that does not offer `eq` rejects the shorthand. The allowed-parameter list advertises the bare spelling for `eq` and the bracketed one for everything else. Drops two guards from the key parser that could not fire. Operator validation already rejects every malformed operator, and `field in spec.filters` already rejects every field nobody declared, so a well-formedness check on top of them was unreachable; the tests cover the malformed keys directly instead. * fix(proxy): validate list specs at construction and reject repeated params Two gaps a review flagged on the list framework. The page-size cap was only enforced against a supplied page_size, so a spec whose default_page_size exceeded its max_page_size served more rows than the resource allows on exactly the request that omits the parameter. A default of zero was worse: it reached the total_pages division and made the resource 500 on every request. ListSpec now validates 1 <= default_page_size <= max_page_size when it is built, so a misconfigured resource fails as it is registered rather than per request. default_sort is checked against sortable for the same reason; caller-supplied sort was already validated, but the default never passed through that path and a typo there reached the ORDER BY clause untouched. Raising is right here despite the usual model-failures-as-values rule: there is no request in flight and no caller to answer. Repeated query parameters silently collapsed to their last value, so ?page=1&page=999 paged from 999 and a repeated sort key quietly won, which is the same silently-altered-semantics failure the surface already rejects unknown parameters to avoid. They are now a 400. The check lives in handle_list rather than build_query_plan because a Mapping[str, str] cannot represent a repeat at all; the boundary that can see one is the boundary that rejects it. A denied scope still outranks it, matching every other rejection here. Also corrects the order_by_sql docstring, which claimed every field reaching it had been validated against sortable. That held for caller-supplied sort only. * refactor(proxy): model list predicates as frozen values instead of dicts The LIT002 budget rejected the framework: building a where-fragment meant a dict literal per operator, and a dict keyed by a column name chosen at runtime cannot be frozen into a TypedDict or a dataclass field, so there was no spelling of the old shape the rule would accept. Replacing the fragments with a tagged union removes the construction entirely. A plan's where is now a tuple of frozen Compare / Within / IsNull / AnyOf, matched exhaustively, and the field name is a value rather than a key. That also retires the Mapping[str, object] the plan used to carry, which said nothing about what was inside it and left the fragment shape as a convention two sides had to keep agreeing on. Scope predicates take the same type, so a resource declares its row filter in the same vocabulary rather than hand-rolling a backend dict. where_sql renders a plan for a raw-SQL executor, binding every caller-supplied value to a numbered placeholder and writing only spec-declared column names into the statement. It is the counterpart to order_by_sql, which already existed for the same reason: nulls ordering forces the executor onto raw SQL, so the escaping and placeholder arithmetic belong in one reviewed place rather than in each consumer. Also folds the two remaining mutable builds out of the module (set comprehensions and Counter to frozenset/tuple, the serialized page to a tuple pydantic coerces), and lifts the LIKE escaper into common.py so the facet endpoint and the framework share one copy instead of two that can drift. No behavioural change to the facet endpoint; its tests, including the one pinning the escaping, pass untouched. --- .../management_v1/common.py | 38 +- .../management_v1/list_framework.py | 522 +++++++++++ .../management_v1/spend_logs.py | 7 +- .../management_endpoints/management_v1.py | 34 + .../management_v1/test_list_framework.py | 871 ++++++++++++++++++ 5 files changed, 1458 insertions(+), 14 deletions(-) create mode 100644 litellm/proxy/management_endpoints/management_v1/list_framework.py create mode 100644 tests/test_litellm/proxy/management_endpoints/management_v1/test_list_framework.py diff --git a/litellm/proxy/management_endpoints/management_v1/common.py b/litellm/proxy/management_endpoints/management_v1/common.py index c0e7f49f2e9..daa2c60ac5e 100644 --- a/litellm/proxy/management_endpoints/management_v1/common.py +++ b/litellm/proxy/management_endpoints/management_v1/common.py @@ -7,6 +7,7 @@ from fastapi.dependencies.utils import get_flat_dependant from fastapi.responses import JSONResponse from litellm.types.proxy.management_endpoints.management_v1 import ( + ListLinks, PageLinks, ProblemDetail, ) @@ -43,6 +44,21 @@ def _declared_query_params(request: Request) -> frozenset[str]: return frozenset(field.alias for field in get_flat_dependant(dependant, skip_repeats=True).query_params) +def escape_like(value: str) -> str: + """Escape LIKE/ILIKE metacharacters. Ids routinely contain `_`, which is a wildcard unescaped.""" + return value.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_") + + +def unknown_query_param_problem(unknown: tuple[str, ...], allowed: tuple[str, ...]) -> ProblemDetail: + return ProblemDetail( + type=f"{PROBLEM_TYPE_BASE}unknown-query-parameter", + title="Unknown query parameter", + status=400, + detail=f"Unrecognized query parameter(s): {', '.join(unknown)}.", + allowed=sorted(allowed), + ) + + async def reject_unknown_query_params(request: Request) -> None: """Reject any query param the route did not declare. @@ -53,15 +69,7 @@ async def reject_unknown_query_params(request: Request) -> None: unknown: tuple[str, ...] = tuple(sorted(name for name in request.query_params if name not in declared)) if not unknown: return - raise ManagementProblem( - ProblemDetail( - type=f"{PROBLEM_TYPE_BASE}unknown-query-parameter", - title="Unknown query parameter", - status=400, - detail=f"Unrecognized query parameter(s): {', '.join(unknown)}.", - allowed=sorted(declared), - ) - ) + raise ManagementProblem(unknown_query_param_problem(unknown=unknown, allowed=tuple(sorted(declared)))) def _page_url(request: Request, page: int) -> str: @@ -75,3 +83,15 @@ def build_page_links(request: Request, page: int, has_more: bool) -> PageLinks: prev=_page_url(request, page - 1) if page > 1 else None, next=_page_url(request, page + 1) if has_more else None, ) + + +def build_list_links(request: Request, page: int, total_pages: int) -> ListLinks: + """Page-mode links. `last` clamps to page 1 on an empty result set so every link still resolves.""" + last = max(total_pages, 1) + return ListLinks( + self_link=_page_url(request, page), + first=_page_url(request, 1), + prev=_page_url(request, page - 1) if page > 1 else None, + next=_page_url(request, page + 1) if page < last else None, + last=_page_url(request, last), + ) diff --git a/litellm/proxy/management_endpoints/management_v1/list_framework.py b/litellm/proxy/management_endpoints/management_v1/list_framework.py new file mode 100644 index 00000000000..3e4b9131d1e --- /dev/null +++ b/litellm/proxy/management_endpoints/management_v1/list_framework.py @@ -0,0 +1,522 @@ +"""Generic list handling for `/management/v1` collection routes. + +A resource declares a `ListSpec`; `build_query_plan` turns query parameters into a +`QueryPlan` or an RFC 9457 problem without touching a database, and `handle_list` +runs that plan through an injected `ListExecutor`. Keeping the planning pure is what +lets a caller assert the plan as a value instead of asserting against a live Prisma +client, and it keeps this module free of any database dependency. + +A plan's `where` is a tuple of frozen `Predicate`s rather than a backend-shaped +mapping, so the framework never has to know which query builder executes it and a +planned predicate cannot be rewritten afterwards. `where_sql` renders one for a +raw-SQL executor with every caller-supplied value bound to a placeholder. +""" + +from collections.abc import Callable, Mapping, Sequence +from dataclasses import dataclass +from datetime import datetime, timezone +from math import ceil +from typing import Generic, Literal, Protocol, TypeVar + +from fastapi import Request +from pydantic import TypeAdapter, ValidationError +from typing_extensions import assert_never + +from litellm.proxy._types import UserAPIKeyAuth +from litellm.proxy.management_endpoints.management_v1.common import ( + PROBLEM_TYPE_BASE, + ManagementProblem, + build_list_links, + escape_like, + unknown_query_param_problem, +) +from litellm.types.proxy.management_endpoints.management_v1 import ( + ListMeta, + ListResponse, + ProblemDetail, +) + +ComparisonOp = Literal["eq", "gte", "lte", "gt", "lt", "contains", "not"] +# `is_null` is not in the design doc's operator set. It is here because there is no +# other way to ask for "max_budget IS NULL", and a table that renders nulls as +# "Unlimited" has to be able to filter on them. +FilterOp = ComparisonOp | Literal["in", "is_null"] + +FilterType = type[str] | type[int] | type[float] | type[datetime] +FilterValue = str | int | float | datetime + +PAGE_PARAM = "page" +PAGE_SIZE_PARAM = "page_size" +SORT_PARAM = "sort" +SEARCH_PARAM = "q" + +TRow = TypeVar("TRow") +TRow_co = TypeVar("TRow_co", covariant=True) +TOut = TypeVar("TOut") + +_FILTER_OP_ADAPTER: TypeAdapter[FilterOp] = TypeAdapter(FilterOp) + + +@dataclass(frozen=True, slots=True) +class Compare: + """`field value`.""" + + field: str + op: ComparisonOp + value: FilterValue + + +@dataclass(frozen=True, slots=True) +class Within: + """`field IN (values)`.""" + + field: str + values: tuple[FilterValue, ...] + + +@dataclass(frozen=True, slots=True) +class IsNull: + """`field IS NULL`, or `IS NOT NULL` when negated.""" + + field: str + negated: bool + + +@dataclass(frozen=True, slots=True) +class AnyOf: + """Disjunction of its clauses. `?q=` is the only producer today.""" + + clauses: tuple["Predicate", ...] + + +Predicate = Compare | Within | IsNull | AnyOf + + +@dataclass(frozen=True, slots=True) +class FilterSpec: + type: FilterType + ops: frozenset[FilterOp] + + +@dataclass(frozen=True, slots=True) +class SortKey: + field: str + descending: bool + + +@dataclass(frozen=True, slots=True) +class ScopeAll: + """The caller may read every row of the resource.""" + + +@dataclass(frozen=True, slots=True) +class ScopeWhere: + """The caller may read the rows matching every predicate in `where`.""" + + where: tuple[Predicate, ...] + + +@dataclass(frozen=True, slots=True) +class ScopeDenied: + """The caller may read no rows at all, and should be told so rather than shown an empty page.""" + + reason: str + + +Scope = ScopeAll | ScopeWhere | ScopeDenied + + +@dataclass(frozen=True, slots=True) +class ListSpec(Generic[TRow, TOut]): + resource: str + sortable: frozenset[str] + searchable: frozenset[str] + filters: Mapping[str, FilterSpec] + default_sort: tuple[SortKey, ...] + default_page_size: int + max_page_size: int + scope: Callable[[UserAPIKeyAuth], Scope] + serialize: Callable[[TRow], TOut] + tiebreaker: str + + def __post_init__(self) -> None: + """A malformed spec is a programming error at import time, so this raises rather than + returning a problem: there is no request in flight and no caller to answer.""" + if not 1 <= self.default_page_size <= self.max_page_size: + raise ValueError( + f"{self.resource}: default_page_size must be between 1 and max_page_size " + f"({self.max_page_size}), got {self.default_page_size}. A default above the cap " + f"would serve more rows than the resource allows whenever page_size is omitted." + ) + if not self.tiebreaker: + raise ValueError(f"{self.resource}: tiebreaker is required; it is the final sort key on every query.") + undeclared = tuple(sorted(frozenset(key.field for key in self.default_sort) - self.sortable)) + if undeclared: + raise ValueError(f"{self.resource}: default_sort orders by non-sortable field(s): {', '.join(undeclared)}.") + non_text = tuple( + sorted(field for field, spec in self.filters.items() if "contains" in spec.ops and spec.type is not str) + ) + if non_text: + raise ValueError( + f"{self.resource}: contains renders as ILIKE and is only meaningful on text columns, " + f"but is declared on: {', '.join(non_text)}." + ) + + +@dataclass(frozen=True, slots=True) +class QueryPlan: + """`where` is an implicit AND, ordered scope-first; `order` always ends with the spec's tiebreaker.""" + + where: tuple[Predicate, ...] + order: tuple[SortKey, ...] + skip: int + take: int + + +class ListExecutor(Protocol[TRow_co]): + """The database half of a list, injected so this module never imports Prisma.""" + + async def count(self, where: tuple[Predicate, ...]) -> int: ... + + async def find_many(self, plan: QueryPlan) -> Sequence[TRow_co]: ... + + +def order_by_sql(order: tuple[SortKey, ...]) -> str: + """`ORDER BY` body for a plan, NULLS LAST in both directions. + + Postgres sorts nulls last ascending but first descending, so an unqualified flip of + the sort direction drags every "Unlimited" row to the top of the table. Every field + reaching here is either a member of `ListSpec.sortable` (caller-supplied sort is + checked against it, `default_sort` at construction) or the spec's `tiebreaker`, so + these are developer-declared column names, never caller-controlled text. + """ + return ", ".join(f'"{key.field}" {"DESC" if key.descending else "ASC"} NULLS LAST' for key in order) + + +def _sql_operator(op: ComparisonOp) -> str: + match op: + case "eq": + return "=" + case "not": + return "<>" + case "gte": + return ">=" + case "lte": + return "<=" + case "gt": + return ">" + case "lt": + return "<" + case "contains": + return "ILIKE" + case _: + assert_never(op) + + +def _render(predicate: Predicate, index: int) -> tuple[str, tuple[object, ...]]: + match predicate: + case IsNull(field=field, negated=negated): + return f'"{field}" IS {"NOT NULL" if negated else "NULL"}', () + case Within(field=field, values=values): + placeholders = ", ".join(f"${index + offset}" for offset in range(len(values))) + return f'"{field}" IN ({placeholders})', values + case AnyOf(clauses=clauses): + rendered, params = _render_all(clauses, index) + return f"({' OR '.join(rendered)})", params + case Compare(field=field, op="contains", value=value): + return f"\"{field}\" ILIKE ${index} ESCAPE '\\'", (f"%{escape_like(str(value))}%",) + case Compare(field=field, op=op, value=value): + return f'"{field}" {_sql_operator(op)} ${index}', (value,) + case _: + assert_never(predicate) + + +def _render_all(predicates: tuple[Predicate, ...], index: int) -> tuple[tuple[str, ...], tuple[object, ...]]: + if not predicates: + return (), () + head, head_params = _render(predicates[0], index) + tail, tail_params = _render_all(predicates[1:], index + len(head_params)) + return (head, *tail), head_params + tail_params + + +def where_sql(where: tuple[Predicate, ...], first_index: int = 1) -> tuple[str, tuple[object, ...]]: + """`WHERE` body and its bind parameters, numbered from `first_index`. + + Returns `("", ())` when there is nothing to filter on. Every caller-supplied value + becomes a `$n` placeholder rather than being written into the SQL text; only column + names reach the text, and those come from the spec's own declarations. + """ + clauses, params = _render_all(where, first_index) + return " AND ".join(clauses), params + + +def _problem(slug: str, title: str, status: int, detail: str, allowed: tuple[str, ...] | None = None) -> ProblemDetail: + return ProblemDetail( + type=f"{PROBLEM_TYPE_BASE}{slug}", + title=title, + status=status, + detail=detail, + allowed=sorted(allowed) if allowed is not None else None, + ) + + +def _invalid(detail: str) -> ProblemDetail: + return _problem("invalid-query-parameter", "Invalid query parameter", 400, detail) + + +def _parse_filter_key(name: str) -> tuple[str, FilterOp] | None: + """`filter[max_budget][gte]` -> `("max_budget", "gte")`; bare `filter[status]` -> `("status", "eq")`.""" + if not name.startswith("filter[") or not name.endswith("]"): + return None + field, separator, raw_op = name[len("filter[") : -1].partition("][") + if not separator: + return field, "eq" + try: + return field, _FILTER_OP_ADAPTER.validate_python(raw_op) + except ValidationError: + return None + + +def _is_known_param(spec: ListSpec[TRow, TOut], name: str) -> bool: + if name in (PAGE_PARAM, PAGE_SIZE_PARAM): + return True + if name == SORT_PARAM: + return bool(spec.sortable) + if name == SEARCH_PARAM: + return bool(spec.searchable) + parsed = _parse_filter_key(name) + return parsed is not None and parsed[0] in spec.filters + + +def _allowed_params(spec: ListSpec[TRow, TOut]) -> tuple[str, ...]: + return tuple( + sorted( + (PAGE_PARAM, PAGE_SIZE_PARAM) + + ((SORT_PARAM,) if spec.sortable else ()) + + ((SEARCH_PARAM,) if spec.searchable else ()) + + tuple( + f"filter[{field}]" if op == "eq" else f"filter[{field}][{op}]" + for field, filter_spec in spec.filters.items() + for op in filter_spec.ops + ) + ) + ) + + +def _parse_positive_int(name: str, raw: str) -> int | ProblemDetail: + try: + value = int(raw) + except ValueError: + return _invalid(f"'{name}' must be an integer.") + if value < 1: + return _invalid(f"'{name}' must be 1 or greater.") + return value + + +def _parse_page(params: Mapping[str, str]) -> int | ProblemDetail: + raw = params.get(PAGE_PARAM) + return 1 if raw is None else _parse_positive_int(PAGE_PARAM, raw) + + +def _parse_page_size(spec: ListSpec[TRow, TOut], params: Mapping[str, str]) -> int | ProblemDetail: + raw = params.get(PAGE_SIZE_PARAM) + if raw is None: + return spec.default_page_size + value = _parse_positive_int(PAGE_SIZE_PARAM, raw) + if isinstance(value, ProblemDetail): + return value + return min(value, spec.max_page_size) + + +def _parse_sort(spec: ListSpec[TRow, TOut], params: Mapping[str, str]) -> tuple[SortKey, ...] | ProblemDetail: + raw = params.get(SORT_PARAM) + if raw is None: + return spec.default_sort + segments = tuple(segment.strip() for segment in raw.split(",")) + keys = tuple( + SortKey(field=segment[1:] if segment.startswith("-") else segment, descending=segment.startswith("-")) + for segment in segments + ) + rejected = tuple(sorted(frozenset(key.field for key in keys) - spec.sortable)) + if rejected: + return _problem( + "invalid-sort-field", + "Invalid sort field", + 400, + f"Cannot sort {spec.resource} by: {', '.join(repr(field) for field in rejected)}.", + tuple(spec.sortable), + ) + return keys + + +def _to_utc(value: datetime) -> datetime: + return value.replace(tzinfo=timezone.utc) if value.tzinfo is None else value.astimezone(timezone.utc) + + +def _coerce(field: str, op: FilterOp, raw: str, target: FilterType) -> FilterValue | ProblemDetail: + try: + if target is str: + return raw + if target is int: + return int(raw) + if target is float: + return float(raw) + return _to_utc(datetime.fromisoformat(raw[:-1] + "+00:00" if raw.endswith("Z") else raw)) + except ValueError: + return _invalid(f"'filter[{field}][{op}]' is not a valid {target.__name__}: {raw!r}.") + + +def _null_predicate(field: str, raw: str) -> Predicate | ProblemDetail: + if raw.lower() == "true": + return IsNull(field=field, negated=False) + if raw.lower() == "false": + return IsNull(field=field, negated=True) + return _invalid(f"'filter[{field}][is_null]' must be 'true' or 'false'.") + + +def _within_predicate(field: str, raw: str, target: FilterType) -> Predicate | ProblemDetail: + coerced = tuple(_coerce(field, "in", item.strip(), target) for item in raw.split(",")) + problems = tuple(item for item in coerced if isinstance(item, ProblemDetail)) + if problems: + return problems[0] + return Within(field=field, values=tuple(item for item in coerced if not isinstance(item, ProblemDetail))) + + +def _parse_filter(field: str, op: FilterOp, raw: str, filter_spec: FilterSpec) -> Predicate | ProblemDetail: + if op not in filter_spec.ops: + return _problem( + "unsupported-filter-operator", + "Unsupported filter operator", + 400, + f"Operator '{op}' is not supported on '{field}'.", + tuple(filter_spec.ops), + ) + if op == "is_null": + return _null_predicate(field, raw) + if op == "in": + return _within_predicate(field, raw, filter_spec.type) + value = _coerce(field, op, raw, filter_spec.type) + if isinstance(value, ProblemDetail): + return value + return Compare(field=field, op=op, value=value) + + +def _parse_filters(spec: ListSpec[TRow, TOut], params: Mapping[str, str]) -> tuple[Predicate, ...] | ProblemDetail: + keys = tuple( + (name, parsed) + for name in sorted(params) + if (parsed := _parse_filter_key(name)) is not None and parsed[0] in spec.filters + ) + parsed = tuple(_parse_filter(field, op, params[name], spec.filters[field]) for name, (field, op) in keys) + problems = tuple(item for item in parsed if isinstance(item, ProblemDetail)) + if problems: + return problems[0] + return tuple(item for item in parsed if not isinstance(item, ProblemDetail)) + + +def _search_predicate(spec: ListSpec[TRow, TOut], params: Mapping[str, str]) -> Predicate | None: + raw = params.get(SEARCH_PARAM) + if not raw: + return None + return AnyOf(clauses=tuple(Compare(field=field, op="contains", value=raw) for field in sorted(spec.searchable))) + + +def _scope_predicates(scope: Scope) -> tuple[Predicate, ...] | ProblemDetail: + match scope: + case ScopeAll(): + return () + case ScopeWhere(where=where): + return where + case ScopeDenied(reason=reason): + return _problem("forbidden", "Forbidden", 403, reason) + case _: + assert_never(scope) + + +def build_query_plan( + spec: ListSpec[TRow, TOut], + params: Mapping[str, str], + caller: UserAPIKeyAuth, +) -> QueryPlan | ProblemDetail: + """Turn query parameters into a plan, or into the problem that explains why they are not one.""" + scope_predicates = _scope_predicates(spec.scope(caller)) + if isinstance(scope_predicates, ProblemDetail): + return scope_predicates + + unknown = tuple(sorted(name for name in params if not _is_known_param(spec, name))) + if unknown: + return unknown_query_param_problem(unknown=unknown, allowed=_allowed_params(spec)) + + page = _parse_page(params) + if isinstance(page, ProblemDetail): + return page + + page_size = _parse_page_size(spec, params) + if isinstance(page_size, ProblemDetail): + return page_size + + sort = _parse_sort(spec, params) + if isinstance(sort, ProblemDetail): + return sort + + filters = _parse_filters(spec, params) + if isinstance(filters, ProblemDetail): + return filters + + search = _search_predicate(spec, params) + return QueryPlan( + # Scope first: conjuncts a caller filter sits behind and cannot replace. + where=scope_predicates + filters + ((search,) if search is not None else ()), + # Ordering by an all-null column without a unique final key lets Postgres return + # the same row on two different pages. + order=sort + (SortKey(field=spec.tiebreaker, descending=False),), + skip=(page - 1) * page_size, + take=page_size, + ) + + +def _duplicate_params(request: Request) -> tuple[str, ...]: + names = tuple(name for name, _ in request.query_params.multi_items()) + return tuple(sorted(frozenset(name for name in names if names.count(name) > 1))) + + +async def handle_list( + spec: ListSpec[TRow, TOut], + executor: ListExecutor[TRow], + request: Request, + caller: UserAPIKeyAuth, +) -> ListResponse[TOut]: + """Plan, execute, count, serialize, envelope. Failures reach the client as RFC 9457 problems.""" + plan = build_query_plan(spec=spec, params=request.query_params, caller=caller) + if isinstance(plan, ProblemDetail): + raise ManagementProblem(plan) + + # Checked here rather than in build_query_plan because a Mapping[str, str] cannot + # represent a repeat: query_params.get() silently keeps the last one, so ?page=1&page=999 + # would page from 999 without the caller ever being told which value won. + duplicates = _duplicate_params(request) + if duplicates: + raise ManagementProblem( + _problem( + "duplicate-query-parameter", + "Duplicate query parameter", + 400, + f"Repeated query parameter(s): {', '.join(duplicates)}. Each may appear once; " + f"use a comma-separated list for multiple sort keys or filter values.", + ) + ) + + total_count = await executor.count(plan.where) + rows = await executor.find_many(plan) + total_pages = ceil(total_count / plan.take) + page = plan.skip // plan.take + 1 + return ListResponse[TOut]( + data=tuple(spec.serialize(row) for row in rows), + meta=ListMeta( + total_count=total_count, + page=page, + page_size=plan.take, + total_pages=total_pages, + ), + links=build_list_links(request=request, page=page, total_pages=total_pages), + ) diff --git a/litellm/proxy/management_endpoints/management_v1/spend_logs.py b/litellm/proxy/management_endpoints/management_v1/spend_logs.py index c11a14bbfea..ccde3c4112c 100644 --- a/litellm/proxy/management_endpoints/management_v1/spend_logs.py +++ b/litellm/proxy/management_endpoints/management_v1/spend_logs.py @@ -13,6 +13,7 @@ from litellm.proxy.management_endpoints.management_v1.common import ( PROBLEM_TYPE_BASE, ManagementProblem, build_page_links, + escape_like, reject_unknown_query_params, ) from litellm.proxy.utils import PrismaClient @@ -34,10 +35,6 @@ def _as_utc(value: datetime) -> datetime: return value.replace(tzinfo=timezone.utc) if value.tzinfo is None else value.astimezone(timezone.utc) -def _escape_like(value: str) -> str: - return value.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_") - - async def _end_user_scope_clause( user_api_key_dict: UserAPIKeyAuth, prisma_client: PrismaClient, @@ -133,7 +130,7 @@ async def list_spend_log_end_users( ) window_params: tuple[Any, ...] = (_as_utc(start_time), _as_utc(end_time)) - search_params: tuple[Any, ...] = (f"%{_escape_like(q)}%",) if q else () + search_params: tuple[Any, ...] = (f"%{escape_like(q)}%",) if q else () search_clause = (f"end_user ILIKE ${len(window_params) + 1} ESCAPE '\\'",) if q else () scope_clause, scope_params = await _end_user_scope_clause( diff --git a/litellm/types/proxy/management_endpoints/management_v1.py b/litellm/types/proxy/management_endpoints/management_v1.py index 2aecc54f114..b2244f6eb9b 100644 --- a/litellm/types/proxy/management_endpoints/management_v1.py +++ b/litellm/types/proxy/management_endpoints/management_v1.py @@ -1,7 +1,11 @@ """Shared response shapes for the `/management/v1` control-plane surface.""" +from typing import Generic, TypeVar + from pydantic import BaseModel, ConfigDict, Field +TOut = TypeVar("TOut") + class ProblemDetail(BaseModel): """RFC 9457 problem details, served as `application/problem+json`.""" @@ -37,3 +41,33 @@ class FacetListResponse(BaseModel): data: list[str] meta: PageMeta links: PageLinks + + +class ListMeta(BaseModel): + """Page-mode counterpart to `PageMeta`: an entity list pays for the COUNT(*) so the table can show a page count.""" + + total_count: int + page: int + page_size: int + total_pages: int + + +class ListLinks(BaseModel): + """Page-mode counterpart to `PageLinks`. `first`/`last` are knowable here because the total count is.""" + + model_config = ConfigDict(populate_by_name=True) + + self_link: str = Field(alias="self") + first: str + prev: str | None = None + next: str | None = None + last: str + + +class ListResponse(BaseModel, Generic[TOut]): + """Rows stay flat: JSON:API's `{type, id, attributes}` wrapper is a deliberate deviation, so every + dashboard column accessor would otherwise have to go through `.attributes`.""" + + data: list[TOut] + meta: ListMeta + links: ListLinks diff --git a/tests/test_litellm/proxy/management_endpoints/management_v1/test_list_framework.py b/tests/test_litellm/proxy/management_endpoints/management_v1/test_list_framework.py new file mode 100644 index 00000000000..35bd5517361 --- /dev/null +++ b/tests/test_litellm/proxy/management_endpoints/management_v1/test_list_framework.py @@ -0,0 +1,871 @@ +from collections.abc import Mapping, Sequence +from dataclasses import dataclass, replace +from datetime import datetime, timezone + +import pytest +from fastapi import Request +from pydantic import BaseModel + +from litellm.proxy._types import UserAPIKeyAuth +from litellm.proxy.management_endpoints.management_v1.common import ( + MANAGEMENT_V1_PREFIX, + PROBLEM_TYPE_BASE, + ManagementProblem, + build_page_links, +) +from litellm.proxy.management_endpoints.management_v1.list_framework import ( + AnyOf, + Compare, + FilterSpec, + IsNull, + ListSpec, + QueryPlan, + ScopeAll, + ScopeDenied, + ScopeWhere, + SortKey, + Within, + build_query_plan, + handle_list, + order_by_sql, + where_sql, +) +from litellm.types.proxy.management_endpoints.management_v1 import ( + PageLinks, + PageMeta, + ProblemDetail, +) + +BUDGETS_PATH = f"{MANAGEMENT_V1_PREFIX}/budgets" +CALLER = UserAPIKeyAuth(user_id="caller-1") + + +@dataclass(frozen=True, slots=True) +class BudgetRow: + budget_id: str + max_budget: float | None + created_by: str + + +class BudgetOut(BaseModel): + budget_id: str + max_budget: float | None + + +def _serialize(row: BudgetRow) -> BudgetOut: + return BudgetOut(budget_id=row.budget_id, max_budget=row.max_budget) + + +def _spec( + scope=lambda caller: ScopeAll(), + searchable=frozenset({"budget_id", "created_by"}), + sortable=frozenset({"max_budget", "created_at", "budget_id"}), +) -> ListSpec[BudgetRow, BudgetOut]: + return ListSpec( + resource="budgets", + sortable=sortable, + searchable=searchable, + filters={ + "max_budget": FilterSpec(type=float, ops=frozenset({"eq", "gte", "lte", "is_null"})), + "created_at": FilterSpec(type=datetime, ops=frozenset({"gte", "lte"})), + "created_by": FilterSpec(type=str, ops=frozenset({"eq", "in", "contains"})), + "tpm_limit": FilterSpec(type=int, ops=frozenset({"eq"})), + }, + default_sort=(SortKey(field="created_at", descending=True),), + default_page_size=25, + max_page_size=100, + scope=scope, + serialize=_serialize, + tiebreaker="budget_id", + ) + + +def _spec_with(**overrides) -> ListSpec[BudgetRow, BudgetOut]: + """`replace` re-runs `__init__`, so the spec's own validation applies to the override.""" + return replace(_spec(), **overrides) + + +class RecordingExecutor: + """In-memory stand-in for the Prisma-backed executor PR 2 supplies.""" + + def __init__(self, rows: tuple[BudgetRow, ...], total_count: int | None = None) -> None: + self.rows = rows + self.total_count = len(rows) if total_count is None else total_count + self.plan: QueryPlan | None = None + self.count_where: tuple[object, ...] | None = None + + async def count(self, where: tuple[object, ...]) -> int: + self.count_where = where + return self.total_count + + async def find_many(self, plan: QueryPlan) -> Sequence[BudgetRow]: + self.plan = plan + return self.rows[plan.skip : plan.skip + plan.take] + + +def _request(query: str = "") -> Request: + return Request( + { + "type": "http", + "method": "GET", + "scheme": "http", + "root_path": "", + "path": BUDGETS_PATH, + "query_string": query.encode(), + "headers": [(b"host", b"testserver")], + } + ) + + +def _plan(query_params: Mapping[str, str], spec: ListSpec[BudgetRow, BudgetOut] | None = None) -> QueryPlan: + result = build_query_plan(spec=spec or _spec(), params=query_params, caller=CALLER) + assert isinstance(result, QueryPlan), result + return result + + +def _problem(query_params: Mapping[str, str], spec: ListSpec[BudgetRow, BudgetOut] | None = None) -> ProblemDetail: + result = build_query_plan(spec=spec or _spec(), params=query_params, caller=CALLER) + assert isinstance(result, ProblemDetail), result + return result + + +def _conjuncts(plan: QueryPlan) -> tuple[object, ...]: + return plan.where + + +# ---------------------------------------------------------------- invariant 1 + + +def test_appends_the_tiebreaker_to_the_default_sort(): + """Without a unique final key, ordering by an all-null column lets Postgres hand the + same row back on two different pages.""" + assert _plan({}).order == (SortKey(field="created_at", descending=True), SortKey(field="budget_id", descending=False)) + + +def test_appends_the_tiebreaker_to_an_explicit_multi_key_sort(): + order = _plan({"sort": "-max_budget,created_at"}).order + + assert len(order) == 3 + assert order[-1] == SortKey(field="budget_id", descending=False) + + +def test_appends_the_tiebreaker_even_when_the_caller_already_sorts_by_it(): + """Deduplicating it away is the tempting simplification, and it is the one that + reintroduces a non-total order the moment the leading key stops being unique.""" + assert _plan({"sort": "-budget_id"}).order == ( + SortKey(field="budget_id", descending=True), + SortKey(field="budget_id", descending=False), + ) + + +# ---------------------------------------------------------------- invariant 2 + + +def test_orders_nulls_last_in_both_directions(): + """Postgres sorts nulls last ascending but first descending, so flipping the sort + direction on max_budget would otherwise float every "Unlimited" row to the top.""" + sql = order_by_sql((SortKey(field="max_budget", descending=True), SortKey(field="budget_id", descending=False))) + + assert sql == '"max_budget" DESC NULLS LAST, "budget_id" ASC NULLS LAST' + + +def test_order_sql_covers_every_key_in_the_plan(): + sql = order_by_sql(_plan({"sort": "-max_budget,created_at"}).order) + + assert sql.count("NULLS LAST") == 3 + assert sql == '"max_budget" DESC NULLS LAST, "created_at" ASC NULLS LAST, "budget_id" ASC NULLS LAST' + + +# ------------------------------------------------------------ where rendering + + +def test_every_caller_value_is_bound_not_interpolated(): + """The one property that keeps a filter value from reaching the SQL text. A value that + looks like SQL has to come back as a parameter, never as part of the statement.""" + sql, params = where_sql((Compare(field="budget_id", op="eq", value="'; DROP TABLE x --"),)) + + assert sql == '"budget_id" = $1' + assert params == ("'; DROP TABLE x --",) + assert "DROP" not in sql + + +def test_placeholders_are_numbered_across_the_whole_plan(): + """A predicate that binds several values has to advance the counter by that many, or + every later predicate reads the wrong parameter.""" + sql, params = where_sql( + ( + Compare(field="created_by", op="eq", value="alice"), + Within(field="budget_id", values=("a", "b", "c")), + Compare(field="max_budget", op="gte", value=5.0), + ) + ) + + assert sql == '"created_by" = $1 AND "budget_id" IN ($2, $3, $4) AND "max_budget" >= $5' + assert params == ("alice", "a", "b", "c", 5.0) + + +def test_placeholder_numbering_can_start_past_earlier_parameters(): + sql, params = where_sql((Compare(field="created_by", op="eq", value="alice"),), first_index=4) + + assert sql == '"created_by" = $4' + assert params == ("alice",) + + +def test_is_null_binds_no_parameter_and_does_not_consume_a_placeholder(): + sql, params = where_sql( + (IsNull(field="max_budget", negated=False), Compare(field="created_by", op="eq", value="alice")) + ) + + assert sql == '"max_budget" IS NULL AND "created_by" = $1' + assert params == ("alice",) + + +def test_is_null_negated_renders_is_not_null(): + assert where_sql((IsNull(field="max_budget", negated=True),))[0] == '"max_budget" IS NOT NULL' + + +def test_a_search_renders_as_a_parenthesised_or(): + """Without the parentheses the OR would bind looser than the surrounding ANDs and the + scope predicate would stop constraining the search branch.""" + sql, params = where_sql( + ( + Compare(field="created_by", op="eq", value="alice"), + AnyOf( + clauses=( + Compare(field="budget_id", op="contains", value="prod"), + Compare(field="created_by", op="contains", value="prod"), + ) + ), + ) + ) + + assert sql == ( + '"created_by" = $1 AND (' + "\"budget_id\" ILIKE $2 ESCAPE '\\'" + " OR " + "\"created_by\" ILIKE $3 ESCAPE '\\'" + ")" + ) + assert params == ("alice", "%prod%", "%prod%") + + +def test_contains_escapes_like_metacharacters(): + """Budget ids routinely contain '_', which is a single-character wildcard unescaped.""" + _, params = where_sql((Compare(field="budget_id", op="contains", value="device_id%"),)) + + assert params == (r"%device\_id\%%",) + + +@pytest.mark.parametrize( + ("op", "operator"), + [("eq", "="), ("not", "<>"), ("gte", ">="), ("lte", "<="), ("gt", ">"), ("lt", "<")], +) +def test_each_comparison_operator_renders_its_sql_spelling(op, operator): + assert where_sql((Compare(field="max_budget", op=op, value=1),))[0] == f'"max_budget" {operator} $1' + + +def test_an_empty_plan_renders_no_where_body(): + assert where_sql(()) == ("", ()) + + +def test_a_planned_filter_renders_end_to_end(): + """Ties the parser to the renderer: what build_query_plan produces is what executes.""" + sql, params = where_sql(_plan({"filter[max_budget][is_null]": "true", "q": "prod"}).where) + + assert sql == ( + '"max_budget" IS NULL AND (' + "\"budget_id\" ILIKE $1 ESCAPE '\\'" + " OR " + "\"created_by\" ILIKE $2 ESCAPE '\\'" + ")" + ) + assert params == ("%prod%", "%prod%") + + +# ---------------------------------------------------------------- invariant 3 + + +def test_the_scope_predicate_is_the_first_conjunct(): + spec = _spec(scope=lambda caller: ScopeWhere(where=(Compare(field="created_by", op="eq", value=caller.user_id),))) + + conjuncts = _conjuncts(_plan({"filter[max_budget][gte]": "5"}, spec=spec)) + + assert conjuncts[0] == Compare(field="created_by", op="eq", value="caller-1") + + +def test_a_caller_filter_cannot_replace_the_scope_predicate(): + """The failure this guards is a `{**scope, **filters}` merge: a caller filtering on + the scoped column would silently overwrite the scope and read another user's rows.""" + spec = _spec(scope=lambda caller: ScopeWhere(where=(Compare(field="created_by", op="eq", value=caller.user_id),))) + + conjuncts = _conjuncts(_plan({"filter[created_by][eq]": "someone-else"}, spec=spec)) + + assert conjuncts[0] == Compare(field="created_by", op="eq", value="caller-1") + assert Compare(field="created_by", op="eq", value="someone-else") in conjuncts + assert len(conjuncts) == 2 + + +def test_the_scope_predicate_survives_a_search(): + spec = _spec(scope=lambda caller: ScopeWhere(where=(Compare(field="created_by", op="eq", value=caller.user_id),))) + + conjuncts = _conjuncts(_plan({"q": "prod"}, spec=spec)) + + assert conjuncts[0] == Compare(field="created_by", op="eq", value="caller-1") + assert any(isinstance(conjunct, AnyOf) for conjunct in conjuncts) + + +def test_an_unscoped_caller_gets_no_scope_conjunct(): + assert _plan({"filter[max_budget][gte]": "5"}).where == (Compare(field="max_budget", op="gte", value=5.0),) + + +def test_an_unfiltered_unscoped_list_has_an_empty_where(): + assert _plan({}).where == () + + +# ---------------------------------------------------------------- invariant 4 + + +def test_a_denied_scope_is_a_403_problem(): + spec = _spec(scope=lambda caller: ScopeDenied(reason="Only a proxy admin can list budgets.")) + + problem = _problem({}, spec=spec) + + assert problem.status == 403 + assert problem.type == f"{PROBLEM_TYPE_BASE}forbidden" + assert problem.detail == "Only a proxy admin can list budgets." + + +@pytest.mark.asyncio +async def test_a_denied_scope_never_reaches_the_database(): + """A 200 with an empty list would tell the caller the resource is empty rather than + that they cannot read it, and would still pay for the query.""" + spec = _spec(scope=lambda caller: ScopeDenied(reason="nope")) + executor = RecordingExecutor(rows=(BudgetRow(budget_id="b1", max_budget=None, created_by="x"),)) + + with pytest.raises(ManagementProblem) as raised: + await handle_list(spec=spec, executor=executor, request=_request(), caller=CALLER) + + assert raised.value.problem.status == 403 + assert executor.plan is None + assert executor.count_where is None + + +# ---------------------------------------------------------------- invariant 5 + + +def test_page_size_falls_back_to_the_spec_default(): + assert _plan({}).take == 25 + + +def test_page_size_is_clamped_to_the_spec_maximum(): + """Clamped rather than rejected: an over-large page is a UI bug, not a caller error, + but serving it would let one request read the whole table.""" + assert _plan({"page_size": "100000"}).take == 100 + + +def test_page_offsets_by_page_size(): + plan = _plan({"page": "3", "page_size": "10"}) + + assert (plan.skip, plan.take) == (20, 10) + + +@pytest.mark.parametrize("page", ["0", "-1"], ids=["zero", "negative"]) +def test_page_below_one_is_rejected(page): + problem = _problem({"page": page}) + + assert problem.status == 400 + assert problem.type == f"{PROBLEM_TYPE_BASE}invalid-query-parameter" + + +@pytest.mark.parametrize( + "params", + [{"page": "one"}, {"page_size": "many"}, {"page_size": "0"}], + ids=["page-not-an-int", "page-size-not-an-int", "page-size-zero"], +) +def test_unusable_paging_values_are_rejected(params): + assert _problem(params).status == 400 + + +# ---------------------------------------------------------------- invariant 6 + + +def test_an_unknown_query_parameter_is_rejected_with_the_allowed_set(): + problem = _problem({"page_sizee": "10"}) + + assert problem.status == 400 + assert problem.type == f"{PROBLEM_TYPE_BASE}unknown-query-parameter" + assert "page_sizee" in problem.detail + assert problem.allowed is not None + assert "page_size" in problem.allowed + assert "filter[max_budget][gte]" in problem.allowed + + +def test_the_allowed_set_enumerates_only_operators_the_field_declares(): + problem = _problem({"nope": "1"}) + + assert problem.allowed is not None + assert "filter[max_budget][is_null]" in problem.allowed + assert "filter[tpm_limit][gte]" not in problem.allowed + assert "filter[created_by][in]" in problem.allowed + + +def test_a_filter_on_an_undeclared_field_is_an_unknown_parameter(): + problem = _problem({"filter[secret_column][eq]": "x"}) + + assert problem.type == f"{PROBLEM_TYPE_BASE}unknown-query-parameter" + assert "filter[secret_column][eq]" in problem.detail + + +def test_every_declared_parameter_is_accepted(): + """Guards the unknown-param check against rejecting the spec's own contract.""" + plan = _plan( + { + "page": "2", + "page_size": "10", + "sort": "-max_budget", + "q": "prod", + "filter[max_budget][gte]": "5", + "filter[created_by][in]": "a,b", + } + ) + + assert plan.take == 10 + + +# ---------------------------------------------------------------- invariant 7 + + +def test_sorting_by_an_undeclared_field_is_rejected(): + problem = _problem({"sort": "api_key"}) + + assert problem.status == 400 + assert problem.type == f"{PROBLEM_TYPE_BASE}invalid-sort-field" + assert problem.allowed == ["budget_id", "created_at", "max_budget"] + assert "api_key" in problem.detail + + +def test_one_bad_key_rejects_the_whole_multi_key_sort(): + """Dropping the unknown key and sorting by the rest would silently return a + differently-ordered page than the one asked for.""" + assert _problem({"sort": "-created_at,api_key"}).type == f"{PROBLEM_TYPE_BASE}invalid-sort-field" + + +def test_a_double_dash_prefix_is_not_a_descending_sort(): + assert _problem({"sort": "--created_at"}).type == f"{PROBLEM_TYPE_BASE}invalid-sort-field" + + +# ---------------------------------------------------------------- invariant 8 + + +def test_an_operator_the_field_does_not_declare_is_rejected(): + problem = _problem({"filter[max_budget][contains]": "5"}) + + assert problem.status == 400 + assert problem.type == f"{PROBLEM_TYPE_BASE}unsupported-filter-operator" + assert problem.allowed == ["eq", "gte", "is_null", "lte"] + assert "contains" in problem.detail + + +def test_the_same_operator_is_accepted_on_a_field_that_declares_it(): + """Pins the rejection to the field's own operator set rather than a global denylist.""" + conjuncts = _conjuncts(_plan({"filter[created_by][contains]": "ops"})) + + assert conjuncts == (Compare(field="created_by", op="contains", value="ops"),) + + +def test_a_string_that_is_not_an_operator_at_all_is_an_unknown_parameter(): + """`gt3` is a typo, not an operator the field withheld, so the useful reply is the + parameter list rather than this field's operator set.""" + problem = _problem({"filter[max_budget][gt3]": "5"}) + + assert problem.type == f"{PROBLEM_TYPE_BASE}unknown-query-parameter" + assert problem.allowed is not None + assert "filter[max_budget][gte]" in problem.allowed + + +# ---------------------------------------------------------------- invariant 9 + + +def test_search_against_a_spec_with_nothing_searchable_is_rejected(): + """A silently-empty search filter returns the unfiltered table, which reads as + "no results were filtered out" rather than "this resource cannot be searched".""" + problem = _problem({"q": "prod"}, spec=_spec(searchable=frozenset())) + + assert problem.status == 400 + assert problem.type == f"{PROBLEM_TYPE_BASE}unknown-query-parameter" + assert problem.allowed is not None + assert "q" not in problem.allowed + + +def test_search_is_a_case_insensitive_or_across_every_searchable_field(): + conjuncts = _conjuncts(_plan({"q": "Prod"})) + + assert conjuncts == ( + AnyOf( + clauses=( + Compare(field="budget_id", op="contains", value="Prod"), + Compare(field="created_by", op="contains", value="Prod"), + ) + ), + ) + + +def test_an_empty_search_string_adds_no_filter(): + assert _plan({"q": ""}).where == () + + +# --------------------------------------------------------------- invariant 10 + + +def test_multi_key_sort_parses_the_json_api_grammar(): + order = _plan({"sort": "-created_at,budget_id,-max_budget"}).order + + assert order[:3] == ( + SortKey(field="created_at", descending=True), + SortKey(field="budget_id", descending=False), + SortKey(field="max_budget", descending=True), + ) + + +def test_sort_segments_tolerate_surrounding_whitespace(): + assert _plan({"sort": "-created_at, budget_id"}).order[:2] == ( + SortKey(field="created_at", descending=True), + SortKey(field="budget_id", descending=False), + ) + + +# ------------------------------------------------------------- filter parsing + + +def test_comparison_operators_become_prisma_range_fragments(): + conjuncts = _conjuncts(_plan({"filter[max_budget][gte]": "5", "filter[max_budget][lte]": "50"})) + + assert conjuncts == ( + Compare(field="max_budget", op="gte", value=5.0), + Compare(field="max_budget", op="lte", value=50.0), + ) + + +def test_eq_is_a_bare_value_not_a_wrapped_one(): + assert _conjuncts(_plan({"filter[tpm_limit][eq]": "100"})) == (Compare(field="tpm_limit", op="eq", value=100),) + + +def test_a_filter_with_no_operator_bracket_means_eq(): + """`filter[status]=active` is the design doc's canonical spelling for equality; + only the non-eq operators carry a second bracket.""" + assert _conjuncts(_plan({"filter[tpm_limit]": "100"})) == (Compare(field="tpm_limit", op="eq", value=100),) + + +def test_the_bare_form_and_the_explicit_eq_form_agree(): + assert _plan({"filter[created_by]": "alice"}) == _plan({"filter[created_by][eq]": "alice"}) + + +def test_the_bare_form_still_coerces_to_the_declared_type(): + assert _problem({"filter[tpm_limit]": "1.5"}).status == 400 + + +def test_the_bare_form_is_rejected_on_a_field_that_does_not_declare_eq(): + """The shorthand is sugar for the eq operator, not a bypass around the operator set.""" + problem = _problem({"filter[created_at]": "2026-07-23T00:00:00Z"}) + + assert problem.type == f"{PROBLEM_TYPE_BASE}unsupported-filter-operator" + assert problem.allowed == ["gte", "lte"] + + +def test_the_allowed_set_advertises_the_bare_spelling_for_eq(): + allowed = _problem({"nope": "1"}).allowed + + assert allowed is not None + assert "filter[max_budget]" in allowed + assert "filter[max_budget][eq]" not in allowed + assert "filter[created_at][gte]" in allowed + assert "filter[created_at]" not in allowed + + +@pytest.mark.parametrize( + "name", + ["filter[]", "filter[a][b][c]", "filter[a][", "filter", "filter[a][gte", "filter[max_budget]]["], + ids=["empty", "triple", "unbalanced", "bare-word", "unterminated", "bracketed-field"], +) +def test_malformed_filter_keys_are_unknown_parameters_not_eq_filters(name): + """A malformed key must not fall through to the bare-eq branch and silently filter + on a field nobody declared. `field in spec.filters` is the gate that makes this hold, + which is also why the parser needs no separate well-formedness guard.""" + assert _problem({name: "x"}).type == f"{PROBLEM_TYPE_BASE}unknown-query-parameter" + + +def test_in_splits_on_commas_and_coerces_every_member(): + assert _conjuncts(_plan({"filter[created_by][in]": "alice, bob"})) == ( + Within(field="created_by", values=("alice", "bob")), + ) + + +def test_is_null_true_matches_rows_with_no_budget(): + """Budgets renders a null max_budget as "Unlimited"; without is_null there is no way + to ask for those rows.""" + assert _conjuncts(_plan({"filter[max_budget][is_null]": "true"})) == ( + IsNull(field="max_budget", negated=False), + ) + + +def test_is_null_false_matches_rows_that_have_one(): + assert _conjuncts(_plan({"filter[max_budget][is_null]": "false"})) == ( + IsNull(field="max_budget", negated=True), + ) + + +def test_is_null_rejects_a_non_boolean(): + assert _problem({"filter[max_budget][is_null]": "maybe"}).status == 400 + + +@pytest.mark.parametrize( + "params", + [ + {"filter[max_budget][gte]": "lots"}, + {"filter[tpm_limit][eq]": "1.5"}, + {"filter[created_at][gte]": "yesterday"}, + {"filter[created_by][in]": "alice,"}, + ], + ids=["float", "int", "datetime", "in-member"], +) +def test_a_value_that_does_not_match_the_declared_type_is_rejected(params): + numeric_in = _spec_with(filters={**_spec().filters, "created_by": FilterSpec(type=int, ops=frozenset({"in"}))}) + target = numeric_in if "filter[created_by][in]" in params else _spec() + + assert _problem(params, spec=target).status == 400 + + +def test_a_datetime_filter_is_normalised_to_utc(): + """The dashboard sends both offset-bearing and naive timestamps; reading a naive one + as server-local time would shift the window off what the table is showing.""" + with_offset = _conjuncts(_plan({"filter[created_at][gte]": "2026-07-23T02:00:00+02:00"})) + naive = _conjuncts(_plan({"filter[created_at][gte]": "2026-07-23 00:00:00"})) + + assert with_offset == (Compare(field="created_at", op="gte", value=datetime(2026, 7, 23, tzinfo=timezone.utc)),) + assert naive == with_offset + + +def test_filters_are_ordered_deterministically(): + """Two requests differing only in query-string order must plan identically, or the + plan stops being a comparable value.""" + forwards = _plan({"filter[created_by][eq]": "a", "filter[max_budget][gte]": "5"}) + backwards = _plan({"filter[max_budget][gte]": "5", "filter[created_by][eq]": "a"}) + + assert forwards == backwards + + +# ------------------------------------------------------------------- envelope + + +@pytest.mark.asyncio +async def test_returns_the_page_mode_envelope(): + executor = RecordingExecutor( + rows=tuple(BudgetRow(budget_id=f"b{i}", max_budget=float(i), created_by="u") for i in range(10)), + total_count=42, + ) + + response = await handle_list( + spec=_spec(), executor=executor, request=_request("page=2&page_size=5"), caller=CALLER + ) + body = response.model_dump(by_alias=True) + + assert body["meta"] == {"total_count": 42, "page": 2, "page_size": 5, "total_pages": 9} + assert set(body) == {"data", "meta", "links"} + assert "has_more" not in body["meta"] + + +@pytest.mark.asyncio +async def test_serializes_rows_flat_without_a_json_api_resource_wrapper(): + executor = RecordingExecutor(rows=(BudgetRow(budget_id="b1", max_budget=None, created_by="u"),)) + + response = await handle_list(spec=_spec(), executor=executor, request=_request(), caller=CALLER) + body = response.model_dump(by_alias=True) + + assert body["data"] == [{"budget_id": "b1", "max_budget": None}] + assert "attributes" not in body["data"][0] + assert "created_by" not in body["data"][0] + + +@pytest.mark.asyncio +async def test_links_let_a_client_page_without_building_urls(): + executor = RecordingExecutor(rows=(), total_count=42) + + response = await handle_list( + spec=_spec(), executor=executor, request=_request("page=2&page_size=5"), caller=CALLER + ) + links = response.model_dump(by_alias=True)["links"] + + assert links["self"] == f"{BUDGETS_PATH}?page_size=5&page=2" + assert links["first"] == f"{BUDGETS_PATH}?page_size=5&page=1" + assert links["prev"] == f"{BUDGETS_PATH}?page_size=5&page=1" + assert links["next"] == f"{BUDGETS_PATH}?page_size=5&page=3" + assert links["last"] == f"{BUDGETS_PATH}?page_size=5&page=9" + + +@pytest.mark.asyncio +async def test_the_last_page_has_no_next_link(): + executor = RecordingExecutor(rows=(), total_count=10) + + response = await handle_list( + spec=_spec(), executor=executor, request=_request("page=2&page_size=5"), caller=CALLER + ) + links = response.model_dump(by_alias=True)["links"] + + assert links["next"] is None + assert links["prev"] == f"{BUDGETS_PATH}?page_size=5&page=1" + + +@pytest.mark.asyncio +async def test_an_empty_result_set_still_resolves_every_link(): + executor = RecordingExecutor(rows=(), total_count=0) + + response = await handle_list(spec=_spec(), executor=executor, request=_request(), caller=CALLER) + body = response.model_dump(by_alias=True) + + assert body["data"] == [] + assert body["meta"]["total_pages"] == 0 + assert body["links"]["first"] == body["links"]["last"] == f"{BUDGETS_PATH}?page=1" + assert body["links"]["next"] is None + assert body["links"]["prev"] is None + + +@pytest.mark.asyncio +async def test_the_executor_counts_the_same_predicate_it_reads(): + """Counting a wider predicate than the read inflates total_pages and hands the UI + pages that are always empty.""" + executor = RecordingExecutor(rows=(), total_count=3) + spec = _spec(scope=lambda caller: ScopeWhere(where=(Compare(field="created_by", op="eq", value=caller.user_id),))) + + await handle_list(spec=spec, executor=executor, request=_request("filter[max_budget][gte]=5"), caller=CALLER) + + assert executor.plan is not None + assert executor.count_where == executor.plan.where + + +@pytest.mark.asyncio +async def test_a_rejected_request_is_raised_as_a_problem_before_any_query(): + executor = RecordingExecutor(rows=()) + + with pytest.raises(ManagementProblem) as raised: + await handle_list(spec=_spec(), executor=executor, request=_request("sort=api_key"), caller=CALLER) + + assert raised.value.problem.status == 400 + assert executor.count_where is None + + +# ------------------------------------------------------- spec construction + + +def test_a_default_page_size_above_the_cap_is_rejected_at_construction(): + """The cap is only enforced on a supplied page_size, so a default above it would serve + more rows than the resource allows on exactly the request that omits page_size.""" + with pytest.raises(ValueError, match="default_page_size"): + _spec_with(default_page_size=200, max_page_size=100) + + +@pytest.mark.parametrize( + "overrides", + [{"default_page_size": 0}, {"default_page_size": -5}, {"max_page_size": 0}], + ids=["zero-default", "negative-default", "zero-cap"], +) +def test_a_non_positive_page_size_is_rejected_at_construction(overrides): + """take=0 divides by zero when handle_list computes total_pages, so the resource would + 500 on every request instead of failing when it is registered.""" + with pytest.raises(ValueError, match="default_page_size"): + _spec_with(**overrides) + + +def test_a_default_sort_on_a_non_sortable_field_is_rejected_at_construction(): + """Caller-supplied sort is validated against `sortable`; default_sort is not read from + the request, so without this it reaches order_by_sql and yields invalid SQL.""" + with pytest.raises(ValueError, match="default_sort"): + _spec_with(default_sort=(SortKey(field="not_a_column", descending=True),)) + + +def test_an_empty_tiebreaker_is_rejected_at_construction(): + with pytest.raises(ValueError, match="tiebreaker"): + _spec_with(tiebreaker="") + + +def test_a_page_size_equal_to_the_cap_is_a_valid_spec(): + """Guards the bound against being tightened into an off-by-one that bans max==default.""" + assert _spec_with(default_page_size=100, max_page_size=100).default_page_size == 100 + + +# ------------------------------------------------------ repeated parameters + + +@pytest.mark.asyncio +async def test_a_repeated_query_parameter_is_rejected(): + """Starlette keeps the last value, so ?page=1&page=999 would page from 999 with nothing + telling the caller which one won. The doc rejects silently-altered params for this reason.""" + executor = RecordingExecutor(rows=()) + + with pytest.raises(ManagementProblem) as raised: + await handle_list(spec=_spec(), executor=executor, request=_request("page=1&page=999"), caller=CALLER) + + assert raised.value.problem.status == 400 + assert raised.value.problem.type == f"{PROBLEM_TYPE_BASE}duplicate-query-parameter" + assert "page" in raised.value.problem.detail + assert executor.count_where is None + + +@pytest.mark.asyncio +async def test_a_repeated_filter_parameter_is_rejected(): + executor = RecordingExecutor(rows=()) + + with pytest.raises(ManagementProblem) as raised: + await handle_list( + spec=_spec(), + executor=executor, + request=_request("filter[created_by][eq]=alice&filter[created_by][eq]=bob"), + caller=CALLER, + ) + + assert raised.value.problem.type == f"{PROBLEM_TYPE_BASE}duplicate-query-parameter" + assert "filter[created_by][eq]" in raised.value.problem.detail + + +@pytest.mark.asyncio +async def test_distinct_parameters_are_not_treated_as_duplicates(): + """Guards the check against rejecting two different operators on one field, which is + how a range filter is expressed.""" + executor = RecordingExecutor(rows=(), total_count=0) + + response = await handle_list( + spec=_spec(), + executor=executor, + request=_request("filter[max_budget][gte]=5&filter[max_budget][lte]=50&page=2"), + caller=CALLER, + ) + + assert response.meta.page == 2 + assert executor.count_where is not None + + +@pytest.mark.asyncio +async def test_a_denied_scope_outranks_a_duplicate_parameter(): + """Permission is the stronger statement about the caller, so it is answered first.""" + spec = _spec(scope=lambda caller: ScopeDenied(reason="nope")) + executor = RecordingExecutor(rows=()) + + with pytest.raises(ManagementProblem) as raised: + await handle_list(spec=spec, executor=executor, request=_request("page=1&page=2"), caller=CALLER) + + assert raised.value.problem.status == 403 + + +# --------------------------------------------------- facet-mode regression + + +def test_the_facet_page_shapes_are_untouched_by_page_mode(): + """The live facet endpoint reports `has_more` and has no first/last, because it + deliberately skips the COUNT(*). Folding it into the page-mode shapes would either + break its response or make every keystroke pay for a full-table count.""" + assert set(PageMeta.model_fields) == {"page", "page_size", "has_more"} + assert set(PageLinks.model_fields) == {"self_link", "prev", "next"} + + links = build_page_links(request=_request("q=ac&page=2"), page=2, has_more=True).model_dump(by_alias=True) + + assert set(links) == {"self", "prev", "next"} + assert links["next"] == "/management/v1/budgets?q=ac&page=3" From 16507f11742144aa6c66c8239a05279b992cd597 Mon Sep 17 00:00:00 2001 From: Yassin Kortam Date: Fri, 31 Jul 2026 09:48:08 -0700 Subject: [PATCH 21/92] fix(aiohttp): dispose recycled client sessions deterministically (#33428) * fix(aiohttp): dispose recycled client sessions deterministically LiteLLMAiohttpTransport replaced its cached aiohttp.ClientSession on loop-mismatch, loop-inspection failure, and "Session is closed" retry without reliably closing the previous session: - the close task from asyncio.create_task() was never referenced, so it could be garbage-collected before running; - the (RuntimeError, AttributeError) fallback branch replaced the session without closing it at all; - sessions bound to a closed event loop were abandoned to the GC ("rely on GC"), and sessions bound to a loop running in another thread were closed from the wrong loop. Replaced sessions surfaced as intermittent "Unclosed client session" / "Unclosed connector" errors from the event-loop exception handler at GC time. _close_recycled_session() now covers the three lifecycles a recycled session can be in: same-loop closes keep a strong task reference until completion; sessions owned by a loop running elsewhere are closed on their own loop via run_coroutine_threadsafe; sessions whose loop is gone are disposed synchronously through the connector teardown that aiohttp's own finalizer uses, which releases pooled connections and silences the finalizer warnings. Fixes #24230 * fix(aiohttp): guard threadsafe close callback against cancelled futures --------- Co-authored-by: Anmol Jaiswal <68013660+anmolg1997@users.noreply.github.com> --- .../llms/custom_httpx/aiohttp_transport.py | 123 ++++++- .../custom_httpx/test_aiohttp_transport.py | 303 ++++++++++++++++++ 2 files changed, 416 insertions(+), 10 deletions(-) diff --git a/litellm/llms/custom_httpx/aiohttp_transport.py b/litellm/llms/custom_httpx/aiohttp_transport.py index df5b10b3bdc..2c5f455692c 100644 --- a/litellm/llms/custom_httpx/aiohttp_transport.py +++ b/litellm/llms/custom_httpx/aiohttp_transport.py @@ -1,10 +1,11 @@ import asyncio +import concurrent.futures import contextlib import os import ssl import typing import urllib.request -from typing import Any, Callable, Dict, Optional, Union +from typing import Any, Callable, ClassVar, Dict, Optional, Union import aiohttp import aiohttp.client_exceptions @@ -138,6 +139,11 @@ class LiteLLMAiohttpTransport(AiohttpTransport): Credit to: https://github.com/karpetrosyan/httpx-aiohttp for this implementation """ + # Strong references to scheduled session-close tasks. A bare + # asyncio.create_task() result may be garbage-collected before it runs, + # leaving the recycled session unclosed ("Unclosed client session"). + _background_close_tasks: ClassVar[set["asyncio.Task[None]"]] = set() # mutable-ok: strong refs for pending closes + def __init__( self, client: Union[ClientSession, Callable[[], ClientSession]], @@ -164,6 +170,92 @@ class LiteLLMAiohttpTransport(AiohttpTransport): self._owns_session = True return session + @classmethod + def _on_close_task_done(cls, task: "asyncio.Task[None]") -> None: + cls._background_close_tasks.discard(task) + if task.cancelled(): + return + exc = task.exception() + if exc is not None: + verbose_logger.debug("Error closing recycled aiohttp session: %s", exc) + + @staticmethod + def _on_threadsafe_close_done(future: "concurrent.futures.Future[None]") -> None: + if future.cancelled(): + return + exc = future.exception() + if exc is not None: + verbose_logger.debug("Error closing recycled aiohttp session on its own loop: %s", exc) + + @staticmethod + def _mark_connector_closed(session: ClientSession) -> None: + """Synchronously dispose a session whose event loop is gone. + + An async close can no longer run on a closed loop. BaseConnector._close + is the same synchronous teardown aiohttp's own finalizer (__del__) + uses: it is guarded for closed loops, releases pooled connections, and + flips the flags that ClientSession.closed / BaseConnector.closed read - + so no "Unclosed client session" / "Unclosed connector" warnings reach + the event-loop exception handler at garbage collection. + """ + connector = getattr(session, "_connector", None) + close_sync = getattr(connector, "_close", None) + if not callable(close_sync): + return + try: + close_sync() + except (RuntimeError, AttributeError, OSError) as e: + verbose_logger.debug("Best-effort connector close failed: %s", e) + + def _close_recycled_session(self, session: ClientSession) -> None: + """Deterministically dispose a ClientSession this transport is replacing. + + Covers the three lifecycles a recycled session can be in: + - its loop is the current running loop: schedule an async close and keep + a strong reference to the task until it completes; + - its loop is still running elsewhere (e.g. another thread): hand the + close to that loop thread-safely; + - its loop is stopped or closed, or there is no running loop: fall + back to the synchronous finalizer-safe teardown. + """ + if session.closed: + return + + session_loop = getattr(session, "_loop", None) + try: + current_loop: Optional[asyncio.AbstractEventLoop] = asyncio.get_running_loop() + except RuntimeError: + current_loop = None + + if session_loop is not None and session_loop is not current_loop: + if not session_loop.is_closed() and session_loop.is_running(): + # The session's loop is running somewhere else (e.g. another + # thread): closing from here would touch that loop's internals + # unsafely; hand the close to its own loop. + try: + future = asyncio.run_coroutine_threadsafe(session.close(), session_loop) + except RuntimeError as e: # loop shut down between the checks + verbose_logger.debug("Threadsafe session close failed: %s", e) + self._mark_connector_closed(session) + else: + future.add_done_callback(self._on_threadsafe_close_done) + return + + # Foreign loop that is stopped or closed: an async close can no + # longer run there, and running it on the current loop would touch + # another loop's internals. Dispose synchronously instead. + self._mark_connector_closed(session) + return + + if current_loop is None: + self._mark_connector_closed(session) + return + + task = current_loop.create_task(session.close()) + cls = type(self) + cls._background_close_tasks.add(task) + task.add_done_callback(cls._on_close_task_done) + def _get_valid_client_session(self) -> ClientSession: """ Helper to get a valid ClientSession for the current event loop. @@ -193,21 +285,25 @@ class LiteLLMAiohttpTransport(AiohttpTransport): # Close old session to prevent leaks old_session = self.client try: - if self._owns_session and not old_session.closed: - try: - asyncio.create_task(old_session.close()) - except RuntimeError: - # Different event loop - can't schedule task, rely on GC - verbose_logger.debug("Old session from different loop, relying on GC") + if self._owns_session: + self._close_recycled_session(old_session) except Exception as e: verbose_logger.debug(f"Error closing old session: {e}") # Create a new session in the current event loop self.client = self._rebuild_session() - except (RuntimeError, AttributeError): - # If we can't check the loop or session is invalid, recreate it + except (RuntimeError, AttributeError) as e: + # If we can't check the loop or session is invalid, recreate it, + # but still dispose of the session being replaced. + old_session = self.client + if self._owns_session: + try: + self._close_recycled_session(old_session) + except (RuntimeError, AttributeError, OSError) as close_error: + verbose_logger.debug(f"Error closing old session: {close_error}") self.client = self._rebuild_session() + verbose_logger.debug(f"Error checking session loop, created new session: {e}") return self.client @@ -301,7 +397,14 @@ class LiteLLMAiohttpTransport(AiohttpTransport): # Handle the case where session was closed between our check and actual use if "Session is closed" in str(e): verbose_logger.debug(f"Session closed during request, retrying with new session: {e}") - # Force creation of a new session + # Dispose of the session that actually faulted. Do NOT read + # self.client here: a concurrent task may already have + # replaced it with a healthy session that must stay open. + # Guarded by isinstance: factory-injected sessions may be + # duck-typed test doubles without a close() coroutine. + # Read _owns_session before _rebuild_session() claims ownership. + if self._owns_session and isinstance(client_session, ClientSession): + self._close_recycled_session(client_session) self.client = self._rebuild_session() client_session = self.client diff --git a/tests/test_litellm/llms/custom_httpx/test_aiohttp_transport.py b/tests/test_litellm/llms/custom_httpx/test_aiohttp_transport.py index 0550898c73d..b0b092a541f 100644 --- a/tests/test_litellm/llms/custom_httpx/test_aiohttp_transport.py +++ b/tests/test_litellm/llms/custom_httpx/test_aiohttp_transport.py @@ -1,4 +1,5 @@ import asyncio +import concurrent.futures import os import sys @@ -827,3 +828,305 @@ async def test_stale_loop_rebuild_does_not_close_unowned_session(): shared_session._loop = running_loop other_loop.close() await shared_session.close() + + +# --------------------------------------------------------------------------- +# Recycled-session leak tests (#24230) +# --------------------------------------------------------------------------- + + +async def _new_session() -> aiohttp.ClientSession: + return aiohttp.ClientSession() + + +def _make_session_on_dead_loop() -> aiohttp.ClientSession: + """Create a ClientSession bound to an event loop that is then closed. + + Runs in a worker thread: the caller may already be inside a running + event loop, where a nested run_until_complete is forbidden. + """ + import threading + + result: dict = {} + + def build() -> None: + loop = asyncio.new_event_loop() + try: + result["session"] = loop.run_until_complete(_new_session()) + finally: + loop.close() + + thread = threading.Thread(target=build) + thread.start() + thread.join(5) + return result["session"] + + +def _flaky_get_running_loop_factory(): + """get_running_loop stand-in that fails once, then delegates. + + Reproduces #24230: a transient loop-inspection failure sends + _get_valid_client_session into its (RuntimeError, AttributeError) + fallback branch. + """ + real_get_running_loop = asyncio.get_running_loop + calls = {"count": 0} + + def flaky(): + calls["count"] += 1 + if calls["count"] == 1: + raise RuntimeError("simulated loop inspection failure") + return real_get_running_loop() + + return flaky + + +@pytest.mark.asyncio +async def test_fallback_recreate_closes_previous_session(): + """ + Regression test for #24230: when loop inspection fails and the fallback + branch recreates the session, the replaced session must still be closed - + not silently abandoned to the garbage collector. + """ + from unittest.mock import patch + + old_session = aiohttp.ClientSession() + transport = LiteLLMAiohttpTransport(client=lambda: aiohttp.ClientSession()) + transport.client = old_session + + with patch( + "litellm.llms.custom_httpx.aiohttp_transport.asyncio.get_running_loop", + side_effect=_flaky_get_running_loop_factory(), + ): + new_session = transport._get_valid_client_session() + + try: + assert new_session is not old_session + for _ in range(3): + await asyncio.sleep(0) + assert old_session.closed, "replaced session must be closed, not leaked" + finally: + await new_session.close() + if not old_session.closed: + await old_session.close() + + +@pytest.mark.asyncio +async def test_replaced_session_emits_no_unclosed_warnings(): + """ + Regression test for #24230: a session replaced by the fallback branch must + not surface "Unclosed client session" / "Unclosed connector" warnings when + the garbage collector finalizes it. + """ + import gc + import warnings as warnings_mod + from unittest.mock import patch + + old_session = aiohttp.ClientSession() + transport = LiteLLMAiohttpTransport(client=lambda: aiohttp.ClientSession()) + transport.client = old_session + + with patch( + "litellm.llms.custom_httpx.aiohttp_transport.asyncio.get_running_loop", + side_effect=_flaky_get_running_loop_factory(), + ): + new_session = transport._get_valid_client_session() + + try: + for _ in range(3): + await asyncio.sleep(0) + + del old_session + with warnings_mod.catch_warnings(record=True) as caught: + warnings_mod.simplefilter("always") + gc.collect() + + unclosed = [ + str(w.message) + for w in caught + if "Unclosed client session" in str(w.message) or "Unclosed connector" in str(w.message) + ] + assert not unclosed, f"leaked session warnings: {unclosed}" + finally: + await new_session.close() + + +@pytest.mark.asyncio +async def test_dead_loop_session_closed_synchronously_on_recycle(): + """ + Regression test for #24230: a session whose event loop is already closed + cannot run an async close anywhere. Recycling it must dispose of it + deterministically, the session reads closed as soon as the recycle + returns, so no finalizer warning window remains. + """ + old_session = _make_session_on_dead_loop() + transport = LiteLLMAiohttpTransport(client=lambda: aiohttp.ClientSession()) + transport.client = old_session + + new_session = transport._get_valid_client_session() + + try: + assert new_session is not old_session + assert old_session.closed, "session from a closed loop must be disposed synchronously at recycle" + finally: + await new_session.close() + + +@pytest.mark.asyncio +async def test_close_task_strongly_referenced_until_done(): + """ + Regression test for #24230: scheduled session-close tasks must be strongly + referenced (and pruned on completion) so they cannot be garbage-collected + before they run. + """ + old_session = aiohttp.ClientSession() + transport = LiteLLMAiohttpTransport(client=lambda: aiohttp.ClientSession()) + + transport._close_recycled_session(old_session) + + assert LiteLLMAiohttpTransport._background_close_tasks, "close task must be strongly referenced while pending" + for _ in range(5): + await asyncio.sleep(0) + assert old_session.closed + assert not LiteLLMAiohttpTransport._background_close_tasks, "completed close tasks must be pruned from the registry" + + +@pytest.mark.asyncio +async def test_session_from_other_running_loop_closed_threadsafe(): + """ + Regression test for #24230: a session that belongs to a loop still running + in another thread must be closed on its own loop (thread-safe), not driven + from the current loop. + """ + import threading + import time + + ready = threading.Event() + holder: dict = {} + + def worker() -> None: + loop = asyncio.new_event_loop() + holder["loop"] = loop + + async def make() -> None: + holder["session"] = aiohttp.ClientSession() + + loop.run_until_complete(make()) + ready.set() + loop.run_forever() + loop.close() + + thread = threading.Thread(target=worker, daemon=True) + thread.start() + assert ready.wait(5), "worker loop failed to start" + + transport = LiteLLMAiohttpTransport(client=lambda: aiohttp.ClientSession()) + transport.client = holder["session"] + + new_session = transport._get_valid_client_session() + + try: + deadline = time.monotonic() + 5 + while not holder["session"].closed and time.monotonic() < deadline: + await asyncio.sleep(0.01) + assert holder["session"].closed, "foreign-loop session was never closed" + finally: + holder["loop"].call_soon_threadsafe(holder["loop"].stop) + thread.join(5) + await new_session.close() + + +def test_threadsafe_close_done_callback_tolerates_cancelled_future(): + """ + Regression test for #24230 (review finding): when the foreign loop stops + before the handed-off close coroutine runs, asyncio cancels the + concurrent.futures.Future. The done-callback must return quietly instead + of letting future.exception() raise CancelledError (a BaseException that + escapes _invoke_callbacks and crashes the foreign loop's thread). + """ + future: "concurrent.futures.Future[None]" = concurrent.futures.Future() + future.cancel() + + LiteLLMAiohttpTransport._on_threadsafe_close_done(future) + + +@pytest.mark.asyncio +async def test_session_closed_retry_does_not_close_concurrent_replacement(): + """ + Regression test for #24230 (review finding): when the "Session is closed" + retry fires, the handler must dispose the session that actually faulted, + not self.client - a concurrent task may already have replaced self.client + with a healthy session, which must stay open. + """ + from unittest.mock import patch + + faulted_session = aiohttp.ClientSession() + healthy_replacement = aiohttp.ClientSession() + transport = LiteLLMAiohttpTransport(client=lambda: aiohttp.ClientSession()) + transport.client = faulted_session + + calls = {"n": 0} + + async def fake_make_request(*args, **kwargs): + calls["n"] += 1 + if calls["n"] == 1: + # simulate a concurrent task replacing the shared session between + # the failed await and the exception handler + transport.client = healthy_replacement + raise RuntimeError("Session is closed") + raise StopAsyncIteration("stop after retry dispatch") + + with patch.object(transport, "_make_aiohttp_request", side_effect=fake_make_request): + with pytest.raises(Exception): + await transport.handle_async_request(httpx.Request("GET", "http://example.com")) + + try: + assert not healthy_replacement.closed, "concurrent replacement session must not be closed by the retry handler" + for _ in range(3): + await asyncio.sleep(0) + assert faulted_session.closed, "the faulted session must be disposed" + finally: + await faulted_session.close() + await healthy_replacement.close() + new_session = transport.client + if isinstance(new_session, aiohttp.ClientSession): + await new_session.close() + + +@pytest.mark.asyncio +async def test_stopped_loop_session_disposed_synchronously_on_recycle(): + """ + Regression test for #24230 (review finding): a session whose loop is + stopped but not yet closed cannot safely run an async close on another + loop, and nothing will ever process a close handed to the stopped loop. + Recycling must dispose it synchronously, like the closed-loop case. + """ + import threading + + result: dict = {} + + def build() -> None: + loop = asyncio.new_event_loop() + + async def make() -> None: + result["session"] = aiohttp.ClientSession() + + loop.run_until_complete(make()) + result["loop"] = loop # stopped, deliberately NOT closed + + thread = threading.Thread(target=build) + thread.start() + thread.join(5) + + old_session = result["session"] + transport = LiteLLMAiohttpTransport(client=lambda: aiohttp.ClientSession()) + transport.client = old_session + + new_session = transport._get_valid_client_session() + + try: + assert new_session is not old_session + assert old_session.closed, "session from a stopped (not yet closed) loop must be disposed synchronously" + finally: + await new_session.close() + result["loop"].close() From d9f53258e9f3a3281706b4692a42c660f01e4e21 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Fri, 31 Jul 2026 09:52:46 -0700 Subject: [PATCH 22/92] refactor(test): tighten typing on the tag list verification token double Replaces the double's Any annotations and List/Dict aliases with concrete types, matching the equivalent double in the tool policy tests: kwargs are object, records are Sequence[Mock] held as a tuple, and the call log is list[dict[str, object]]. Behaviour is unchanged; the double still binds every call against the real generated prisma action signature, verified by reintroducing the select kwarg and watching the regression tests fail --- .../test_tag_management_endpoints.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/test_litellm/proxy/management_endpoints/test_tag_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_tag_management_endpoints.py index 9927c56b847..4fe1b54694f 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_tag_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_tag_management_endpoints.py @@ -2,7 +2,8 @@ import inspect import json import os import sys -from typing import Any, Dict, List, Optional +from collections.abc import Sequence +from typing import Optional import pytest from fastapi import HTTPException @@ -13,7 +14,7 @@ sys.path.insert( 0, os.path.abspath("../../../..") ) # Adds the parent directory to the system path -from unittest.mock import patch +from unittest.mock import Mock, patch import litellm from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth @@ -33,11 +34,11 @@ class FakeVerificationTokenTable: surfaces as an HTTP 500. """ - def __init__(self, records: List[Any]): - self._records = records - self.calls: List[Dict[str, Any]] = [] + def __init__(self, records: Sequence[Mock]): + self._records = tuple(records) + self.calls: list[dict[str, object]] = [] - async def find_many(self, **kwargs: Any) -> List[Any]: + async def find_many(self, **kwargs: object) -> tuple[Mock, ...]: inspect.signature(LiteLLM_VerificationTokenActions.find_many).bind( self, **kwargs ) From 88ab22fefc6dda9f2827f0c1d9112b6ecf56d813 Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Fri, 31 Jul 2026 10:19:20 -0700 Subject: [PATCH 23/92] test(e2e): skip the three Datadog MCP tool-call tests pending LIT-5052 (#35380) All three send a `telemetry` object in the arguments to Datadog's search_datadog_logs tool. Datadog tightened that tool's input schema to reject unknown properties, so every call now fails validation with 'unexpected additional properties ["telemetry"]' before the behavior each test exists to prove is reached. `telemetry` was never a documented Datadog parameter; the tests relied on the server ignoring extra properties. The proxy transmitted exactly what the tests supplied and surfaced the upstream error faithfully, so this is test-side. The covers markers and registry rows stay put: the collector counts a cell as covered only when a test pytest would actually run declares it, so skipping hands all four cells back to the gap list where they belong. --- tests/e2e/mcp/test_mcp_datadog_e2e.py | 10 ++++++++++ tests/e2e/mcp/test_mcp_guardrail_e2e.py | 10 ++++++++++ tests/e2e/mcp/test_mcp_key_access_e2e.py | 10 ++++++++++ 3 files changed, 30 insertions(+) diff --git a/tests/e2e/mcp/test_mcp_datadog_e2e.py b/tests/e2e/mcp/test_mcp_datadog_e2e.py index d093e307f99..138f654272d 100644 --- a/tests/e2e/mcp/test_mcp_datadog_e2e.py +++ b/tests/e2e/mcp/test_mcp_datadog_e2e.py @@ -49,6 +49,16 @@ def _seed_completion(proxy: ProxyClient, *, key: str, marker: str) -> None: class TestDatadogMcpRoundTrip: + @pytest.mark.skip( + reason=( + "LIT-5052: this test sends a `telemetry` argument that Datadog's " + "search_datadog_logs tool now rejects, so every tool call fails validation with " + "'unexpected additional properties [\"telemetry\"]' before the round-trip " + "assertion is reached. `telemetry` was never a documented Datadog parameter; the " + "test relied on the server ignoring unknown properties. Unskip once the argument " + "is dropped." + ) + ) @pytest.mark.covers("mcp.list_tools.api_key.succeeds", "mcp.call_tool.api_key.succeeds") def test_search_logs_finds_seeded_completion( self, diff --git a/tests/e2e/mcp/test_mcp_guardrail_e2e.py b/tests/e2e/mcp/test_mcp_guardrail_e2e.py index 9e3a8c48395..60a349ddc5e 100644 --- a/tests/e2e/mcp/test_mcp_guardrail_e2e.py +++ b/tests/e2e/mcp/test_mcp_guardrail_e2e.py @@ -78,6 +78,16 @@ def _search_on_synced_pod( class TestMcpToolCallGuardrail: + @pytest.mark.skip( + reason=( + "LIT-5052: the control call sends a `telemetry` argument that Datadog's " + "search_datadog_logs tool now rejects, so the clean-argument half of this test " + "errors with 'unexpected additional properties [\"telemetry\"]' and the guardrail " + "block it exists to prove is never exercised. `telemetry` was never a documented " + "Datadog parameter; the test relied on the server ignoring unknown properties. " + "Unskip once the argument is dropped." + ) + ) @pytest.mark.covers( "guardrail.litellm_content_filter.pre_mcp_call.blocks", exercised_on=["mcp_operations"], diff --git a/tests/e2e/mcp/test_mcp_key_access_e2e.py b/tests/e2e/mcp/test_mcp_key_access_e2e.py index 678424e36d1..788a0a3f45c 100644 --- a/tests/e2e/mcp/test_mcp_key_access_e2e.py +++ b/tests/e2e/mcp/test_mcp_key_access_e2e.py @@ -51,6 +51,16 @@ class TestMcpKeyWithoutAccessIsDenied: f"boundary: {denied_tools}" ) + @pytest.mark.skip( + reason=( + "LIT-5052: the control call proving a granted key CAN invoke the tool sends a " + "`telemetry` argument that Datadog's search_datadog_logs tool now rejects, so it " + "errors with 'unexpected additional properties [\"telemetry\"]' and the denial " + "assertion is never reached. `telemetry` was never a documented Datadog " + "parameter; the test relied on the server ignoring unknown properties. Unskip " + "once the argument is dropped." + ) + ) @pytest.mark.covers("mcp.call_tool.api_key.denied_without_permission") def test_call_tool_denied_without_permission( self, From 78c756dff94554435b2912cd416022ebb9c10291 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Fri, 31 Jul 2026 10:19:24 -0700 Subject: [PATCH 24/92] fix(proxy): rework the budgets list onto the merged list contract PR #35308 landed a different shape than this branch was written against: `where` is a tuple of frozen predicates rather than a Prisma-shaped mapping, `ListSpec` carries both the row and the wire type, and `where_sql` / `order_by_sql` render for a raw-SQL executor. The budgets executor now queries through `query_raw` the way the spend logs facet does, selecting only the columns it serves. Also casts datetime binds in `where_sql`. They cross into the query engine as JSON, so an uncast placeholder arrives as text and Postgres refuses `timestamp >= text` outright; every `filter[created_at][gte|lte]` was answering 500. The cast reads the bind as an instant and drops it to naive UTC to match Prisma's TIMESTAMP(3) column, the same one /spend/logs/ui applies. --- .../management_v1/budgets.py | 25 +++++--- .../management_v1/list_framework.py | 15 ++++- .../management_v1/test_budgets.py | 15 ++++- ui/litellm-dashboard/src/lib/http/schema.d.ts | 61 ++++++++++++++----- 4 files changed, 87 insertions(+), 29 deletions(-) diff --git a/litellm/proxy/management_endpoints/management_v1/budgets.py b/litellm/proxy/management_endpoints/management_v1/budgets.py index d9104eb5da2..bc1521caf0b 100644 --- a/litellm/proxy/management_endpoints/management_v1/budgets.py +++ b/litellm/proxy/management_endpoints/management_v1/budgets.py @@ -1,8 +1,9 @@ """`GET /management/v1/budgets`.""" -from collections.abc import Sequence +from collections.abc import Mapping, Sequence from dataclasses import dataclass from datetime import datetime +from types import MappingProxyType from typing import Annotated from fastapi import APIRouter, Depends, Request @@ -112,15 +113,19 @@ def _scope(caller: UserAPIKeyAuth) -> Scope: # budget_duration is deliberately absent from `sortable`: the column holds strings # like "7d" and "30d", so a lexicographic ORDER BY puts "30d" ahead of "7d". +BUDGET_FILTERS: Mapping[str, FilterSpec] = MappingProxyType( + { # mutable-ok: an immutable mapping has no literal form; MappingProxyType freezes this one and it never escapes + "budget_duration": FilterSpec(type=str, ops=frozenset(("in", "is_null"))), + "max_budget": FilterSpec(type=float, ops=frozenset(("gte", "lte", "is_null"))), + "created_at": FilterSpec(type=datetime, ops=frozenset(("gte", "lte"))), + } +) + BUDGETS_LIST_SPEC: ListSpec[BudgetListItem, BudgetListItem] = ListSpec( resource="budgets", - sortable=frozenset({"budget_id", "max_budget", "tpm_limit", "rpm_limit", "created_at"}), - searchable=frozenset({"budget_id"}), - filters={ - "budget_duration": FilterSpec(type=str, ops=frozenset({"in", "is_null"})), - "max_budget": FilterSpec(type=float, ops=frozenset({"gte", "lte", "is_null"})), - "created_at": FilterSpec(type=datetime, ops=frozenset({"gte", "lte"})), - }, + sortable=frozenset(("budget_id", "max_budget", "tpm_limit", "rpm_limit", "created_at")), + searchable=frozenset(("budget_id",)), + filters=BUDGET_FILTERS, default_sort=(SortKey(field="created_at", descending=True),), default_page_size=50, max_page_size=100, @@ -132,8 +137,8 @@ BUDGETS_LIST_SPEC: ListSpec[BudgetListItem, BudgetListItem] = ListSpec( @router.get( "/budgets", - tags=["budget management"], - dependencies=[Depends(user_api_key_auth)], + tags=("budget management",), + dependencies=(Depends(user_api_key_auth),), response_model=ListResponse[BudgetListItem], ) async def list_budgets( diff --git a/litellm/proxy/management_endpoints/management_v1/list_framework.py b/litellm/proxy/management_endpoints/management_v1/list_framework.py index 3e4b9131d1e..e2bddefab83 100644 --- a/litellm/proxy/management_endpoints/management_v1/list_framework.py +++ b/litellm/proxy/management_endpoints/management_v1/list_framework.py @@ -213,12 +213,23 @@ def _sql_operator(op: ComparisonOp) -> str: assert_never(op) +def _placeholder(index: int, value: FilterValue) -> str: + """`$n`, cast when the bind is a datetime. + + Binds cross into the query engine as JSON, so a datetime arrives as text and + Postgres refuses `timestamp >= text` outright. Prisma stores DateTime as a naive + `TIMESTAMP(3)` holding UTC, so the bind is read as an instant and then dropped to + naive UTC to match the column, the same cast `/spend/logs/ui` applies. + """ + return f"${index}::timestamptz AT TIME ZONE 'UTC'" if isinstance(value, datetime) else f"${index}" + + def _render(predicate: Predicate, index: int) -> tuple[str, tuple[object, ...]]: match predicate: case IsNull(field=field, negated=negated): return f'"{field}" IS {"NOT NULL" if negated else "NULL"}', () case Within(field=field, values=values): - placeholders = ", ".join(f"${index + offset}" for offset in range(len(values))) + placeholders = ", ".join(_placeholder(index + offset, value) for offset, value in enumerate(values)) return f'"{field}" IN ({placeholders})', values case AnyOf(clauses=clauses): rendered, params = _render_all(clauses, index) @@ -226,7 +237,7 @@ def _render(predicate: Predicate, index: int) -> tuple[str, tuple[object, ...]]: case Compare(field=field, op="contains", value=value): return f"\"{field}\" ILIKE ${index} ESCAPE '\\'", (f"%{escape_like(str(value))}%",) case Compare(field=field, op=op, value=value): - return f'"{field}" {_sql_operator(op)} ${index}', (value,) + return f'"{field}" {_sql_operator(op)} {_placeholder(index, value)}', (value,) case _: assert_never(predicate) diff --git a/tests/test_litellm/proxy/management_endpoints/management_v1/test_budgets.py b/tests/test_litellm/proxy/management_endpoints/management_v1/test_budgets.py index fe6d0289e53..f98286985b7 100644 --- a/tests/test_litellm/proxy/management_endpoints/management_v1/test_budgets.py +++ b/tests/test_litellm/proxy/management_endpoints/management_v1/test_budgets.py @@ -371,15 +371,28 @@ def test_in_filter_binds_each_requested_duration(query_raw, as_proxy_admin): def test_created_at_range_is_bound_as_a_timestamp(query_raw, as_proxy_admin): + """The bind crosses into the query engine as JSON, so an uncast placeholder reaches + Postgres as text and `timestamp >= text` is a hard error, not a wrong answer.""" _serve(query_raw, []) _get("filter[created_at][gte]=2026-07-01T00:00:00Z") sql, *params = _select_call(query_raw) - assert '"created_at" >= $1' in sql + assert "\"created_at\" >= $1::timestamptz AT TIME ZONE 'UTC'" in sql assert params[0] == datetime(2026, 7, 1, tzinfo=timezone.utc) +def test_a_non_datetime_bind_is_not_cast(query_raw, as_proxy_admin): + """Guards the cast above from being applied to every placeholder.""" + _serve(query_raw, []) + + _get("filter[max_budget][gte]=5") + + sql = _select_call(query_raw)[0] + assert '"max_budget" >= $1' in sql + assert "timestamptz" not in sql + + def test_an_offsetless_created_at_bound_is_read_as_utc(query_raw, as_proxy_admin): """The dashboard sends 'YYYY-MM-DDTHH:MM:SS' with no offset. Left naive, Postgres would compare it in the session timezone and shift the window off the rows shown.""" diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index 46a158bbac7..9a301f1c474 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -7187,10 +7187,11 @@ export interface paths { * * `sort` takes a comma-separated list of `budget_id`, `max_budget`, `tpm_limit`, * `rpm_limit` or `created_at`, each optionally prefixed with `-` for descending, - * and defaults to `-created_at,budget_id`. `q` is a case-insensitive substring - * match on `budget_id`. `page_size` defaults to 50 and is capped at 100. - * Filters are `filter[budget_duration][in|is_null]`, - * `filter[max_budget][gte|lte|is_null]` and `filter[created_at][gte|lte]`. + * and defaults to `-created_at`. `budget_id` is appended to every sort as the + * tiebreaker. `q` is a case-insensitive substring match on `budget_id`. + * `page_size` defaults to 50 and is capped at 100. Filters are + * `filter[budget_duration][in|is_null]`, `filter[max_budget][gte|lte|is_null]` + * and `filter[created_at][gte|lte]`. * * Example curl: * ``` @@ -21570,6 +21571,40 @@ export interface components { /** Reset At */ reset_at?: string | null; }; + /** + * BudgetListItem + * @description One budget as the Budgets page reads it, and as it comes back off the table. + * + * Validating the raw row through here is what makes `tpm_limit` / `rpm_limit` + * numbers: they are `BigInt?` in the schema, which the query engine hands back as + * decimal strings, and a quoted "60000" breaks arithmetic in the dashboard. + */ + BudgetListItem: { + /** Budget Duration */ + budget_duration?: string | null; + /** Budget Id */ + budget_id: string; + /** Budget Reset At */ + budget_reset_at?: string | null; + /** + * Created At + * Format: date-time + */ + created_at: string; + /** Max Budget */ + max_budget?: number | null; + /** Rpm Limit */ + rpm_limit?: number | null; + /** Soft Budget */ + soft_budget?: number | null; + /** Tpm Limit */ + tpm_limit?: number | null; + /** + * Updated At + * Format: date-time + */ + updated_at: string; + }; /** BudgetNewRequest */ BudgetNewRequest: { /** @@ -24814,7 +24849,6 @@ export interface components { /** Updated By */ updated_by?: string | null; }; - JsonValue: unknown; /** KeyHealthResponse */ KeyHealthResponse: { /** @@ -24968,7 +25002,7 @@ export interface components { }; /** * ListLinks - * @description Hypermedia for an entity list. `first`/`last` exist here because `total_pages` is known. + * @description Page-mode counterpart to `PageLinks`. `first`/`last` are knowable here because the total count is. */ ListLinks: { /** First */ @@ -24984,7 +25018,7 @@ export interface components { }; /** * ListMeta - * @description An entity list can afford the COUNT(*) a facet cannot, so it reports a real total. + * @description Page-mode counterpart to `PageMeta`: an entity list pays for the COUNT(*) so the table can show a page count. */ ListMeta: { /** Page */ @@ -25011,15 +25045,10 @@ export interface components { /** Prompts */ prompts: components["schemas"]["PromptSpec"][]; }; - /** - * ListResponse - * @description One page of an entity collection. Rows are flat: no `{type, id, attributes}` wrapper. - */ - ListResponse: { + /** ListResponse[BudgetListItem] */ + ListResponse_BudgetListItem_: { /** Data */ - data: { - [key: string]: components["schemas"]["JsonValue"]; - }[]; + data: components["schemas"]["BudgetListItem"][]; links: components["schemas"]["ListLinks"]; meta: components["schemas"]["ListMeta"]; }; @@ -43574,7 +43603,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["ListResponse"]; + "application/json": components["schemas"]["ListResponse_BudgetListItem_"]; }; }; }; From 0e9a624a97851b8c5f3bf9e1cdc3c272645568b7 Mon Sep 17 00:00:00 2001 From: Yassin Kortam Date: Fri, 31 Jul 2026 10:25:38 -0700 Subject: [PATCH 25/92] feat(mcp): source the ID-JAG subject from the user's stored SSO assertion (#35147) The ID-JAG egress arm could only assert a caller that presented its own IdP identity token on the request, so an agent holding a brokered LiteLLM credential got a 412 and never reached the upstream. The assertion captured at SSO login was already persisted per user for exactly this purpose, but nothing read it back. The arm now falls back to that stored assertion, keyed on the authenticated principal's user_id. The identity is always taken from the credential the gateway authenticated, never from a caller-supplied field, so no caller can select whose identity is asserted upstream. A missing, expired, or unidentified subject stays a 412; ID-JAG exists to assert a specific user and a missing subject has no safe substitute. A store outage is the one exception: it is surfaced as a typed AssertionStoreUnavailable and mapped to 503, so a database blip cannot 500 the egress or the upstream-401 retry, and does not tell the user to sign in again over something they cannot fix. Sourcing a subject from the store rather than the request changed what invalidation can rely on, so the exchanged-token cache changed with it. The entry is now addressed by a slot key derived from the principal, plus the caller's own token when it presented one, with a fingerprint of the subject token and config stored beside the bearer and compared on every read. A mismatch reads as a miss and re-mints, so a rotated assertion or an edited server config cannot be served a bearer authorized under the old inputs, and two callers cannot receive each other's. Invalidation is a single delete of a key it can always compute, needing no store lookup on the recovery path. The upstream-401 invalidate-and-retry path was also gated on a truthy inbound subject token, which skipped recovery entirely for store-sourced calls. The gate is now mode-aware: token_exchange still requires an inbound token because it has nothing else to mint from, id_jag does not. oauth2_id_jag is also now selectable in the admin dashboard with its own field set, instead of being reachable only from config.yaml or the REST API. The auth-type selects drop antd list virtualization: at eleven options the last one no longer mounts, which is a scroll in a browser but makes the option unreachable to anything reading the rendered list. Co-authored-by: Yassin Kortam --- .../mcp_server/mcp_server_manager.py | 16 +- .../outbound_credentials/resolver.py | 146 +++++- .../sso_assertion_store.py | 38 +- .../outbound_credentials/token_endpoint.py | 35 +- .../mcp_server/outbound_credentials/types.py | 3 +- .../outbound_credentials/test_resolver.py | 422 +++++++++++++++++- .../test_sso_assertion_store.py | 23 + .../mcp_server/test_mcp_server_manager.py | 32 +- ui/litellm-dashboard/eslint-suppressions.json | 5 + .../_components/IdJagFormFields.tsx | 158 +++++++ .../_components/create_mcp_server.test.tsx | 121 +++++ .../_components/create_mcp_server.tsx | 8 +- .../_components/mcp_server_edit.tsx | 8 +- .../src/components/mcp_tools/types.tsx | 8 +- 14 files changed, 987 insertions(+), 36 deletions(-) create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/IdJagFormFields.tsx diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index 89c83459524..db80c0f76ee 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -906,6 +906,20 @@ def _extract_upstream_auth_failure( return upstream_auth_challenge(exc) +def _obo_retry_applies(server: MCPServer, subject_token: str | None) -> bool: + """Whether an upstream 401/403 should invalidate the minted credential and retry once. + + ``oauth2_token_exchange`` can only mint from an inbound subject token, so with no token there is + nothing to re-mint and the plain single call is correct. ``oauth2_id_jag`` also sources its + subject from the identity assertion stored for the user at SSO login, so it qualifies whether or + not the caller presented a token of its own; gating it on the inbound token would leave a + store-sourced bearer un-invalidated and replayed until its TTL. + """ + if server.auth_type == MCPAuth.oauth2_id_jag: + return True + return server.auth_type == MCPAuth.oauth2_token_exchange and bool(subject_token) + + def _warn_on_server_name_fields( *, server_id: str, @@ -4778,7 +4792,7 @@ class MCPServerManager: arguments=arguments, ) - if mcp_server.auth_type in (MCPAuth.oauth2_token_exchange, MCPAuth.oauth2_id_jag) and subject_token: + if _obo_retry_applies(mcp_server, subject_token): # OBO / ID-JAG: the exchanged token may have been revoked/rotated upstream since it was # cached, so an upstream 401 gets one invalidate + re-mint + retry. Gated to these modes; # all others keep the plain single call below. diff --git a/litellm/proxy/_experimental/mcp_server/outbound_credentials/resolver.py b/litellm/proxy/_experimental/mcp_server/outbound_credentials/resolver.py index 69984a56311..b70db64ba94 100644 --- a/litellm/proxy/_experimental/mcp_server/outbound_credentials/resolver.py +++ b/litellm/proxy/_experimental/mcp_server/outbound_credentials/resolver.py @@ -10,19 +10,24 @@ at runtime instead of returning `None`. `none`, `api_key` (shared-key source), and `passthrough` (forwards the caller's own inbound token) are live, as is `authorization_code`, which reads the user's token from the injected `OAuthTokenStore`, `token_exchange`, which swaps the caller's inbound token through the injected -`TokenExchanger`, and `client_credentials`, which mints and caches the gateway's M2M token through -the injected `ClientCredentialsTokenSource`. The remaining arms are `not_implemented` stubs that -each land in a follow-up PR with their seam. Pure v2: no imports from v1. +`TokenExchanger`, `client_credentials`, which mints and caches the gateway's M2M token through the +injected `ClientCredentialsTokenSource`, and `id_jag`, which runs the two-leg identity-assertion +grant against a subject token taken from the request or from the injected `SSOAssertionStore`. The +remaining arms are `not_implemented` stubs that each land in a follow-up PR with their seam. Pure +v2: no imports from v1. """ from __future__ import annotations import hashlib +from datetime import datetime, timezone from functools import partial import httpx from typing_extensions import assert_never +from litellm._logging import verbose_proxy_logger + from litellm.proxy._experimental.mcp_server.outbound_credentials.client_credentials import ( ClientCredentialsBearerAuth, ClientCredentialsTokenSource, @@ -41,6 +46,12 @@ from litellm.proxy._experimental.mcp_server.outbound_credentials.result import ( Ok, Result, ) +from litellm.proxy._experimental.mcp_server.outbound_credentials.sso_assertion_store import ( + AssertionStoreUnavailable, + DbSSOAssertionStore, + SSOAssertionStore, + SSOIdentityAssertion, +) from litellm.proxy._experimental.mcp_server.outbound_credentials.token_endpoint import ( ExchangedToken, ExchangedTokenCache, @@ -111,12 +122,14 @@ class UpstreamCredentialProvider: token_endpoint: TokenEndpointClient | None = None, exchanged_tokens: ExchangedTokenCache | None = None, client_credentials_source: ClientCredentialsTokenSource | None = None, + sso_assertion_store: SSOAssertionStore | None = None, ) -> None: self._oauth_token_store: OAuthTokenStore = oauth_token_store or _NullOAuthTokenStore() self._token_exchanger: TokenExchanger = token_exchanger or _NullTokenExchanger() self._token_endpoint: TokenEndpointClient = token_endpoint or TokenEndpointClient() self._exchanged_tokens: ExchangedTokenCache = exchanged_tokens or ExchangedTokenCache() self._client_credentials_source = client_credentials_source or ClientCredentialsTokenSource() + self._sso_assertion_store: SSOAssertionStore = sso_assertion_store or DbSSOAssertionStore() async def resolve_credentials(self, subject: Subject, server: ServerSpec) -> Result[httpx.Auth, CredError]: match server.config: @@ -171,15 +184,73 @@ class UpstreamCredentialProvider: assert_never(config.key_source) async def _id_jag(self, subject: Subject, server: ServerSpec, config: IdJagConfig) -> Result[httpx.Auth, CredError]: - if subject.inbound_token is None: + match await self._id_jag_subject_token(subject): + case Error(err): + return Error(err) + case Ok(subject_token): + return await self._id_jag_exchange(subject, subject_token, server, config) + + async def _id_jag_subject_token(self, subject: Subject) -> Result[str, CredError]: + """The identity token ID-JAG leg 1 asserts, from the request or from the SSO login it was captured at. + + A caller that presents its own IdP identity token wins: that is the strongest available + assertion of who is calling. Otherwise the subject is the assertion captured for this user + at LiteLLM SSO login, which is what lets an agent holding a brokered LiteLLM credential + reach an upstream as the user it was issued for. The user is always taken from the + authenticated principal, never from a caller-supplied field, so no caller can select whose + identity is asserted upstream. + + Every miss is ``precondition_required`` (412) rather than a fall-through to a weaker + credential: ID-JAG exists to assert a specific user, so a missing subject has no safe + substitute. A store outage is the one exception: it is ``upstream_unavailable`` (503), not + 412, because the user has nothing to fix by signing in again, and it is a value rather than + a raised error so a DB blip cannot 500 the egress or the upstream-401 retry. + """ + if subject.inbound_token is not None: + return Ok(subject.inbound_token.get_secret_value()) + if not subject.subject_id: return Error( CredError.of_precondition_required( - "ID-JAG requires a caller identity token; it asserts the calling " - "user's identity upstream and cannot use a static credential." + "ID-JAG requires an identified caller; this request carries neither an " + "identity token nor a resolved LiteLLM user." ) ) - token = subject.inbound_token.get_secret_value() - cache_key = _id_jag_cache_key(token, server.server_id, config) + try: + assertion = await self._sso_assertion_store.fetch(subject.subject_id) + except AssertionStoreUnavailable as exc: + # The driver's message can name hosts, schemas or connection details, and this summary + # is returned to the caller verbatim as a 503 body. Operators get it from the log. + verbose_proxy_logger.warning( + "ID-JAG: the IdP identity assertion store is unreachable for user_id=%s: %s", + subject.subject_id, + exc, + ) + return Error( + CredError.of_upstream_unavailable( + "The IdP identity assertion store is unreachable, so ID-JAG cannot resolve a subject." + ) + ) + if assertion is None: + return Error( + CredError.of_precondition_required( + "ID-JAG requires an IdP identity assertion for this user and none is stored. " + "Sign in through LiteLLM SSO so the gateway captures one." + ) + ) + if _assertion_expired(assertion, datetime.now(timezone.utc)): + return Error( + CredError.of_precondition_required( + "The stored IdP identity assertion for this user has expired. Sign in through " + "LiteLLM SSO again to capture a current one." + ) + ) + return Ok(assertion.id_token.get_secret_value()) + + async def _id_jag_exchange( + self, subject: Subject, token: str, server: ServerSpec, config: IdJagConfig + ) -> Result[httpx.Auth, CredError]: + slot = _id_jag_slot_key(subject, server) + fingerprint = _id_jag_fingerprint(token, server.server_id, config) async def _exchange() -> Result[ExchangedToken, CredError]: leg1_params = { @@ -211,7 +282,7 @@ class UpstreamCredentialProvider: config.client_auth, ) - match await self._exchanged_tokens.get_or_compute(cache_key, _exchange): + match await self._exchanged_tokens.get_or_compute(slot, _exchange, fingerprint=fingerprint): case Ok(access_token): return Ok(StaticHeaderAuth(f"Bearer {access_token}")) case Error(err): @@ -273,17 +344,27 @@ class UpstreamCredentialProvider: re-mintable cached credential here; `client_credentials` recovers inside its own auth flow (`ClientCredentialsBearerAuth` retries the 401'd request once with a fresh token), and other modes are a no-op. + + `id_jag` evicts by a slot key derived from the principal, so it needs no lookup against the + assertion store on this path; the fingerprint stored beside the entry is what keeps a slot + shared between callers safe. """ - if subject.inbound_token is None: - return - if isinstance(server.config, TokenExchangeConfig): + if isinstance(server.config, IdJagConfig): + self._invalidate_id_jag(subject, server) + elif isinstance(server.config, TokenExchangeConfig) and subject.inbound_token is not None: await self._token_exchanger.invalidate( subject.inbound_token.get_secret_value(), server, server.config, tenant_id=subject.tenant_id ) - if isinstance(server.config, IdJagConfig): - self._exchanged_tokens.invalidate( - _id_jag_cache_key(subject.inbound_token.get_secret_value(), server.server_id, server.config) - ) + + def _invalidate_id_jag(self, subject: Subject, server: ServerSpec) -> None: + """Evict the bearer this `(subject, server)` last resolved, without depending on the store. + + The slot is addressed by the principal (plus the caller's own token when it presented one), + never by the credential material, so it stays computable when the assertion store is down. + The fingerprint stored with the entry is what keeps that safe: an entry minted for different + inputs reads as a miss rather than being served. + """ + self._exchanged_tokens.invalidate(_id_jag_slot_key(subject, server)) async def _authz_token(self, subject: Subject, server: ServerSpec) -> OAuthToken | None: """The user's authorization_code token, or None when absent or the store is unreachable. @@ -297,8 +378,37 @@ class UpstreamCredentialProvider: return None -def _id_jag_cache_key(subject_token: str, server_id: str, config: IdJagConfig) -> str: - """Bind the cached leg-2 bearer to the caller token, the server, AND the config that minted it. +def _id_jag_slot_key(subject: Subject, server: ServerSpec) -> str: + """Which cache slot this caller's bearer for this upstream lives in. + + Addressed by the principal, plus the caller's own token when it presented one so two callers + sharing an empty principal do not contend for one slot. Deliberately free of the stored + assertion, which is what lets invalidation compute this while the assertion store is down. The + entry's fingerprint, not this key, is what guarantees a cached bearer matches current inputs. + """ + inbound = subject.inbound_token.get_secret_value() if subject.inbound_token is not None else "" + material = "\x00".join((subject.tenant_id, subject.subject_id, server.server_id, inbound)) + return hashlib.sha256(material.encode()).hexdigest() + + +def _assertion_expired(assertion: SSOIdentityAssertion, now: datetime) -> bool: + """Whether the stored assertion's ``exp`` has passed. An assertion carrying no expiry is + treated as usable and left for the IdP to reject, since the store records what the id_token + claimed rather than imposing a lifetime of its own. A naive ``expires_at`` is read as UTC so a + stored value that lost its offset compares instead of raising. + """ + expires_at = assertion.expires_at + if expires_at is None: + return False + normalized = expires_at if expires_at.tzinfo is not None else expires_at.replace(tzinfo=timezone.utc) + return normalized <= now + + +def _id_jag_fingerprint(subject_token: str, server_id: str, config: IdJagConfig) -> str: + """What the cached leg-2 bearer was minted from: the subject token, the server, and the config. + + Stored beside the bearer and compared on every read, so a rotated assertion or an edited server + config reads as a miss and re-mints instead of serving a bearer authorized under the old policy. Every exchange parameter derives from the config (endpoints, audience, resource, scopes, client auth), so a server update that changes any of them must change the key; otherwise the old bearer, diff --git a/litellm/proxy/_experimental/mcp_server/outbound_credentials/sso_assertion_store.py b/litellm/proxy/_experimental/mcp_server/outbound_credentials/sso_assertion_store.py index e0927cc4f64..d52c718c0b8 100644 --- a/litellm/proxy/_experimental/mcp_server/outbound_credentials/sso_assertion_store.py +++ b/litellm/proxy/_experimental/mcp_server/outbound_credentials/sso_assertion_store.py @@ -18,7 +18,7 @@ from __future__ import annotations import json from datetime import datetime, timezone -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Protocol import jwt from pydantic import BaseModel, ConfigDict, SecretStr, TypeAdapter, ValidationError @@ -160,6 +160,42 @@ async def fetch_sso_identity_assertion(user_id: str) -> SSOIdentityAssertion | N ) +class AssertionStoreUnavailable(Exception): + """Raised by ``fetch`` when the backing store is unreachable (e.g. the DB is down). + + Distinct from returning ``None`` for "this user has no captured assertion": an outage must not + read as a definite absence, which would tell the user to sign in again over a transient failure, + and it must not escape as an unhandled error on the egress or retry path. Mirrors + ``TokenStoreUnavailable`` on the sibling per-user OAuth store. + """ + + +class SSOAssertionStore(Protocol): + """The read seam the ``id_jag`` egress arm depends on, so the arm takes a collaborator + rather than reaching for a module-level function and a proxy global at call time. + + Returns the user's captured assertion, or ``None`` when they have never signed in. Raises + ``AssertionStoreUnavailable`` when the backing store is unreachable. + """ + + async def fetch(self, user_id: str) -> SSOIdentityAssertion | None: ... + + +class DbSSOAssertionStore: + """The live store: the row the SSO callback wrote, read back by ``user_id``. + + A storage failure is re-raised as ``AssertionStoreUnavailable`` so the resolver can map it to a + typed fail-closed result; letting the raw driver error escape would surface a DB blip as a 500 + from credential resolution and from the upstream-401 retry. + """ + + async def fetch(self, user_id: str) -> SSOIdentityAssertion | None: + try: + return await fetch_sso_identity_assertion(user_id) + except Exception as exc: # noqa: BLE001 # any driver/storage failure is an outage, not an absence + raise AssertionStoreUnavailable(str(exc)) from exc + + async def rotate_sso_identity_assertions_master_key(prisma_client: PrismaClient, new_master_key: str) -> None: """Re-encrypt every stored assertion under ``new_master_key`` during a salt-key rotation, mirroring the sibling per-user credential tables; an unreadable row is skipped so one diff --git a/litellm/proxy/_experimental/mcp_server/outbound_credentials/token_endpoint.py b/litellm/proxy/_experimental/mcp_server/outbound_credentials/token_endpoint.py index 4bc5732ec0e..3ed22732c90 100644 --- a/litellm/proxy/_experimental/mcp_server/outbound_credentials/token_endpoint.py +++ b/litellm/proxy/_experimental/mcp_server/outbound_credentials/token_endpoint.py @@ -23,7 +23,7 @@ from dataclasses import dataclass import httpx import jwt -from pydantic import BaseModel, ValidationError +from pydantic import BaseModel, TypeAdapter, ValidationError from typing_extensions import assert_never from litellm._logging import verbose_proxy_logger @@ -51,6 +51,9 @@ from litellm.proxy._experimental.mcp_server.outbound_credentials.types import ( ) from litellm.types.llms.custom_http import httpxSpecialProvider +# The cache stores (fingerprint, token); anything else in the slot is treated as absent. +_CACHED_ENTRY_ADAPTER: TypeAdapter[tuple[str, str]] = TypeAdapter(tuple[str, str]) + CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" CLIENT_ASSERTION_LIFETIME_SECONDS = 60 @@ -134,19 +137,28 @@ class ExchangedTokenCache: self, cache_key: str, compute: Callable[[], Awaitable[Result[ExchangedToken, CredError]]], + *, + fingerprint: str = "", ) -> Result[str, CredError]: - cached = self._get(cache_key) + """The cached token for `cache_key`, minting one when absent. + + `fingerprint` lets a caller address a slot by something stable (a principal) while still + guaranteeing the token it gets back was minted for the *current* inputs: a stored entry + whose fingerprint differs reads as a miss and is re-minted over. That keeps eviction + addressable without the key having to encode the credential material it protects. + """ + cached = self._get(cache_key, fingerprint) if cached is not None: return Ok(cached) async with self._lock(cache_key): - cached = self._get(cache_key) + cached = self._get(cache_key, fingerprint) if cached is not None: return Ok(cached) match await compute(): case Ok(token): self._cache.set_cache( # pyright: ignore[reportUnknownMemberType] # InMemoryCache is untyped cache_key, - token.access_token, + (fingerprint, token.access_token), ttl=_cache_ttl_seconds(token.expires_in), ) return Ok(token.access_token) @@ -157,9 +169,18 @@ class ExchangedTokenCache: """Evict one cached token so the next `get_or_compute` re-mints (e.g. after an upstream 401).""" self._cache.delete_cache(cache_key) # pyright: ignore[reportUnknownMemberType] # InMemoryCache is untyped - def _get(self, cache_key: str) -> str | None: - value = self._cache.get_cache(cache_key) # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType] # InMemoryCache is untyped; narrowed by isinstance below - return value if isinstance(value, str) else None + def _get(self, cache_key: str, fingerprint: str) -> str | None: + """The stored token, or None when absent or minted for different inputs. + + The fingerprint comparison is what makes a shared slot safe: a mismatch never returns the + other party's token, it just reads as a miss. + """ + value = self._cache.get_cache(cache_key) # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType] # InMemoryCache is untyped; the adapter below is the type gate + try: + stored_fingerprint, token = _CACHED_ENTRY_ADAPTER.validate_python(value) + except ValidationError: + return None + return token if stored_fingerprint == fingerprint else None def _lock(self, cache_key: str) -> asyncio.Lock: lock = self._locks.get(cache_key) diff --git a/litellm/proxy/_experimental/mcp_server/outbound_credentials/types.py b/litellm/proxy/_experimental/mcp_server/outbound_credentials/types.py index 0f276cb8e5c..f80954986e5 100644 --- a/litellm/proxy/_experimental/mcp_server/outbound_credentials/types.py +++ b/litellm/proxy/_experimental/mcp_server/outbound_credentials/types.py @@ -391,7 +391,8 @@ class Subject(BaseModel): tenant_id: str subject_id: str - # Opaque, already-validated inbound identity. Only `token_exchange` / `passthrough` read it. + # Opaque, already-validated inbound identity. Read by `token_exchange`, `passthrough`, and + # `id_jag` (which falls back to the user's stored SSO assertion when it is absent). inbound_token: SecretStr | None = None diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_resolver.py b/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_resolver.py index a710da81962..0d130767bd5 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_resolver.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_resolver.py @@ -7,6 +7,10 @@ also guards reachability: a dropped `case` would hit `assert_never` and raise in returning the stub. """ +import asyncio +import logging +from datetime import datetime, timedelta, timezone + import httpx import pytest from pydantic import SecretStr @@ -25,6 +29,7 @@ from litellm.proxy._experimental.mcp_server.outbound_credentials import ( NoOpAuth, Ok, PassthroughConfig, + PrivateKeyJwtAuth, Result, ServerSpec, SharedKey, @@ -37,6 +42,10 @@ from litellm.proxy._experimental.mcp_server.outbound_credentials.oauth_token_sto OAuthToken, TokenStoreUnavailable, ) +from litellm.proxy._experimental.mcp_server.outbound_credentials.sso_assertion_store import ( + AssertionStoreUnavailable, + SSOIdentityAssertion, +) from litellm.proxy._experimental.mcp_server.outbound_credentials.token_endpoint import ( ExchangedToken, ) @@ -71,6 +80,23 @@ def _with_inbound(token: str) -> Subject: return Subject(tenant_id="", subject_id="alice", inbound_token=SecretStr(token)) +class _FakeAssertionStore: + """The SSO assertion read seam, canned per user_id and recording every lookup.""" + + def __init__(self, assertions: dict[str, SSOIdentityAssertion] | None = None) -> None: + self._assertions = dict(assertions or {}) + self.lookups: list[str] = [] + + async def fetch(self, user_id: str) -> SSOIdentityAssertion | None: + self.lookups.append(user_id) + return self._assertions.get(user_id) + + +def _assertion(id_token: str, expires_in: timedelta | None = timedelta(minutes=30)) -> SSOIdentityAssertion: + expires_at = datetime.now(timezone.utc) + expires_in if expires_in is not None else None + return SSOIdentityAssertion(id_token=SecretStr(id_token), expires_at=expires_at) + + def _spec(config): return ServerSpec(server_id="s", resource="https://upstream.example.com", config=config) @@ -471,9 +497,10 @@ async def test_id_jag_runs_both_legs_and_returns_the_leg2_bearer(): @pytest.mark.asyncio -async def test_id_jag_without_inbound_token_is_precondition_required_no_http(): +async def test_id_jag_without_inbound_token_or_stored_assertion_is_precondition_required_no_http(): endpoint = _FakeTokenEndpoint([]) - provider = UpstreamCredentialProvider(token_endpoint=endpoint) + store = _FakeAssertionStore() + provider = UpstreamCredentialProvider(token_endpoint=endpoint, sso_assertion_store=store) result = await provider.resolve_credentials( Subject(tenant_id="", subject_id="alice"), _spec(_id_jag_config()) ) @@ -481,6 +508,397 @@ async def test_id_jag_without_inbound_token_is_precondition_required_no_http(): assert isinstance(result, Error) assert result.error.tag == "precondition_required" assert endpoint.calls == [] + assert store.lookups == ["alice"] + + +@pytest.mark.asyncio +async def test_id_jag_exchanges_the_stored_sso_assertion_when_the_caller_presents_no_token(): + """The agent-triggered flow: a brokered LiteLLM credential carries no IdP token, so leg 1's + subject is the assertion captured for that user at SSO login.""" + endpoint = _FakeTokenEndpoint(_two_leg_ok("final-access")) + store = _FakeAssertionStore({"alice": _assertion("alice-id-token")}) + provider = UpstreamCredentialProvider(token_endpoint=endpoint, sso_assertion_store=store) + + result = await provider.resolve_credentials( + Subject(tenant_id="", subject_id="alice"), _spec(_id_jag_config()) + ) + + assert isinstance(result, Ok) + assert _emitted(result.ok)["Authorization"] == "Bearer final-access" + assert store.lookups == ["alice"] + _, _, leg1_params = endpoint.calls[0] + assert leg1_params["subject_token"] == "alice-id-token" + assert leg1_params["requested_token_type"] == "urn:ietf:params:oauth:token-type:id-jag" + + +@pytest.mark.asyncio +async def test_id_jag_prefers_the_callers_own_token_over_the_stored_assertion(): + endpoint = _FakeTokenEndpoint(_two_leg_ok("final-access")) + store = _FakeAssertionStore({"alice": _assertion("stored-id-token")}) + provider = UpstreamCredentialProvider(token_endpoint=endpoint, sso_assertion_store=store) + + result = await provider.resolve_credentials(_with_inbound("inbound-id-token"), _spec(_id_jag_config())) + + assert isinstance(result, Ok) + _, _, leg1_params = endpoint.calls[0] + assert leg1_params["subject_token"] == "inbound-id-token" + assert store.lookups == [] + + +@pytest.mark.asyncio +async def test_id_jag_refuses_an_expired_stored_assertion_without_calling_the_idp(): + endpoint = _FakeTokenEndpoint([]) + store = _FakeAssertionStore({"alice": _assertion("stale-id-token", expires_in=-timedelta(seconds=1))}) + provider = UpstreamCredentialProvider(token_endpoint=endpoint, sso_assertion_store=store) + + result = await provider.resolve_credentials( + Subject(tenant_id="", subject_id="alice"), _spec(_id_jag_config()) + ) + + assert isinstance(result, Error) + assert result.error.tag == "precondition_required" + assert endpoint.calls == [] + + +@pytest.mark.asyncio +async def test_id_jag_accepts_a_stored_assertion_that_declares_no_expiry(): + endpoint = _FakeTokenEndpoint(_two_leg_ok("final-access")) + store = _FakeAssertionStore({"alice": _assertion("undated-id-token", expires_in=None)}) + provider = UpstreamCredentialProvider(token_endpoint=endpoint, sso_assertion_store=store) + + result = await provider.resolve_credentials( + Subject(tenant_id="", subject_id="alice"), _spec(_id_jag_config()) + ) + + assert isinstance(result, Ok) + _, _, leg1_params = endpoint.calls[0] + assert leg1_params["subject_token"] == "undated-id-token" + + +@pytest.mark.asyncio +async def test_id_jag_never_reads_the_store_for_an_unidentified_caller(): + """An empty subject_id must not select a credential; otherwise every anonymous caller would + share one store slot.""" + endpoint = _FakeTokenEndpoint([]) + store = _FakeAssertionStore({"": _assertion("anonymous-slot")}) + provider = UpstreamCredentialProvider(token_endpoint=endpoint, sso_assertion_store=store) + + result = await provider.resolve_credentials(Subject(tenant_id="", subject_id=""), _spec(_id_jag_config())) + + assert isinstance(result, Error) + assert result.error.tag == "precondition_required" + assert store.lookups == [] + assert endpoint.calls == [] + + +@pytest.mark.asyncio +async def test_id_jag_keeps_store_sourced_bearers_partitioned_per_user(): + endpoint = _FakeTokenEndpoint( + [ + Ok(ExchangedToken(access_token="alice-id-jag", expires_in=300)), + Ok(ExchangedToken(access_token="alice-bearer", expires_in=3600)), + Ok(ExchangedToken(access_token="bob-id-jag", expires_in=300)), + Ok(ExchangedToken(access_token="bob-bearer", expires_in=3600)), + ] + ) + store = _FakeAssertionStore( + {"alice": _assertion("alice-id-token"), "bob": _assertion("bob-id-token")} + ) + provider = UpstreamCredentialProvider(token_endpoint=endpoint, sso_assertion_store=store) + + alice = await provider.resolve_credentials( + Subject(tenant_id="", subject_id="alice"), _spec(_id_jag_config()) + ) + bob = await provider.resolve_credentials(Subject(tenant_id="", subject_id="bob"), _spec(_id_jag_config())) + + assert isinstance(alice, Ok) and isinstance(bob, Ok) + assert _emitted(alice.ok)["Authorization"] == "Bearer alice-bearer" + assert _emitted(bob.ok)["Authorization"] == "Bearer bob-bearer" + + +_DRIVER_DETAIL = "could not connect to host=pg-primary.internal port=5432 user=litellm" + + +class _OutageAssertionStore: + """A store whose backing DB is down, failing with a driver message full of internals.""" + + def __init__(self) -> None: + self.lookups: list[str] = [] + + async def fetch(self, user_id: str) -> SSOIdentityAssertion | None: + self.lookups.append(user_id) + raise AssertionStoreUnavailable(_DRIVER_DETAIL) + + +@pytest.mark.asyncio +async def test_id_jag_maps_an_assertion_store_outage_to_upstream_unavailable(): + """A store outage must not escape as an unhandled error, and must not be reported as a missing + assertion: telling the user to sign in again does not fix a database that is down.""" + endpoint = _FakeTokenEndpoint([]) + provider = UpstreamCredentialProvider(token_endpoint=endpoint, sso_assertion_store=_OutageAssertionStore()) + + result = await provider.resolve_credentials( + Subject(tenant_id="", subject_id="alice"), _spec(_id_jag_config()) + ) + + assert isinstance(result, Error) + assert result.error.tag == "upstream_unavailable" + assert endpoint.calls == [] + + +@pytest.mark.asyncio +async def test_id_jag_store_outage_does_not_leak_driver_detail_to_the_caller(caplog): + """`upstream_unavailable` is rendered into the 503 body verbatim, so the driver's message, which + can name hosts, ports and users, must stay out of the summary and go to the log instead.""" + provider = UpstreamCredentialProvider( + token_endpoint=_FakeTokenEndpoint([]), sso_assertion_store=_OutageAssertionStore() + ) + + with caplog.at_level(logging.WARNING): + result = await provider.resolve_credentials( + Subject(tenant_id="", subject_id="alice"), _spec(_id_jag_config()) + ) + + assert isinstance(result, Error) + assert _DRIVER_DETAIL not in result.error.summary + assert "pg-primary.internal" not in result.error.summary + # The operator still needs it, so it must be in the log. + assert _DRIVER_DETAIL in caplog.text + + +@pytest.mark.asyncio +async def test_id_jag_invalidation_survives_an_assertion_store_outage(): + """invalidate_credentials runs on the upstream-401 retry path, so a store outage there must be + swallowed rather than turning a recoverable 401 into a 500.""" + provider = UpstreamCredentialProvider( + token_endpoint=_FakeTokenEndpoint([]), sso_assertion_store=_OutageAssertionStore() + ) + + await provider.invalidate_credentials( + Subject(tenant_id="", subject_id="alice"), _spec(_id_jag_config()) + ) + + +class _FlakyAssertionStore: + """Serves an assertion, but fails while ``down`` is set.""" + + def __init__(self, assertion: SSOIdentityAssertion) -> None: + self._assertion = assertion + self.down = False + + async def fetch(self, user_id: str) -> SSOIdentityAssertion | None: + if self.down: + raise AssertionStoreUnavailable("connection refused") + return self._assertion + + +@pytest.mark.asyncio +async def test_id_jag_evicts_the_rejected_bearer_even_if_the_store_is_down_during_invalidation(): + """The upstream-401 recovery sequence with a transient store blip. + + Invalidation runs while the store is unreachable and the store recovers before the retry + resolves. Deriving the eviction key from a fresh lookup would evict nothing and then recompute + the identical key, handing the retry the very bearer the upstream just rejected. + """ + endpoint = _FakeTokenEndpoint(_two_leg_ok("rejected-bearer") + _two_leg_ok("reminted-bearer")) + store = _FlakyAssertionStore(_assertion("alice-id-token")) + provider = UpstreamCredentialProvider(token_endpoint=endpoint, sso_assertion_store=store) + subject = Subject(tenant_id="", subject_id="alice") + spec = _spec(_id_jag_config()) + + first = await provider.resolve_credentials(subject, spec) + assert isinstance(first, Ok) + assert _emitted(first.ok)["Authorization"] == "Bearer rejected-bearer" + + store.down = True + await provider.invalidate_credentials(subject, spec) + store.down = False + + second = await provider.resolve_credentials(subject, spec) + assert isinstance(second, Ok) + assert _emitted(second.ok)["Authorization"] == "Bearer reminted-bearer" + assert len(endpoint.calls) == 4 + + +class _SwitchableAssertionStore: + """Serves whichever assertion the test currently points it at, as a re-login would.""" + + def __init__(self, id_token: str) -> None: + self.id_token = id_token + + async def fetch(self, user_id: str) -> SSOIdentityAssertion | None: + return _assertion(self.id_token) + + +@pytest.mark.asyncio +async def test_id_jag_invalidation_clears_every_live_bearer_for_the_principal(): + """Overlapping store-sourced requests for one principal can hold different keys (a re-login + between them mints a different subject token). Invalidation must clear all of them: keeping + only the newest would let one request's 401 recovery evict the other's entry and leave its own + rejected bearer cached to be replayed on the retry.""" + endpoint = _FakeTokenEndpoint( + _two_leg_ok("bearer-from-first") + _two_leg_ok("bearer-from-second") + _two_leg_ok("reminted") + ) + store = _SwitchableAssertionStore("id-token-first") + provider = UpstreamCredentialProvider(token_endpoint=endpoint, sso_assertion_store=store) + subject = Subject(tenant_id="", subject_id="alice") + spec = _spec(_id_jag_config()) + + first = await provider.resolve_credentials(subject, spec) + store.id_token = "id-token-second" + second = await provider.resolve_credentials(subject, spec) + assert isinstance(first, Ok) and isinstance(second, Ok) + assert _emitted(first.ok)["Authorization"] == "Bearer bearer-from-first" + assert _emitted(second.ok)["Authorization"] == "Bearer bearer-from-second" + + await provider.invalidate_credentials(subject, spec) + + # Point the store back at the first token. If that entry had survived the invalidation this + # would replay "bearer-from-first", which is the bearer an upstream may already have rejected. + store.id_token = "id-token-first" + third = await provider.resolve_credentials(subject, spec) + assert isinstance(third, Ok) + assert _emitted(third.ok)["Authorization"] == "Bearer reminted" + + +class _SequentialAssertionStore: + """Issues a distinct assertion per call unless pinned, so concurrent resolutions genuinely + mint distinct credentials rather than collapsing onto one through single-flight.""" + + def __init__(self) -> None: + self.pinned: str | None = None + self.issued: list[str] = [] + self._n = 0 + + async def fetch(self, user_id: str) -> SSOIdentityAssertion | None: + await asyncio.sleep(0) + if self.pinned is not None: + return _assertion(self.pinned) + self._n += 1 + token = f"id-token-{self._n}" + self.issued.append(token) + return _assertion(token) + + +class _CountingTokenEndpoint: + """Mints a unique bearer per exchange and yields, so exchanges interleave.""" + + def __init__(self) -> None: + self._n = 0 + + async def fetch(self, endpoint, client_id, grant_params, client_auth): + await asyncio.sleep(0) + self._n += 1 + return Ok(ExchangedToken(access_token=f"tok-{self._n}", expires_in=3600)) + + +@pytest.mark.asyncio +async def test_id_jag_invalidation_leaves_no_bearer_behind_under_concurrency(): + """After invalidation, no bearer minted before it may ever be served again. + + Drives many overlapping resolutions that each mint a distinct credential, invalidates once, + then replays every subject token that was issued. Any credential the eviction could not reach + would show up here as a replayed pre-invalidation bearer. + """ + concurrency = 20 + endpoint = _CountingTokenEndpoint() + store = _SequentialAssertionStore() + provider = UpstreamCredentialProvider(token_endpoint=endpoint, sso_assertion_store=store) + subject = Subject(tenant_id="t", subject_id="alice") + spec = _spec(_id_jag_config()) + + results = await asyncio.gather(*(provider.resolve_credentials(subject, spec) for _ in range(concurrency))) + before = {_emitted(r.ok)["Authorization"] for r in results if isinstance(r, Ok)} + issued = list(store.issued) + # Guard the guard: if these collapsed onto one credential the test would prove nothing. + assert len(before) > 1 + + await provider.invalidate_credentials(subject, spec) + + for token in issued: + store.pinned = token + replayed = await provider.resolve_credentials(subject, spec) + assert isinstance(replayed, Ok) + assert _emitted(replayed.ok)["Authorization"] not in before + + +@pytest.mark.asyncio +async def test_id_jag_never_serves_a_bearer_minted_for_a_different_caller(): + """Two unidentified-principal callers share a slot, so the fingerprint, not the key, is what + keeps them apart: a mismatch must read as a miss rather than hand over the other's bearer.""" + endpoint = _FakeTokenEndpoint(_two_leg_ok("first-callers-bearer") + _two_leg_ok("second-callers-bearer")) + provider = UpstreamCredentialProvider(token_endpoint=endpoint) + spec = _spec(_id_jag_config()) + + first = await provider.resolve_credentials(_with_inbound("caller-one-token"), spec) + second = await provider.resolve_credentials(_with_inbound("caller-two-token"), spec) + + assert isinstance(first, Ok) and isinstance(second, Ok) + assert _emitted(first.ok)["Authorization"] == "Bearer first-callers-bearer" + assert _emitted(second.ok)["Authorization"] == "Bearer second-callers-bearer" + + +@pytest.mark.asyncio +async def test_id_jag_rotating_the_signing_key_does_not_reuse_the_cached_bearer(): + """The cache key fingerprints the private-key-JWT client auth, so a rotated signing key + re-mints instead of serving a bearer authorized under the retired key.""" + endpoint = _FakeTokenEndpoint(_two_leg_ok("old-key-bearer") + _two_leg_ok("new-key-bearer")) + store = _FakeAssertionStore({"alice": _assertion("alice-id-token")}) + provider = UpstreamCredentialProvider(token_endpoint=endpoint, sso_assertion_store=store) + subject = Subject(tenant_id="", subject_id="alice") + + def _with_key(pem: str) -> IdJagConfig: + return _id_jag_config().model_copy( + update={"client_auth": PrivateKeyJwtAuth(private_key=SecretStr(pem), key_id="kid-1")} + ) + + first = await provider.resolve_credentials(subject, _spec(_with_key("-----OLD KEY-----"))) + second = await provider.resolve_credentials(subject, _spec(_with_key("-----NEW KEY-----"))) + + assert isinstance(first, Ok) and isinstance(second, Ok) + assert _emitted(first.ok)["Authorization"] == "Bearer old-key-bearer" + assert _emitted(second.ok)["Authorization"] == "Bearer new-key-bearer" + assert len(endpoint.calls) == 4 + + +@pytest.mark.asyncio +async def test_id_jag_reads_a_naive_stored_expiry_as_utc(): + """A stored expires_at that lost its offset must still compare rather than raise: an aware/naive + comparison would be a TypeError on the egress path, turning a 412 into a 500.""" + endpoint = _FakeTokenEndpoint([]) + naive_past = datetime.now(timezone.utc).replace(tzinfo=None) - timedelta(hours=1) + store = _FakeAssertionStore( + {"alice": SSOIdentityAssertion(id_token=SecretStr("stale"), expires_at=naive_past)} + ) + provider = UpstreamCredentialProvider(token_endpoint=endpoint, sso_assertion_store=store) + + result = await provider.resolve_credentials( + Subject(tenant_id="", subject_id="alice"), _spec(_id_jag_config()) + ) + + assert isinstance(result, Error) + assert result.error.tag == "precondition_required" + assert endpoint.calls == [] + + +@pytest.mark.asyncio +async def test_invalidate_evicts_a_store_sourced_id_jag_bearer(): + """The upstream-401 recovery path. Keyed off the request alone the eviction would miss, and the + rejected bearer would be replayed until its TTL.""" + endpoint = _FakeTokenEndpoint(_two_leg_ok("first-bearer") + _two_leg_ok("second-bearer")) + store = _FakeAssertionStore({"alice": _assertion("alice-id-token")}) + provider = UpstreamCredentialProvider(token_endpoint=endpoint, sso_assertion_store=store) + subject = Subject(tenant_id="", subject_id="alice") + spec = _spec(_id_jag_config()) + + first = await provider.resolve_credentials(subject, spec) + await provider.invalidate_credentials(subject, spec) + second = await provider.resolve_credentials(subject, spec) + + assert isinstance(first, Ok) and isinstance(second, Ok) + assert _emitted(first.ok)["Authorization"] == "Bearer first-bearer" + assert _emitted(second.ok)["Authorization"] == "Bearer second-bearer" + assert len(endpoint.calls) == 4 @pytest.mark.asyncio diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_sso_assertion_store.py b/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_sso_assertion_store.py index a3f46a49ba9..7b82e004f37 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_sso_assertion_store.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_sso_assertion_store.py @@ -8,6 +8,7 @@ rotation re-encrypts stored rows like the sibling per-user credential tables. """ import json +import os import time from unittest.mock import AsyncMock, MagicMock, patch @@ -15,6 +16,8 @@ import jwt as pyjwt import pytest from litellm.proxy._experimental.mcp_server.outbound_credentials.sso_assertion_store import ( + AssertionStoreUnavailable, + DbSSOAssertionStore, assertion_from_sso_login, ema_assertion_retention_enabled, fetch_sso_identity_assertion, @@ -341,3 +344,23 @@ async def test_rotation_skips_unreadable_rows_but_rotates_readable_ones(): await rotate_sso_identity_assertions_master_key(prisma_client=prisma, new_master_key="another-new-salt-key-0000") assert stored["bad"] == "garbage-blob" assert stored["good"] != good_blob_before + + +@pytest.mark.asyncio +async def test_db_store_converts_a_driver_failure_into_assertion_store_unavailable(): + """The live store must not let a raw driver error escape: the resolver distinguishes an outage + from an absent assertion, and only a typed failure lets it do that.""" + prisma = MagicMock() + prisma.db.litellm_ssoidentityassertion.find_unique = AsyncMock(side_effect=RuntimeError("connection refused")) + with patch("litellm.proxy.proxy_server.prisma_client", prisma): + with pytest.raises(AssertionStoreUnavailable): + await DbSSOAssertionStore().fetch("alice") + + +@pytest.mark.asyncio +async def test_db_store_returns_none_for_a_user_with_no_stored_assertion(): + """An absent row stays an absence, not an outage, so a user who never signed in still gets the + 412 that tells them to.""" + with patch.dict(os.environ, {"LITELLM_SALT_KEY": SALT_KEY}): + with patch("litellm.proxy.proxy_server.prisma_client", _make_prisma({})): + assert await DbSSOAssertionStore().fetch("nobody") is None diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py index db5b64ef131..f6753e28a66 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py @@ -40,6 +40,7 @@ from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( _oauth_endpoints_unresolved, _deserialize_json_list, _normalize_mcp_server_cost_info, + _obo_retry_applies, _should_strip_caller_authorization, _without_authorization, ) @@ -50,7 +51,7 @@ from litellm.proxy._types import ( MCPEnvVarScope, MCPTransport, ) -from litellm.types.mcp import MCPAuth +from litellm.types.mcp import MCPAuth, MCPAuthType from litellm.types.mcp_server.mcp_server_manager import MCPOAuthMetadata, MCPServer @@ -8232,6 +8233,35 @@ def test_should_strip_caller_authorization_for_token_exchange(): assert _should_strip_caller_authorization(mcp_server=server, raw_headers=None, user_api_key_auth=None) is True +def _retry_gate_server(auth_type: MCPAuthType) -> MCPServer: + return MCPServer( + server_id="retry-gate", + name="retry-gate-server", + url="https://up.example.com", + transport=MCPTransport.http, + auth_type=auth_type, + ) + + +def test_obo_retry_applies_to_id_jag_without_an_inbound_subject_token(): + """ID-JAG can source its subject from the user's stored SSO assertion, so the upstream-401 + invalidate-and-retry path must engage even when the caller presented no token of its own; + otherwise a store-sourced bearer is replayed until its TTL after being rejected.""" + assert _obo_retry_applies(_retry_gate_server(MCPAuth.oauth2_id_jag), None) is True + assert _obo_retry_applies(_retry_gate_server(MCPAuth.oauth2_id_jag), "inbound-id-token") is True + + +def test_obo_retry_still_requires_a_subject_token_for_token_exchange(): + """token_exchange can only mint from an inbound token, so with none there is nothing to re-mint.""" + assert _obo_retry_applies(_retry_gate_server(MCPAuth.oauth2_token_exchange), None) is False + assert _obo_retry_applies(_retry_gate_server(MCPAuth.oauth2_token_exchange), "inbound-token") is True + + +def test_obo_retry_does_not_apply_to_other_auth_modes(): + for auth_type in (MCPAuth.none, MCPAuth.api_key, MCPAuth.oauth2, MCPAuth.true_passthrough): + assert _obo_retry_applies(_retry_gate_server(auth_type), "some-token") is False + + class _UpstreamAuthError(Exception): """Mimics a wrapped upstream 401 the way _extract_upstream_auth_failure detects it.""" diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index 4cb3ebe7e16..568b8c9c395 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -794,6 +794,11 @@ "count": 1 } }, + "src/app/(dashboard)/mcp-servers/_components/IdJagFormFields.tsx": { + "no-restricted-imports": { + "count": 1 + } + }, "src/app/(dashboard)/mcp-servers/_components/MCPLogoSelector.test.tsx": { "unused-imports/no-unused-imports": { "count": 1 diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/IdJagFormFields.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/IdJagFormFields.tsx new file mode 100644 index 00000000000..e8730a5b974 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/IdJagFormFields.tsx @@ -0,0 +1,158 @@ +import React from "react"; +import { Form, Input, Select, Tooltip } from "antd"; +import { InfoCircleOutlined } from "@ant-design/icons"; + +interface IdJagFormFieldsProps { + isEditing?: boolean; +} + +const fieldClassName = "rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"; + +const FieldLabel: React.FC<{ label: string; tooltip: string }> = ({ label, tooltip }) => ( + + {label} + + + + +); + +const IdJagFormFields: React.FC = ({ isEditing = false }) => { + const placeholderSuffix = isEditing ? " (leave blank to keep existing)" : ""; + + return ( + <> + + } + name="token_exchange_endpoint" + rules={[{ required: !isEditing, message: "The org token endpoint is required for ID-JAG" }]} + > + + + + } + name={["credentials", "id_jag_resource_token_endpoint"]} + rules={[{ required: !isEditing, message: "The resource token endpoint is required for ID-JAG" }]} + > + + + } + name={["credentials", "client_id"]} + rules={[{ required: !isEditing, message: "Client ID is required for ID-JAG" }]} + > + + + + } + name={["credentials", "client_secret"]} + dependencies={[["credentials", "client_private_key"]]} + rules={[ + ({ getFieldValue }) => ({ + validator: (_, value) => { + if (isEditing || value || getFieldValue(["credentials", "client_private_key"])) { + return Promise.resolve(); + } + return Promise.reject(new Error("Provide either a client secret or a client private key")); + }, + }), + ]} + > + + + + } + name={["credentials", "client_private_key"]} + > + + + + } + name={["credentials", "client_private_key_id"]} + > + + + + } + name={["credentials", "client_assertion_signing_alg"]} + > + + + + } + name="audience" + > + + + + } + name={["credentials", "id_jag_resource"]} + > + + + + } + name="subject_token_type" + > + + + } + name={["credentials", "scopes"]} + > + + + Date: Sat, 1 Aug 2026 11:43:36 -0700 Subject: [PATCH 57/92] feat(proxy): let AI API keys read /model/info Keys created with key_type=llm_api get allowed_routes=["llm_api_routes"], which covered /v1/models but not /v1/model/info, so a client could list model names but not read pricing, mode, or max_tokens without a second key. Adds both /model/info and /v1/model/info (same handler) to llm_api_routes only. Membership there is not the same as RouteChecks.is_llm_api_route(), which is what gates DISABLE_LLM_API_ENDPOINTS, global/virtual-key budget enforcement, enforce_user_param and JWT team attachment; /guardrails/apply_guardrail already sits in the group the same way. /v2/model/info stays out: it is the paginated Admin UI listing, not model metadata a caller needs at request time. public_routes moves from set([...]) to a frozenset literal to keep the LIT002 and ruff-strict ceilings from rising; both budgets ratchet down by one. --- litellm/proxy/_types.py | 12 +++-- ruff-strict-budget.json | 2 +- .../proxy/auth/test_route_checks.py | 54 +++++++++++++++++++ type-discipline-budget.json | 2 +- 4 files changed, 65 insertions(+), 5 deletions(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 23fe7af9994..e8355f4941a 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -517,6 +517,11 @@ class LiteLLMRoutes(enum.Enum): "/guardrails/apply_guardrail", ] + model_info_routes = [ + "/model/info", + "/v1/model/info", + ] + llm_api_routes = ( openai_routes + anthropic_routes @@ -527,6 +532,7 @@ class LiteLLMRoutes(enum.Enum): + mcp_inference_routes + litellm_native_routes + agent_routes + + model_info_routes ) info_routes = [ "/key/info", @@ -653,8 +659,8 @@ class LiteLLMRoutes(enum.Enum): "/global/spend/all_tag_names", ] - public_routes = set( - [ + public_routes = frozenset( + ( "/routes", "/", "/health/liveliness", @@ -669,7 +675,7 @@ class LiteLLMRoutes(enum.Enum): "/public/mcp_hub", "/public/skill_hub", "/public/litellm_model_cost_map", - ] + ) ) # Retained for backwards compatibility with JWT auth configs that reference diff --git a/ruff-strict-budget.json b/ruff-strict-budget.json index b8650eea7aa..57f69dfffe7 100644 --- a/ruff-strict-budget.json +++ b/ruff-strict-budget.json @@ -69,7 +69,7 @@ "limit": 4 }, "C405": { - "limit": 23 + "limit": 22 }, "C408": { "limit": 14 diff --git a/tests/test_litellm/proxy/auth/test_route_checks.py b/tests/test_litellm/proxy/auth/test_route_checks.py index 06764139eda..1426876783b 100644 --- a/tests/test_litellm/proxy/auth/test_route_checks.py +++ b/tests/test_litellm/proxy/auth/test_route_checks.py @@ -445,6 +445,60 @@ def test_virtual_key_llm_api_routes_allows_mcp_inference_endpoints(route, method assert result is True +@pytest.mark.parametrize("route", ["/model/info", "/v1/model/info"]) +def test_virtual_key_llm_api_routes_allows_model_info(route): + """AI API virtual keys must be able to read model metadata (pricing, mode, + max_tokens) for the deployments they can already route to. Both the + unversioned and /v1 paths are the same handler, so both must be reachable. + """ + + valid_token = UserAPIKeyAuth( + user_id="test_user", + allowed_routes=["llm_api_routes"], + ) + + result = RouteChecks.is_virtual_key_allowed_to_call_route( + route=route, + valid_token=valid_token, + request=_mock_request("GET"), + ) + + assert result is True + + +@pytest.mark.parametrize("route", ["/model/info", "/v1/model/info"]) +def test_model_info_not_classified_as_llm_api(route): + """Membership in `llm_api_routes` must not promote /model/info to an + `is_llm_api_route()`. That predicate gates DISABLE_LLM_API_ENDPOINTS, + global/virtual-key budget enforcement, enforce_user_param and the JWT + x-litellm-team-id attachment; model metadata is a free read and must stay + outside all of them. + """ + + assert RouteChecks.is_llm_api_route(route=route) is False + + +@pytest.mark.parametrize("route", ["/v2/model/info", "/model_group/info"]) +def test_virtual_key_llm_api_routes_denies_other_model_info_routes(route): + """The grant is scoped to the two /model/info paths. The paginated Admin UI + listing and the model-group endpoint stay outside it. + """ + + valid_token = UserAPIKeyAuth( + user_id="test_user", + allowed_routes=["llm_api_routes"], + ) + + with pytest.raises(HTTPException) as exc_info: + RouteChecks.is_virtual_key_allowed_to_call_route( + route=route, + valid_token=valid_token, + request=_mock_request("GET"), + ) + + assert exc_info.value.status_code == 403 + + def test_spend_logs_v2_classified_as_management_not_llm_api(): """Paginated spend logs are a management/spend read route, not an LLM API.""" diff --git a/type-discipline-budget.json b/type-discipline-budget.json index ff037a2872e..b790d9acb0f 100644 --- a/type-discipline-budget.json +++ b/type-discipline-budget.json @@ -3,7 +3,7 @@ "limit": 23191 }, "LIT002": { - "limit": 27276 + "limit": 27275 }, "LIT003": { "limit": 292 From a23aa47e1e9842c06ca52245b4a4751850f244b4 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 11:52:27 -0700 Subject: [PATCH 58/92] feat(prometheus): add global exclude_metrics and exclude_labels options (#34201) * feat(prometheus): add global exclude_metrics and exclude_labels options Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(prometheus): apply global exclude_labels to hard-coded metric labels Metrics built with hard-coded labelnames lists (guardrail, provider budget, callback, managed file/batch, batch cost) bypassed prometheus_exclude_labels because only labels resolved via get_labels_for_metric were filtered. Route every metric through a factory that strips excluded labels at construction and proxies labels() so excluded labels are dropped at emission too. Add the non-enum hard-coded labels (guardrail_name, status, error_type, hook_type, purpose, file_type, result) to exclude-config validation so they are accepted instead of raising ValueError at logger init. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * refactor(prometheus): simplify exclude-label factory to the kwargs labelnames path All metric definitions pass labelnames as a keyword argument, and the only metrics that pass it positionally resolve their labels through get_labels_for_metric, which already drops excluded labels, so they never carry an excluded label into the factory. Drop the unreachable positional reconstruction branch. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * chore: re-trigger CI (flaky unrelated bedrock agentcore test) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(prometheus): use immutable constructions to satisfy LIT002 budget Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: shivam Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/__init__.py | 2 + litellm/integrations/prometheus.py | 113 +++++++-- .../integrations/test_prometheus.py | 238 ++++++++++++++++++ 3 files changed, 338 insertions(+), 15 deletions(-) diff --git a/litellm/__init__.py b/litellm/__init__.py index 3f8c742c5a2..a9a78846fa1 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -449,6 +449,8 @@ enable_end_user_cost_tracking_prometheus_only: Optional[bool] = None custom_prometheus_metadata_labels: List[str] = [] custom_prometheus_tags: List[str] = [] prometheus_metrics_config: Optional[List] = None +prometheus_exclude_metrics: Optional[List[str]] = None +prometheus_exclude_labels: Optional[List[str]] = None prometheus_emit_stream_label: bool = False # Opt-in: emit `rate_limit_category` and `rate_limit_type` labels on # `litellm_proxy_failed_requests_metric`. Off by default to preserve the diff --git a/litellm/integrations/prometheus.py b/litellm/integrations/prometheus.py index 41a4d026fe1..1bc7308f7df 100644 --- a/litellm/integrations/prometheus.py +++ b/litellm/integrations/prometheus.py @@ -64,11 +64,48 @@ from litellm.types.utils import ( if TYPE_CHECKING: from apscheduler.schedulers.asyncio import AsyncIOScheduler + from prometheus_client.metrics import MetricWrapperBase else: AsyncIOScheduler = Any _DEFAULT_BUDGET_METRICS_PER_REQUEST_TIMEOUT = 5.0 +_NON_ENUM_METRIC_LABELS: frozenset[str] = frozenset( + ( + "guardrail_name", + "status", + "error_type", + "hook_type", + "purpose", + "file_type", + "result", + ) +) + + +class _ExcludedLabelMetric: + """Proxies a prometheus metric whose declared ``labelnames`` had globally + excluded labels removed, dropping those labels from every ``labels(...)`` + call so the emitted arguments always match the metric's real label set.""" + + def __init__( + self, + metric: MetricWrapperBase, + original_labelnames: tuple[str, ...], + excluded_labels: frozenset[str], + ) -> None: + self._metric = metric + self._original_labelnames = original_labelnames + self._excluded_labels = excluded_labels + + def labels(self, *labelvalues: str, **labelkwargs: str) -> MetricWrapperBase: + values = labelvalues or tuple(labelkwargs[name] for name in self._original_labelnames) + kept_values = tuple( + value for name, value in zip(self._original_labelnames, values) if name not in self._excluded_labels + ) + return self._metric.labels(*kept_values) if kept_values else self._metric + + # Tiers a caller may name in a request, across the providers that accept the # parameter: OpenAI ("auto", "default", "flex", "priority", "scale"), Bedrock and # Groq (subsets of those), Anthropic ("auto", "standard_only") and Vertex, which @@ -122,6 +159,8 @@ class PrometheusLogger(CustomLogger): # Always initialize label_filters, even for non-premium users self.label_filters = self._parse_prometheus_config() + self.exclude_metrics, self.exclude_labels = self._parse_exclude_config() + # Cache resolved label sets per metric. Several entries in # ``PrometheusMetricLabels.get_labels`` read module-level toggles # (e.g. ``litellm.prometheus_emit_stream_label``, @@ -696,6 +735,44 @@ class PrometheusLogger(CustomLogger): self._pretty_print_prometheus_config(label_filters) return label_filters + def _parse_exclude_config(self) -> tuple[frozenset[str], frozenset[str]]: + """Parse and validate the global ``exclude_metrics`` / ``exclude_labels`` settings.""" + from typing import get_args + + import litellm + + exclude_metrics = frozenset(litellm.prometheus_exclude_metrics or ()) + exclude_labels = frozenset(litellm.prometheus_exclude_labels or ()) + + valid_metrics = frozenset(get_args(DEFINED_PROMETHEUS_METRICS)) + invalid_metrics = sorted(exclude_metrics - valid_metrics) + + valid_labels = self._all_defined_labels() + invalid_labels = sorted(exclude_labels - valid_labels) + + errors = ( + *(f"Invalid metric name in prometheus_exclude_metrics: {metric}" for metric in invalid_metrics), + *(f"Invalid label name in prometheus_exclude_labels: {label}" for label in invalid_labels), + ) + if errors: + raise ValueError("Prometheus exclude configuration validation failed:\n" + "\n".join(errors)) + + return exclude_metrics, exclude_labels + + @staticmethod + def _all_defined_labels() -> frozenset[str]: + """Every label a metric can emit: enum labels, hard-coded labels, and configured custom labels / tags.""" + import litellm + + builtin_labels = frozenset(label.value for label in UserAPIKeyLabelNames) + custom_metadata_labels = frozenset( + _sanitize_prometheus_label_name(label) for label in litellm.custom_prometheus_metadata_labels + ) + custom_tag_labels = frozenset( + _sanitize_prometheus_label_name(f"tag_{tag}") for tag in litellm.custom_prometheus_tags + ) + return builtin_labels | _NON_ENUM_METRIC_LABELS | custom_metadata_labels | custom_tag_labels + def _validate_all_configurations(self, parsed_configs: List) -> ValidationResults: """Validate all metric configurations and return collected errors""" metric_errors = [] @@ -1015,6 +1092,9 @@ class PrometheusLogger(CustomLogger): def _is_metric_enabled(self, metric_name: str) -> bool: """Check if a metric is enabled based on configuration""" + if metric_name in self.exclude_metrics: + return False + # If no specific configuration is provided, enable all metrics (default behavior) if not hasattr(self, "enabled_metrics"): return True @@ -1032,11 +1112,18 @@ class PrometheusLogger(CustomLogger): # Extract metric name from the first argument or 'name' keyword argument metric_name = args[0] if args else kwargs.get("name", "") - if self._is_metric_enabled(metric_name): - return metric_class(*args, **kwargs) - else: + if not self._is_metric_enabled(metric_name): return NoOpMetric() + original_labelnames = tuple(kwargs.get("labelnames") or ()) + if not (frozenset(original_labelnames) & self.exclude_labels): + return metric_class(*args, **kwargs) + + kept = tuple(name for name in original_labelnames if name not in self.exclude_labels) + kept_kwargs = {**kwargs, "labelnames": kept} # mutable-ok: ** needs a mapping to override labelnames + real_metric = metric_class(*args, **kept_kwargs) + return _ExcludedLabelMetric(real_metric, original_labelnames, self.exclude_labels) + return factory def get_labels_for_metric(self, metric_name: DEFINED_PROMETHEUS_METRICS) -> List[str]: @@ -1059,19 +1146,15 @@ class PrometheusLogger(CustomLogger): # Get default labels for this metric from PrometheusMetricLabels default_labels = PrometheusMetricLabels.get_labels(metric_name) - # If no label filtering is configured for this metric, use default labels - if metric_name not in self.label_filters: - self._cached_metric_labels[metric_name] = default_labels - return default_labels + resolved_labels = [ + label + for label in default_labels + if label not in self.exclude_labels + and (metric_name not in self.label_filters or label in self.label_filters[metric_name]) + ] - # Get configured labels for this metric - configured_labels = self.label_filters[metric_name] - - # Return intersection of configured and default labels to ensure we only use valid labels - filtered_labels = [label for label in default_labels if label in configured_labels] - - self._cached_metric_labels[metric_name] = filtered_labels - return filtered_labels + self._cached_metric_labels[metric_name] = resolved_labels + return resolved_labels @staticmethod def _guardrail_is_additive(info: StandardLoggingGuardrailInformation) -> bool: diff --git a/tests/enterprise/litellm_enterprise/integrations/test_prometheus.py b/tests/enterprise/litellm_enterprise/integrations/test_prometheus.py index ebea96e2152..0cd6055e09d 100644 --- a/tests/enterprise/litellm_enterprise/integrations/test_prometheus.py +++ b/tests/enterprise/litellm_enterprise/integrations/test_prometheus.py @@ -477,6 +477,244 @@ def test_valid_configuration_passes_validation(): # ============================================================================== +# ============================================================================== +# GLOBAL EXCLUDE TESTS - exclude_metrics / exclude_labels +# ============================================================================== + + +@pytest.fixture +def reset_prometheus_exclude_settings(): + """Restore the global exclude settings after each test so they don't leak.""" + prev_metrics = litellm.prometheus_exclude_metrics + prev_labels = litellm.prometheus_exclude_labels + prev_config = litellm.prometheus_metrics_config + try: + yield + finally: + litellm.prometheus_exclude_metrics = prev_metrics + litellm.prometheus_exclude_labels = prev_labels + litellm.prometheus_metrics_config = prev_config + + +def test_exclude_metrics_disables_only_listed_metrics(reset_prometheus_exclude_settings): + """A metric named in exclude_metrics becomes a NoOpMetric; others stay real.""" + from litellm.types.integrations.prometheus import NoOpMetric + + clear_prometheus_registry() + litellm.prometheus_metrics_config = None + litellm.prometheus_exclude_labels = None + litellm.prometheus_exclude_metrics = [ + "litellm_spend_metric", + "litellm_input_tokens_metric", + ] + + logger = PrometheusLogger() + + assert isinstance(logger.litellm_spend_metric, NoOpMetric) + assert isinstance(logger.litellm_input_tokens_metric, NoOpMetric) + # A metric not in the exclude list is still a real prometheus metric + assert not isinstance(logger.litellm_output_tokens_metric, NoOpMetric) + + +def test_exclude_metrics_wins_over_include_config(reset_prometheus_exclude_settings): + """exclude_metrics removes a metric even if an include-based group enabled it.""" + from litellm.types.integrations.prometheus import NoOpMetric + + clear_prometheus_registry() + litellm.prometheus_exclude_labels = None + litellm.prometheus_metrics_config = [ + { + "group": "tokens", + "metrics": ["litellm_input_tokens_metric", "litellm_output_tokens_metric"], + } + ] + litellm.prometheus_exclude_metrics = ["litellm_input_tokens_metric"] + + logger = PrometheusLogger() + + assert isinstance(logger.litellm_input_tokens_metric, NoOpMetric) + assert not isinstance(logger.litellm_output_tokens_metric, NoOpMetric) + + +def test_exclude_labels_dropped_globally(reset_prometheus_exclude_settings): + """exclude_labels removes the label from every metric that would emit it.""" + clear_prometheus_registry() + litellm.prometheus_metrics_config = None + litellm.prometheus_exclude_metrics = None + litellm.prometheus_exclude_labels = ["hashed_api_key", "api_key_alias"] + + logger = PrometheusLogger() + + for metric_name in ("litellm_spend_metric", "litellm_input_tokens_metric"): + labels = logger.get_labels_for_metric(metric_name) + assert "hashed_api_key" not in labels + assert "api_key_alias" not in labels + # Other default labels remain + assert "team" in labels + + +def test_exclude_labels_intersect_with_include_labels(reset_prometheus_exclude_settings): + """exclude_labels is applied on top of an include-based label filter.""" + clear_prometheus_registry() + litellm.prometheus_exclude_metrics = None + litellm.prometheus_metrics_config = [ + { + "group": "spend", + "metrics": ["litellm_spend_metric"], + "include_labels": ["hashed_api_key", "team", "api_provider"], + } + ] + litellm.prometheus_exclude_labels = ["hashed_api_key"] + + logger = PrometheusLogger() + + labels = logger.get_labels_for_metric("litellm_spend_metric") + assert "hashed_api_key" not in labels + assert set(labels) == {"team", "api_provider"} + + +def test_no_exclude_settings_is_backward_compatible(reset_prometheus_exclude_settings): + """With no exclude settings, all metrics and default labels are preserved.""" + from litellm.types.integrations.prometheus import NoOpMetric + + clear_prometheus_registry() + litellm.prometheus_metrics_config = None + litellm.prometheus_exclude_metrics = None + litellm.prometheus_exclude_labels = None + + logger = PrometheusLogger() + + assert logger.exclude_metrics == frozenset() + assert logger.exclude_labels == frozenset() + assert not isinstance(logger.litellm_spend_metric, NoOpMetric) + default_labels = PrometheusMetricLabels.get_labels("litellm_spend_metric") + assert logger.get_labels_for_metric("litellm_spend_metric") == default_labels + + +def test_invalid_exclude_metric_name_raises(reset_prometheus_exclude_settings): + """An unknown metric name in exclude_metrics fails fast at logger init.""" + clear_prometheus_registry() + litellm.prometheus_metrics_config = None + litellm.prometheus_exclude_labels = None + litellm.prometheus_exclude_metrics = ["not_a_real_metric"] + + with pytest.raises(ValueError) as exc_info: + PrometheusLogger() + + assert "not_a_real_metric" in str(exc_info.value) + assert "prometheus_exclude_metrics" in str(exc_info.value) + + +def test_invalid_exclude_label_name_raises(reset_prometheus_exclude_settings): + """An unknown label name in exclude_labels fails fast at logger init.""" + clear_prometheus_registry() + litellm.prometheus_metrics_config = None + litellm.prometheus_exclude_metrics = None + litellm.prometheus_exclude_labels = ["not_a_real_label"] + + with pytest.raises(ValueError) as exc_info: + PrometheusLogger() + + assert "not_a_real_label" in str(exc_info.value) + assert "prometheus_exclude_labels" in str(exc_info.value) + + +@pytest.mark.parametrize( + "hardcoded_label", + ["guardrail_name", "status", "error_type", "hook_type", "purpose", "file_type", "result"], +) +def test_exclude_hardcoded_label_name_is_accepted(reset_prometheus_exclude_settings, hardcoded_label): + """Labels that only appear in hard-coded metric definitions (not UserAPIKeyLabelNames) + are valid exclude targets and must not fail validation at logger init.""" + clear_prometheus_registry() + litellm.prometheus_metrics_config = None + litellm.prometheus_exclude_metrics = None + litellm.prometheus_exclude_labels = [hardcoded_label] + + logger = PrometheusLogger() + + assert hardcoded_label in logger.exclude_labels + + +def test_exclude_labels_dropped_from_hardcoded_metric(reset_prometheus_exclude_settings): + """A metric built with a hard-coded labelnames list drops excluded labels from its + declared label set instead of silently retaining them.""" + clear_prometheus_registry() + litellm.prometheus_metrics_config = None + litellm.prometheus_exclude_metrics = None + litellm.prometheus_exclude_labels = ["guardrail_name"] + + logger = PrometheusLogger() + + labelnames = logger.litellm_guardrail_latency_metric._metric._labelnames + assert "guardrail_name" not in labelnames + assert set(labelnames) == {"status", "error_type", "hook_type"} + + +def test_hardcoded_metric_emission_omits_excluded_label(reset_prometheus_exclude_settings): + """Emitting a hard-coded metric with the excluded label still passed keeps the emission + working and the excluded label never reaches the scrape output.""" + from prometheus_client import generate_latest + + clear_prometheus_registry() + litellm.prometheus_metrics_config = None + litellm.prometheus_exclude_metrics = None + litellm.prometheus_exclude_labels = ["guardrail_name"] + + logger = PrometheusLogger() + logger.litellm_guardrail_latency_metric.labels( + guardrail_name="my_guardrail", + status="success", + error_type="", + hook_type="pre_call", + ).observe(0.25) + + scrape = generate_latest(REGISTRY).decode() + assert "litellm_guardrail_latency_seconds_bucket" in scrape + assert "my_guardrail" not in scrape + assert 'guardrail_name="' not in scrape + assert 'status="success"' in scrape + + +def test_exclude_only_hardcoded_label_drops_all_labels(reset_prometheus_exclude_settings): + """Excluding the sole label of a hard-coded metric leaves it label-less and still emittable + via both keyword and positional labels() calls.""" + clear_prometheus_registry() + litellm.prometheus_metrics_config = None + litellm.prometheus_exclude_metrics = None + litellm.prometheus_exclude_labels = ["result", "api_provider"] + + logger = PrometheusLogger() + + assert logger.litellm_managed_file_deleted_total._metric._labelnames == () + assert logger.litellm_provider_remaining_budget_metric._metric._labelnames == () + + logger.litellm_managed_file_deleted_total.labels(result="blocked").inc() + logger.litellm_provider_remaining_budget_metric.labels("anthropic").set(5.0) + + +def test_exclude_labels_does_not_touch_unrelated_metrics(reset_prometheus_exclude_settings): + """A metric that never declares the excluded label is left as a plain prometheus metric, + not wrapped, so no behavior changes for it.""" + from litellm.integrations.prometheus import _ExcludedLabelMetric + + clear_prometheus_registry() + litellm.prometheus_metrics_config = None + litellm.prometheus_exclude_metrics = None + litellm.prometheus_exclude_labels = ["guardrail_name"] + + logger = PrometheusLogger() + + assert not isinstance(logger.litellm_spend_metric, _ExcludedLabelMetric) + assert not isinstance(logger.litellm_provider_remaining_budget_metric, _ExcludedLabelMetric) + assert isinstance(logger.litellm_guardrail_latency_metric, _ExcludedLabelMetric) + + +# ============================================================================== +# END GLOBAL EXCLUDE TESTS +# ============================================================================== + + # ============================================================================== # SEMANTIC VALIDATION TESTS - Detect logical errors in metric increments # ============================================================================== From 334805990c321d90eda4071d7cbf6e164fdf4c8f Mon Sep 17 00:00:00 2001 From: Shivam Rawat Date: Sat, 1 Aug 2026 11:56:18 -0700 Subject: [PATCH 59/92] fix(rate-limit): block check-only counters at the limit and scope tpm reservation to tokens Aligns the fix with the constraints in LIT-4800. A zero-increment counter now blocks at current >= limit, matching RPM's semantics; the previous current > limit let a pool sitting exactly at its reservation admit one extra request. reserve_tpm_tokens rebuilds its descriptors with only tokens_per_unit so the requests dimension stays out of the reservation pass, which deliberately leaves RPM to the separate should_rate_limit check. --- .../hooks/parallel_request_limiter_v3.py | 26 +++++++++-- .../hooks/test_parallel_request_limiter_v3.py | 43 ++++++++++++++++--- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/litellm/proxy/hooks/parallel_request_limiter_v3.py b/litellm/proxy/hooks/parallel_request_limiter_v3.py index 89559ff72ae..3e7edd8cd06 100644 --- a/litellm/proxy/hooks/parallel_request_limiter_v3.py +++ b/litellm/proxy/hooks/parallel_request_limiter_v3.py @@ -148,7 +148,13 @@ for i = 1, descriptor_count do current_counter = tonumber(redis.call('GET', counter_key) or 0) end - if current_counter + increment > limit then + local blocked + if increment > 0 then + blocked = current_counter + increment > limit + else + blocked = current_counter >= limit + end + if blocked then return { 1, i, current_counter, limit } end @@ -1534,7 +1540,12 @@ class _PROXY_MaxParallelRequestsHandler_v3(CustomLogger): or 0 ) ) - if current_counter + meta["increment"] > meta["current_limit"]: + over_limit = ( + current_counter + meta["increment"] > meta["current_limit"] + if meta["increment"] > 0 + else current_counter >= meta["current_limit"] + ) + if over_limit: return RateLimitResponse( overall_code="OVER_LIMIT", statuses=[ @@ -1596,7 +1607,16 @@ class _PROXY_MaxParallelRequestsHandler_v3(CustomLogger): shared primitive. """ tpm_descriptors: List[RateLimitDescriptor] = [ - d for d in descriptors if (d.get("rate_limit") or {}).get("tokens_per_unit") is not None + RateLimitDescriptor( + key=d["key"], + value=d["value"], + rate_limit=RateLimitDescriptorRateLimitObject( + tokens_per_unit=(d.get("rate_limit") or {}).get("tokens_per_unit"), + window_size=(d.get("rate_limit") or {}).get("window_size"), + ), + ) + for d in descriptors + if (d.get("rate_limit") or {}).get("tokens_per_unit") is not None ] if not tpm_descriptors: return RateLimitResponse(overall_code="OK", statuses=[]) diff --git a/tests/test_litellm/proxy/hooks/test_parallel_request_limiter_v3.py b/tests/test_litellm/proxy/hooks/test_parallel_request_limiter_v3.py index 7b3f00a55a8..1c29c287c3a 100644 --- a/tests/test_litellm/proxy/hooks/test_parallel_request_limiter_v3.py +++ b/tests/test_litellm/proxy/hooks/test_parallel_request_limiter_v3.py @@ -5035,17 +5035,20 @@ async def test_atomic_check_with_zero_increment_still_enforces_token_limit(): await handler.async_increment_tokens_with_ttl_preservation( pipeline_operations=[ RedisPipelineIncrementOperation( - key=counter_key, increment_value=150, ttl=60 + key=counter_key, increment_value=100, ttl=60 ) ], ) - over_limit = await handler.atomic_check_and_increment_by_n( + at_limit = await handler.atomic_check_and_increment_by_n( descriptors=[descriptor], increments=[zero_token_increment], ) - assert over_limit["overall_code"] == "OVER_LIMIT" - blocked = over_limit["statuses"][0] + assert at_limit["overall_code"] == "OVER_LIMIT", ( + "a check-only pass must block once recorded usage reaches the limit, " + "matching RPM's >= semantics; > would leak one extra request" + ) + blocked = at_limit["statuses"][0] assert blocked["rate_limit_type"] == "tokens" assert blocked["current_limit"] == 100 @@ -5053,7 +5056,7 @@ async def test_atomic_check_with_zero_increment_still_enforces_token_limit(): await handler.internal_usage_cache.async_get_cache( key=counter_key, litellm_parent_otel_span=None, local_only=True ) - == 150 + == 100 ) negative_increment: Dict[str, int] = {"requests": -1, "tokens": -50} @@ -5067,5 +5070,33 @@ async def test_atomic_check_with_zero_increment_still_enforces_token_limit(): await handler.internal_usage_cache.async_get_cache( key=counter_key, litellm_parent_otel_span=None, local_only=True ) - == 150 + == 100 ) + + +@pytest.mark.asyncio +async def test_reserve_tpm_tokens_never_evaluates_the_requests_dimension(): + """The reservation path deliberately leaves RPM to the separate + should_rate_limit pass. Now that zero-increment counters are checked + instead of skipped, reserve_tpm_tokens must strip requests_per_unit from + its descriptors or an exhausted RPM budget would double-enforce here.""" + from litellm.proxy.hooks.parallel_request_limiter_v3 import RateLimitDescriptor + + handler = _PROXY_MaxParallelRequestsHandler( + internal_usage_cache=InternalUsageCache(DualCache()) + ) + descriptor = RateLimitDescriptor( + key="api_key", + value="reserve-test-key", + rate_limit={"requests_per_unit": 0, "tokens_per_unit": 1000, "window_size": 60}, + ) + + response = await handler.reserve_tpm_tokens( + descriptors=[descriptor], + estimated_tokens=10, + ) + assert response["overall_code"] == "OK", ( + "an exhausted requests budget (limit 0) must not block the token " + f"reservation pass, got: {response}" + ) + assert [s["rate_limit_type"] for s in response["statuses"]] == ["tokens"] From 37ca659f847592f4a7be6131014c3d1433ec2935 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Sat, 1 Aug 2026 11:58:49 -0700 Subject: [PATCH 60/92] ci: run unit tests on pushes to main and litellm_internal_staging The unit workflows trigger on pull_request only, so a commit that actually lands on a gated branch ends up with no unit-test check runs at all. The commit status API reports success for those commits, which a release gate reads as "nothing failed" rather than "never tested". PR checks also only ever ran against the merge preview, not the commit that landed, so two branches that are each green can still land broken together Add a push trigger on the two gated branches to the twelve unit workflows and to the code-quality workflow, so every landed commit gets check runs addressable by its SHA test-linting.yml is deliberately left on pull_request only; six of its steps gate on a diff against github.event.pull_request.base.sha, which is empty outside a pull request, and a "what did this branch add" check has no meaning on a merge commit Also key the concurrency group on github.sha and restrict cancel-in-progress to pull_request. The previous group was stable across pushes to a branch, so consecutive merges would cancel the in-flight run for the earlier commit and leave that SHA without a result, which is the same blind spot this change is meant to close --- .github/workflows/test-code-quality.yml | 8 ++++++-- .github/workflows/test-unit-core-utils.yml | 8 ++++++-- .github/workflows/test-unit-documentation.yml | 8 ++++++-- .github/workflows/test-unit-enterprise-routing.yml | 8 ++++++-- .github/workflows/test-unit-integrations.yml | 8 ++++++-- .github/workflows/test-unit-llm-providers.yml | 8 ++++++-- .github/workflows/test-unit-misc.yml | 8 ++++++-- .github/workflows/test-unit-proxy-auth.yml | 8 ++++++-- .github/workflows/test-unit-proxy-db.yml | 8 ++++++-- .github/workflows/test-unit-proxy-endpoints.yml | 8 ++++++-- .github/workflows/test-unit-proxy-infra.yml | 8 ++++++-- .github/workflows/test-unit-proxy-legacy.yml | 8 ++++++-- .github/workflows/test-unit-responses-caching-types.yml | 8 ++++++-- 13 files changed, 78 insertions(+), 26 deletions(-) diff --git a/.github/workflows/test-code-quality.yml b/.github/workflows/test-code-quality.yml index ae31395521a..fab05fc2bbb 100644 --- a/.github/workflows/test-code-quality.yml +++ b/.github/workflows/test-code-quality.yml @@ -7,13 +7,17 @@ on: - litellm_internal_staging - litellm_oss_staging - "litellm_**" + push: + branches: + - main + - litellm_internal_staging permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: code-quality: diff --git a/.github/workflows/test-unit-core-utils.yml b/.github/workflows/test-unit-core-utils.yml index d6d6353238f..a01f09559c6 100644 --- a/.github/workflows/test-unit-core-utils.yml +++ b/.github/workflows/test-unit-core-utils.yml @@ -7,6 +7,10 @@ on: - litellm_internal_staging - litellm_oss_staging - "litellm_**" + push: + branches: + - main + - litellm_internal_staging permissions: contents: read @@ -14,8 +18,8 @@ permissions: pull-requests: write concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: core-utils: diff --git a/.github/workflows/test-unit-documentation.yml b/.github/workflows/test-unit-documentation.yml index c12a289ce9f..50589cb5926 100644 --- a/.github/workflows/test-unit-documentation.yml +++ b/.github/workflows/test-unit-documentation.yml @@ -7,13 +7,17 @@ on: - litellm_internal_staging - litellm_oss_staging - "litellm_**" + push: + branches: + - main + - litellm_internal_staging permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: documentation: diff --git a/.github/workflows/test-unit-enterprise-routing.yml b/.github/workflows/test-unit-enterprise-routing.yml index 13136c968d1..a64f00f4744 100644 --- a/.github/workflows/test-unit-enterprise-routing.yml +++ b/.github/workflows/test-unit-enterprise-routing.yml @@ -7,6 +7,10 @@ on: - litellm_internal_staging - litellm_oss_staging - "litellm_**" + push: + branches: + - main + - litellm_internal_staging permissions: contents: read @@ -14,8 +18,8 @@ permissions: pull-requests: write concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: enterprise-routing: diff --git a/.github/workflows/test-unit-integrations.yml b/.github/workflows/test-unit-integrations.yml index c95ed4e7c24..39752cf8e5d 100644 --- a/.github/workflows/test-unit-integrations.yml +++ b/.github/workflows/test-unit-integrations.yml @@ -7,6 +7,10 @@ on: - litellm_internal_staging - litellm_oss_staging - "litellm_**" + push: + branches: + - main + - litellm_internal_staging permissions: contents: read @@ -14,8 +18,8 @@ permissions: pull-requests: write concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: integrations: diff --git a/.github/workflows/test-unit-llm-providers.yml b/.github/workflows/test-unit-llm-providers.yml index df78564ab0c..4d1c921f723 100644 --- a/.github/workflows/test-unit-llm-providers.yml +++ b/.github/workflows/test-unit-llm-providers.yml @@ -7,13 +7,17 @@ on: - litellm_internal_staging - litellm_oss_staging - "litellm_**" + push: + branches: + - main + - litellm_internal_staging permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: vertex-ai: diff --git a/.github/workflows/test-unit-misc.yml b/.github/workflows/test-unit-misc.yml index 9afaaaead93..505e22cfed4 100644 --- a/.github/workflows/test-unit-misc.yml +++ b/.github/workflows/test-unit-misc.yml @@ -7,6 +7,10 @@ on: - litellm_internal_staging - litellm_oss_staging - "litellm_**" + push: + branches: + - main + - litellm_internal_staging permissions: contents: read @@ -14,8 +18,8 @@ permissions: pull-requests: write concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: misc: diff --git a/.github/workflows/test-unit-proxy-auth.yml b/.github/workflows/test-unit-proxy-auth.yml index 97dfaed6e81..c27fe16d611 100644 --- a/.github/workflows/test-unit-proxy-auth.yml +++ b/.github/workflows/test-unit-proxy-auth.yml @@ -7,6 +7,10 @@ on: - litellm_internal_staging - litellm_oss_staging - "litellm_**" + push: + branches: + - main + - litellm_internal_staging permissions: contents: read @@ -14,8 +18,8 @@ permissions: pull-requests: write concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: proxy-auth: diff --git a/.github/workflows/test-unit-proxy-db.yml b/.github/workflows/test-unit-proxy-db.yml index b0ee56f5a5c..60d2e471862 100644 --- a/.github/workflows/test-unit-proxy-db.yml +++ b/.github/workflows/test-unit-proxy-db.yml @@ -7,13 +7,17 @@ on: - litellm_internal_staging - litellm_oss_staging - "litellm_**" + push: + branches: + - main + - litellm_internal_staging permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} # Semantic matrix: each shard groups tests by concern (auth, server, logging, …) # rather than alphabetical letter ranges. Adding a new test file means adding it diff --git a/.github/workflows/test-unit-proxy-endpoints.yml b/.github/workflows/test-unit-proxy-endpoints.yml index b3eb8f79a43..6a51d2a8578 100644 --- a/.github/workflows/test-unit-proxy-endpoints.yml +++ b/.github/workflows/test-unit-proxy-endpoints.yml @@ -7,14 +7,18 @@ on: - litellm_internal_staging - litellm_oss_staging - "litellm_**" + push: + branches: + - main + - litellm_internal_staging workflow_dispatch: permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: proxy-endpoints: diff --git a/.github/workflows/test-unit-proxy-infra.yml b/.github/workflows/test-unit-proxy-infra.yml index 884d62289b9..913653a1711 100644 --- a/.github/workflows/test-unit-proxy-infra.yml +++ b/.github/workflows/test-unit-proxy-infra.yml @@ -7,6 +7,10 @@ on: - litellm_internal_staging - litellm_oss_staging - "litellm_**" + push: + branches: + - main + - litellm_internal_staging permissions: contents: read @@ -14,8 +18,8 @@ permissions: pull-requests: write concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: proxy-infra: diff --git a/.github/workflows/test-unit-proxy-legacy.yml b/.github/workflows/test-unit-proxy-legacy.yml index bcbf365babf..49aa5f9f51d 100644 --- a/.github/workflows/test-unit-proxy-legacy.yml +++ b/.github/workflows/test-unit-proxy-legacy.yml @@ -7,13 +7,17 @@ on: - litellm_internal_staging - litellm_oss_staging - "litellm_**" + push: + branches: + - main + - litellm_internal_staging permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: test: diff --git a/.github/workflows/test-unit-responses-caching-types.yml b/.github/workflows/test-unit-responses-caching-types.yml index 2f177587997..5b336452069 100644 --- a/.github/workflows/test-unit-responses-caching-types.yml +++ b/.github/workflows/test-unit-responses-caching-types.yml @@ -7,6 +7,10 @@ on: - litellm_internal_staging - litellm_oss_staging - "litellm_**" + push: + branches: + - main + - litellm_internal_staging permissions: contents: read @@ -14,8 +18,8 @@ permissions: pull-requests: write concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: responses-caching-types: From 75f520ae243d7893eb7a7b834791d973751e80cd Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Sat, 1 Aug 2026 12:04:26 -0700 Subject: [PATCH 61/92] bump: litellm-enterprise 0.1.52 -> 0.1.53 --- enterprise/pyproject.toml | 4 ++-- pyproject.toml | 2 +- uv.lock | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/enterprise/pyproject.toml b/enterprise/pyproject.toml index fa209e55eb8..5489eba1494 100644 --- a/enterprise/pyproject.toml +++ b/enterprise/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "litellm-enterprise" -version = "0.1.52" +version = "0.1.53" description = "Package for LiteLLM Enterprise features" readme = "README.md" requires-python = ">=3.9" @@ -26,7 +26,7 @@ required-version = ">=0.10.9" module-root = "" [tool.commitizen] -version = "0.1.52" +version = "0.1.53" version_files = [ "pyproject.toml:^version", "../pyproject.toml:litellm-enterprise==", diff --git a/pyproject.toml b/pyproject.toml index 678e7384a05..57394acf8d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ proxy = [ "azure-storage-blob>=12.28.0,<13.0", "mcp>=1.28.1,<2.0", "litellm-proxy-extras==0.4.81", - "litellm-enterprise==0.1.52", + "litellm-enterprise==0.1.53", "RestrictedPython>=8.1,<9.0", "rich>=13.9.4,<14.0", "InquirerPy>=0.3.4,<1.0", diff --git a/uv.lock b/uv.lock index fa7652c67ec..cb9ba896cd7 100644 --- a/uv.lock +++ b/uv.lock @@ -10,7 +10,7 @@ resolution-markers = [ ] [options] -exclude-newer = "2026-07-28T06:59:32.050819Z" +exclude-newer = "2026-07-29T19:04:35.546526Z" exclude-newer-span = "P3D" [manifest] @@ -4505,7 +4505,7 @@ proxy-dev = [ [[package]] name = "litellm-enterprise" -version = "0.1.52" +version = "0.1.53" source = { editable = "enterprise" } [[package]] From c541fb2b7a7b29af5d08e6d609daebe8ba3d5cca Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Sat, 1 Aug 2026 12:16:27 -0700 Subject: [PATCH 62/92] chore(proxy): remove duplicate Sequence import in team endpoints --- litellm/proxy/management_endpoints/team_endpoints.py | 1 - 1 file changed, 1 deletion(-) diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py index 1d4cb277c2e..1efd0a747df 100644 --- a/litellm/proxy/management_endpoints/team_endpoints.py +++ b/litellm/proxy/management_endpoints/team_endpoints.py @@ -22,7 +22,6 @@ from typing import ( Mapping, Optional, Protocol, - Sequence, Tuple, TypeVar, Union, From 704b9da8abdb140956e6f148a85a7a1eeafcb20b Mon Sep 17 00:00:00 2001 From: Yassin Kortam Date: Sat, 1 Aug 2026 12:39:13 -0700 Subject: [PATCH 63/92] fix(a2a): keep config-defined agents registered and accept the documented `agents:` key (#35163) The public A2A guide tells users to declare agents under a top-level `agents:` key, but the proxy only ever read `agent_list:`, so the documented config was silently ignored and GET /v1/agents returned an empty list. Accept `agents` as the documented spelling and keep `agent_list` working for anyone who found it by reading the source. Selection is by key presence, so an explicitly empty `agents: []` is not overridden by leftover legacy entries. Config-defined agents were also dropped on any database-backed gateway: the periodic reload rebuilt the registry from the DB rows plus a module global that was declared and never assigned. The registry now remembers the agents it loaded from config.yaml and replays them on every rebuild. A database row wins a name collision, mirroring how config-declared MCP servers are unioned under the database registry, so name lookups and deregistration keep addressing exactly one agent. Resolves LIT-4978 --- .../proxy/agent_endpoints/agent_registry.py | 43 +++-- litellm/proxy/proxy_server.py | 9 +- .../agent_endpoints/test_agent_registry.py | 167 ++++++++++++++++++ .../proxy/proxy_server/test_proxy_config.py | 130 ++++++++++++++ 4 files changed, 331 insertions(+), 18 deletions(-) diff --git a/litellm/proxy/agent_endpoints/agent_registry.py b/litellm/proxy/agent_endpoints/agent_registry.py index 43139b18162..e0ed076bcfc 100644 --- a/litellm/proxy/agent_endpoints/agent_registry.py +++ b/litellm/proxy/agent_endpoints/agent_registry.py @@ -89,6 +89,7 @@ def agents_table(prisma_client: PrismaClient) -> AgentTableClient: class AgentRegistry: def __init__(self): self.agent_list: list[AgentResponse] = [] + self.config_agents: tuple[AgentConfig, ...] = () def reset_agent_list(self): self.agent_list = [] @@ -117,9 +118,21 @@ class AgentRegistry: return hashlib.sha256(json.dumps(agent_config, sort_keys=True).encode()).hexdigest() def load_agents_from_config(self, agent_config: Sequence[AgentConfig] | None = None): + """ + Register the agents declared in config.yaml and remember them for later rebuilds. + + A config entry is skipped when its ``agent_name`` is already registered, so a + database record always wins over a config entry that reuses its name and the + registry never holds two agents under one name. Enforcing that here rather than + in the caller keeps the guarantee independent of the order the two sources load + in. Passing ``None`` leaves the remembered agents untouched; passing an empty + sequence clears them. + """ if agent_config is None: return None + self.config_agents = tuple(agent_config) + for agent_config_item in agent_config: if not isinstance(agent_config_item, dict): raise ValueError("agent_config must be a list of dictionaries") @@ -129,6 +142,9 @@ class AgentRegistry: if not all([agent_name, agent_card_params]): continue + if any(agent.agent_name == agent_name for agent in self.agent_list): + continue + # create a stable hash id for config item config_hash = self._create_agent_id(agent_config_item) @@ -139,26 +155,29 @@ class AgentRegistry: agent_config: Sequence[AgentConfig] | None = None, db_agents: list[dict[str, Any]] | None = None, ): + """ + Rebuild the registry from the DB rows plus the agents declared in config.yaml. + + ``agent_config`` defaults to the agents remembered by the last + ``load_agents_from_config`` call, so a periodic DB reload does not drop + config-defined agents. + + The DB rows are registered first so that a config entry reusing one of their + names is dropped by ``load_agents_from_config``, mirroring how config-declared + MCP servers are unioned under the database registry. Name lookups and + deregistration both address a single agent, so the registry must never hold two + under one name. + """ self.reset_agent_list() - if agent_config: - for agent_config_item in agent_config: - if not isinstance(agent_config_item, dict): - raise ValueError("agent_config must be a list of dictionaries") - - self.register_agent( - agent_config=AgentResponse( - agent_id=self._create_agent_id(agent_config_item), - **agent_config_item, - ) - ) # type: ignore - if db_agents: for db_agent in db_agents: if not isinstance(db_agent, dict): raise ValueError("db_agents must be a list of dictionaries") self.register_agent(agent_config=AgentResponse(**db_agent)) # type: ignore + + self.load_agents_from_config(agent_config if agent_config is not None else self.config_agents) return self.agent_list ########################################################### diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 9023f5fb23f..26d5b386cdd 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -654,8 +654,6 @@ from fastapi.security import OAuth2PasswordBearer from fastapi.security.api_key import APIKeyHeader from fastapi.staticfiles import StaticFiles -from litellm.types.agents import AgentConfig - # import enterprise folder enterprise_router = APIRouter() try: @@ -2002,7 +2000,6 @@ config_passthrough_endpoints: Optional[List[Dict[str, Any]]] = None log_file = "api_log.json" worker_config = None master_key: Optional[str] = None -config_agents: Optional[List[AgentConfig]] = None otel_logging = False prisma_client: Optional[PrismaClient] = None shared_aiohttp_session: Optional["ClientSession"] = None # Global shared session for connection reuse @@ -5095,8 +5092,8 @@ class ProxyConfig: global_mcp_tool_registry.load_tools_from_config(mcp_tools_config, config_file_path=config_file_path) ## AGENTS - agent_config = config.get("agent_list", None) - if agent_config: + agent_config = config.get("agents", config.get("agent_list", None)) + if agent_config is not None: from litellm.proxy.agent_endpoints.agent_registry import ( global_agent_registry, ) @@ -6839,7 +6836,7 @@ class ProxyConfig: try: db_agents = await AGENT_REGISTRY.get_all_agents_from_db(prisma_client=prisma_client) - AGENT_REGISTRY.load_agents_from_db_and_config(db_agents=db_agents, agent_config=config_agents) + AGENT_REGISTRY.load_agents_from_db_and_config(db_agents=db_agents) except Exception as e: verbose_proxy_logger.exception( "litellm.proxy.proxy_server.py::ProxyConfig:_init_agents_in_db - {}".format(str(e)) diff --git a/tests/test_litellm/proxy/agent_endpoints/test_agent_registry.py b/tests/test_litellm/proxy/agent_endpoints/test_agent_registry.py index ddd9cc09c8e..f506535b5e1 100644 --- a/tests/test_litellm/proxy/agent_endpoints/test_agent_registry.py +++ b/tests/test_litellm/proxy/agent_endpoints/test_agent_registry.py @@ -112,3 +112,170 @@ async def test_update_agent_in_db_preserves_explicit_static_headers_and_extra_he assert update_data["static_headers"] == '{"Authorization": "Bearer xyz"}' assert update_data["extra_headers"] == ["X-Custom-Header"] + + +def test_load_agents_from_db_and_config_retains_previously_loaded_config_agents(): + """ + A DB reload calls this without an explicit agent_config. It resets the + registry, so it must fall back to the agents remembered from config.yaml + instead of dropping them. + """ + registry = AgentRegistry() + registry.load_agents_from_config( + [ + { + "agent_name": "config-agent", + "agent_card_params": _sample_agent_card_params(), + } + ] + ) + + registry.load_agents_from_db_and_config( + db_agents=[ + { + "agent_id": "db-id", + "agent_name": "db-agent", + "agent_card_params": _sample_agent_card_params(), + } + ] + ) + + assert sorted(agent.agent_name for agent in registry.get_agent_list()) == [ + "config-agent", + "db-agent", + ] + + +def test_load_agents_from_db_and_config_skips_incomplete_config_entries(): + """Config entries missing agent_card_params are skipped, not registered half-built.""" + registry = AgentRegistry() + registry.load_agents_from_config([{"agent_name": "no-card"}]) + + registry.load_agents_from_db_and_config(db_agents=None) + + assert registry.get_agent_list() == [] + + +@pytest.mark.parametrize( + "db_agent_names", + [ + ("shared-name", "other-db-agent"), + ("other-db-agent", "shared-name"), + ], + ids=["colliding-db-row-first", "colliding-db-row-last"], +) +def test_load_agents_from_db_and_config_lets_db_rows_win_a_name_collision(db_agent_names): + """ + A config entry that reuses a DB agent's name must not be registered next to it. + + Name lookups and deregistration both address a single agent, so two entries + under one name would shadow the DB row for routing and let a single delete + drop both. Parametrized over both DB row orders because the registry is a + list and a first-match lookup would otherwise pass on ordering luck. + """ + registry = AgentRegistry() + registry.load_agents_from_config( + [ + { + "agent_name": "shared-name", + "agent_card_params": _sample_agent_card_params(), + }, + { + "agent_name": "config-only", + "agent_card_params": _sample_agent_card_params(), + }, + ] + ) + + registry.load_agents_from_db_and_config( + db_agents=[ + { + "agent_id": f"db-id-{name}", + "agent_name": name, + "agent_card_params": _sample_agent_card_params(), + } + for name in db_agent_names + ] + ) + + registered = registry.get_agent_list() + assert sorted(agent.agent_name for agent in registered) == [ + "config-only", + "other-db-agent", + "shared-name", + ] + + shared = registry.get_agent_by_name("shared-name") + assert shared is not None + assert shared.agent_id == "db-id-shared-name", "the DB row must win the shared name, not the config entry" + + registry.deregister_agent("shared-name") + assert [agent.agent_name for agent in registry.get_agent_list() if agent.agent_name == "shared-name"] == [], ( + "deleting the DB agent must not leave a shadowed config duplicate behind" + ) + + +def test_load_agents_from_config_skips_a_name_already_held_by_a_db_agent(): + """ + The one-agent-per-name rule is enforced by the loader, not by the call order. + + A config load that lands on a registry already holding DB rows must not append a + colliding entry, otherwise the duplicate is reachable any time the two sources are + loaded in the other order. + """ + registry = AgentRegistry() + registry.load_agents_from_db_and_config( + db_agents=[ + { + "agent_id": "db-id", + "agent_name": "shared-name", + "agent_card_params": _sample_agent_card_params(), + } + ] + ) + + registry.load_agents_from_config( + [ + { + "agent_name": "shared-name", + "agent_card_params": _sample_agent_card_params(), + } + ] + ) + + assert [agent.agent_id for agent in registry.get_agent_list()] == ["db-id"] + + +def test_load_agents_from_config_registers_one_agent_per_name_within_the_config(): + """Two config entries sharing a name collapse to the first, keeping name lookups unambiguous.""" + registry = AgentRegistry() + registry.load_agents_from_config( + [ + {"agent_name": "dupe", "agent_card_params": _sample_agent_card_params()}, + {"agent_name": "dupe", "agent_card_params": {**_sample_agent_card_params(), "url": "http://second"}}, + ] + ) + + registered = registry.get_agent_list() + assert len(registered) == 1 + assert registered[0].agent_card_params["url"] == "http://localhost" + + +def test_load_agents_from_config_with_an_empty_list_clears_the_remembered_agents(): + """ + An explicitly empty config must forget the previously loaded agents. + + Otherwise the next DB rebuild replays agents the operator removed from config.yaml. + ``None`` still means "no opinion" and leaves them alone. + """ + registry = AgentRegistry() + registry.load_agents_from_config( + [{"agent_name": "removed-agent", "agent_card_params": _sample_agent_card_params()}] + ) + assert registry.config_agents != () + + registry.load_agents_from_config([]) + assert registry.config_agents == () + + registry.load_agents_from_db_and_config(db_agents=None) + assert registry.get_agent_list() == [], "a removed config agent must not come back on the next rebuild" diff --git a/tests/test_litellm/proxy/proxy_server/test_proxy_config.py b/tests/test_litellm/proxy/proxy_server/test_proxy_config.py index 99287b92b8f..4f1efe484ab 100644 --- a/tests/test_litellm/proxy/proxy_server/test_proxy_config.py +++ b/tests/test_litellm/proxy/proxy_server/test_proxy_config.py @@ -2354,3 +2354,133 @@ async def test_ProxyConfig_load_config_redacts_secret_litellm_setting_keeps_plai assert "num_retries=7" in rendered, ( f"non-secret num_retries value was over-redacted; expected it visible in {rendered!r}" ) + + +# --------------------------------------------------------------------------- +# ProxyConfig agents from config.yaml +# --------------------------------------------------------------------------- + + +@pytest.fixture +def clean_agent_registry(): + from litellm.proxy.agent_endpoints.agent_registry import global_agent_registry + + original_agents = list(global_agent_registry.agent_list) + original_config_agents = getattr(global_agent_registry, "config_agents", ()) + global_agent_registry.agent_list = [] + global_agent_registry.config_agents = () + try: + yield global_agent_registry + finally: + global_agent_registry.agent_list = original_agents + global_agent_registry.config_agents = original_config_agents + + +def _config_agent(agent_name: str) -> Dict[str, Any]: + return { + "agent_name": agent_name, + "agent_card_params": { + "name": "Config Agent", + "url": "http://localhost:10001", + "protocolVersion": "1.0", + }, + } + + +class _FakeAgentRow: + """Stand-in for a prisma agent record: supports dict() and .object_permission.""" + + def __init__(self, agent_id: str, agent_name: str) -> None: + self.agent_id = agent_id + self.agent_name = agent_name + self.object_permission = None + self.spend = 0.0 + + def __iter__(self): + return iter( + { + "agent_id": self.agent_id, + "agent_name": self.agent_name, + "agent_card_params": {"name": self.agent_name, "url": "http://db-agent"}, + "litellm_params": {}, + }.items() + ) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("config_key", ["agents", "agent_list"]) +async def test_ProxyConfig__init_non_llm_configs_registers_agents_from_config(clean_agent_registry, config_key): + """The documented ``agents:`` key must register agents, as must the legacy ``agent_list:``.""" + await ProxyConfig()._init_non_llm_configs( + config={config_key: [_config_agent("config-agent")]}, + config_file_path=None, + ) + + assert [agent.agent_name for agent in clean_agent_registry.get_agent_list()] == ["config-agent"] + + +@pytest.mark.asyncio +async def test_ProxyConfig__init_agents_in_db_keeps_config_defined_agents(clean_agent_registry): + """A DB reload rebuilds the registry; config-defined agents must survive it alongside DB rows.""" + await ProxyConfig()._init_non_llm_configs( + config={"agents": [_config_agent("config-agent")]}, + config_file_path=None, + ) + + prisma_client = MagicMock() + prisma_client.db.litellm_agentstable.find_many = AsyncMock(return_value=[_FakeAgentRow("db-id", "db-agent")]) + + await ProxyConfig()._init_agents_in_db(prisma_client=prisma_client) + + assert sorted(agent.agent_name for agent in clean_agent_registry.get_agent_list()) == [ + "config-agent", + "db-agent", + ] + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "config, expected_agent_names", + [ + ({"agents": [], "agent_list": [_config_agent("legacy-agent")]}, []), + ( + { + "agents": [_config_agent("documented-agent")], + "agent_list": [_config_agent("legacy-agent")], + }, + ["documented-agent"], + ), + ({"agent_list": [_config_agent("legacy-agent")]}, ["legacy-agent"]), + ], + ids=["empty-agents-wins", "populated-agents-wins", "agent_list-alone-still-works"], +) +async def test_ProxyConfig__init_non_llm_configs_prefers_agents_key_by_presence( + clean_agent_registry, config, expected_agent_names +): + """ + ``agents`` outranks the legacy ``agent_list`` whenever the key is present. + + Selecting on truthiness instead would silently register the legacy entries + for a config that spells out ``agents: []``. + """ + await ProxyConfig()._init_non_llm_configs(config=config, config_file_path=None) + + assert [agent.agent_name for agent in clean_agent_registry.get_agent_list()] == expected_agent_names + + +@pytest.mark.asyncio +async def test_ProxyConfig__init_non_llm_configs_empty_agents_key_clears_remembered_agents(clean_agent_registry): + """ + An explicitly empty ``agents:`` must reach the registry, not be skipped as falsy. + + Skipping it leaves the previously remembered agents in place, so the next DB + rebuild replays agents the operator deleted from config.yaml. + """ + clean_agent_registry.load_agents_from_config([_config_agent("stale-agent")]) + assert clean_agent_registry.config_agents != () + + await ProxyConfig()._init_non_llm_configs(config={"agents": []}, config_file_path=None) + + assert clean_agent_registry.config_agents == () + clean_agent_registry.load_agents_from_db_and_config(db_agents=None) + assert clean_agent_registry.get_agent_list() == [] From 2fc81243cd48d259faf310fbaa546f769bfc610e Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Sat, 1 Aug 2026 12:41:26 -0700 Subject: [PATCH 64/92] chore: update Next.js build artifacts (2026-08-01 19:41 UTC, node v20.20.2) --- litellm/proxy/_experimental/out/404.html | 2 +- .../proxy/_experimental/out/404/index.html | 2 +- .../out/__next.!KGRhc2hib2FyZCk.__PAGE__.txt | 8 +- .../out/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../proxy/_experimental/out/__next._full.txt | 36 +- .../proxy/_experimental/out/__next._head.txt | 8 +- .../proxy/_experimental/out/__next._index.txt | 14 +- .../proxy/_experimental/out/__next._tree.txt | 4 +- .../_buildManifest.js | 0 .../_clientMiddlewareManifest.js | 0 .../_ssgManifest.js | 0 .../out/_next/static/chunks/00g6xfr4yow7h.js | 1 - .../out/_next/static/chunks/00lwtxl1k_z8t.js | 1 + .../{10-c3gjiv7yt0.js => 011as3ct2u0nu.js} | 4 +- .../out/_next/static/chunks/02-alpsjfp5b7.js | 10 + .../out/_next/static/chunks/025ocjcb8e481.js | 7 - .../out/_next/static/chunks/03c3h-nx-fb3y.js | 1 + .../out/_next/static/chunks/03kaz3d0v3z45.js | 10 + .../out/_next/static/chunks/046q5lwe95zp6.js | 1 - .../out/_next/static/chunks/048wkdcpsnwne.js | 31 ++ .../out/_next/static/chunks/04y2hqzy08peg.js | 1 - .../out/_next/static/chunks/054z32giulaw7.js | 1 - .../out/_next/static/chunks/058j9m4b8p4wx.js | 1 - .../out/_next/static/chunks/05id71gg6oywc.js | 1 - .../out/_next/static/chunks/05ttqlxo9w0ow.js | 1 + .../out/_next/static/chunks/06hxe45fjy7x7.js | 7 + .../out/_next/static/chunks/06q8aep867ss7.js | 1 - .../out/_next/static/chunks/06sx0oh8weeph.js | 8 + .../out/_next/static/chunks/08goggic_ad66.js | 2 - .../out/_next/static/chunks/08k6jolcrw-uw.js | 1 - .../out/_next/static/chunks/08yk0x-hh3ydk.js | 2 + .../out/_next/static/chunks/0965-angwdvwe.js | 1 - .../out/_next/static/chunks/0_mw8gm-qowti.js | 1 - .../out/_next/static/chunks/0_vlfpb8phl0v.js | 1 + .../out/_next/static/chunks/0a-3zns09ja98.js | 10 - .../out/_next/static/chunks/0ak2vacq91c7k.js | 89 ++++ .../out/_next/static/chunks/0ald9tfsbz2e-.js | 7 - .../out/_next/static/chunks/0am68mi9t9cb6.js | 7 - .../{3fqu7hpcrtg67.js => 0b4c0ak_j76h0.js} | 2 +- .../out/_next/static/chunks/0bzsgiuwi1q0e.js | 7 - .../out/_next/static/chunks/0cgtkk6qelb_j.js | 1 - .../out/_next/static/chunks/0cj588tfl8vcq.js | 10 + .../out/_next/static/chunks/0d398pudg-p7u.js | 2 + .../out/_next/static/chunks/0d6y38dwy2fvp.js | 1 - .../out/_next/static/chunks/0dh09yfknpuy3.js | 1 - .../out/_next/static/chunks/0dsiq_ok1yngk.js | 2 - .../out/_next/static/chunks/0e3vwdd43gm8b.js | 1 + .../out/_next/static/chunks/0eamb3kk74kws.js | 7 - .../out/_next/static/chunks/0euektnkbx78f.js | 1 - .../out/_next/static/chunks/0f_bhylcfohcm.js | 1 - .../{3jgsbd_1fz8l8.js => 0gylheyn-59ow.js} | 4 +- .../out/_next/static/chunks/0i25zatajbma2.js | 1 - .../out/_next/static/chunks/0jjbikxye1xv_.js | 1 + .../{3e4fipm_mrl-n.js => 0jl9haj4wjvj5.js} | 2 +- .../out/_next/static/chunks/0k-74wqsm8dzt.js | 13 - .../out/_next/static/chunks/0kap_rdm2-lem.js | 2 - .../{007c8g8hmd9qz.js => 0kpote6n3ff9a.js} | 2 +- .../out/_next/static/chunks/0kx52ovlpa34x.js | 7 + .../out/_next/static/chunks/0lf6n96uy4q27.js | 1 + .../out/_next/static/chunks/0m-x8i06te864.js | 1 - .../{2wzbftaqumx8j.js => 0ma4y3uxzghhw.js} | 5 +- .../out/_next/static/chunks/0maan-7nzqqca.js | 1 - .../out/_next/static/chunks/0nb8zgkq5nq1r.js | 1 - .../out/_next/static/chunks/0nobv49ll5nyv.js | 1 + .../out/_next/static/chunks/0onjeur4drmh-.js | 1 + .../out/_next/static/chunks/0p_yh7pymv-5p.js | 7 - .../out/_next/static/chunks/0q8q-21f1553f.js | 1 + .../out/_next/static/chunks/0qf84m09hg_q8.js | 1 + .../{0if4h9a-qzqx4.js => 0qkqkii3nce2s.js} | 4 +- .../out/_next/static/chunks/0qtmfaeayrb_n.js | 10 - .../{30dm_jeoikihy.js => 0r0okx31djc7i.js} | 4 +- .../out/_next/static/chunks/0r9-qx6h8pzi9.js | 7 + .../out/_next/static/chunks/0sm3ln66e4502.js | 3 + .../out/_next/static/chunks/0taea1jhojoz5.js | 11 - .../out/_next/static/chunks/0vvnul8uez9kj.js | 1 + .../{1t9h71-jh-nt0.js => 0wo6dp1zxhzve.js} | 2 +- .../out/_next/static/chunks/0ww76lz_0cphv.js | 1 - .../out/_next/static/chunks/0xou7v06a6xww.js | 1 - .../out/_next/static/chunks/0y00ve1sk9qox.js | 2 - .../out/_next/static/chunks/0yjg5mjiahhan.js | 1 - .../out/_next/static/chunks/0zduf1gntl_f8.js | 1 - .../out/_next/static/chunks/111jj26rg98nb.js | 2 + .../out/_next/static/chunks/11_8skd8xyc2y.js | 1 + .../out/_next/static/chunks/11_h-ycwasbfv.js | 7 + .../out/_next/static/chunks/11f7pk3f8kvvz.js | 1 + .../out/_next/static/chunks/12-bl9aesgwlz.js | 7 + .../out/_next/static/chunks/12_259ayj2wfc.js | 426 ------------------ .../out/_next/static/chunks/12xzclxtit2xd.js | 2 + .../out/_next/static/chunks/14aik5-j--wpq.js | 16 - .../{1r8dr-m94xgwo.js => 14rmfrwspq6qw.js} | 4 +- .../out/_next/static/chunks/14wu3p48shec_.js | 1 - .../out/_next/static/chunks/15tw4l45yqt1o.js | 7 + .../out/_next/static/chunks/169bqf_mz3j8m.css | 1 + .../out/_next/static/chunks/17gy9d71tfqhd.js | 1 + .../out/_next/static/chunks/17nqbxvhztf3k.js | 1 - .../out/_next/static/chunks/17ujqh1-hjhsw.js | 2 - .../out/_next/static/chunks/17z98rk6ti8ed.js | 7 + .../{0oy4rb8-8c2l2.js => 18wkke_o3faz6.js} | 2 +- .../out/_next/static/chunks/19283pb0f3m0p.js | 1 - .../out/_next/static/chunks/1964g1_pzq09t.js | 1 - .../out/_next/static/chunks/19o72uq6r0yc6.js | 10 + .../out/_next/static/chunks/19wkvbsdat9-w.js | 1 - .../out/_next/static/chunks/1_l2msnvj037n.js | 1 - .../out/_next/static/chunks/1a0bgy7kzrj91.js | 1 - .../out/_next/static/chunks/1abwfud5uqxxq.js | 1 + .../{1zr7rrk4wkmju.js => 1azbeyb626rh5.js} | 2 +- .../out/_next/static/chunks/1b07b0th52bex.js | 1 + .../out/_next/static/chunks/1bi3j49b6k_jv.js | 7 - .../out/_next/static/chunks/1bwzei67lb34f.js | 1 + .../out/_next/static/chunks/1cat5m5xdpwk8.js | 1 - .../out/_next/static/chunks/1di-caw05k3tq.js | 1 - .../out/_next/static/chunks/1ehpup-6tbb0n.js | 1 - .../out/_next/static/chunks/1fgqa8zynis07.js | 2 - .../out/_next/static/chunks/1gmcfcb5o49sk.js | 2 - .../out/_next/static/chunks/1ioy8obpggx93.js | 3 - .../out/_next/static/chunks/1j3d_14ngm-af.js | 1 + .../{2e__kz2m84e5w.js => 1jk31lastms2d.js} | 6 +- .../out/_next/static/chunks/1jxl7tej0hrz8.js | 1 + .../out/_next/static/chunks/1k06qtyxeubbb.js | 1 - .../{22iools_e0k44.js => 1l1182ye657-m.js} | 2 +- .../out/_next/static/chunks/1lk_03oxuq_2k.js | 1 + .../out/_next/static/chunks/1lndy6n7cwvrq.js | 2 + .../{24quqpgjv0f2h.js => 1lrw-21mmi7hg.js} | 2 +- .../out/_next/static/chunks/1m28zz-ftm1fp.js | 7 + .../out/_next/static/chunks/1mmprbrq4-2gr.js | 1 + .../out/_next/static/chunks/1o8x1l2hhet9i.js | 2 - .../out/_next/static/chunks/1qlouqdmceub4.js | 420 +++++++++++++++++ .../out/_next/static/chunks/1shl79b5yak79.js | 420 ----------------- .../out/_next/static/chunks/1t-d4xiuay30_.js | 10 - .../out/_next/static/chunks/1uxrlxeosisc9.js | 7 - .../out/_next/static/chunks/1uy2av_f_ojad.js | 2 - .../out/_next/static/chunks/1v3t7ods75w3l.js | 1 - .../out/_next/static/chunks/1w3671zqgse91.js | 2 - .../{3js0nq3cf5adx.js => 1wwwkkw13we03.js} | 2 +- .../out/_next/static/chunks/1xuhivu7ukxx1.js | 1 - .../out/_next/static/chunks/1y48qihf_4ttb.js | 1 - .../out/_next/static/chunks/1ye-hq0gakt-m.js | 10 + .../out/_next/static/chunks/1ys3sui-_ujuc.js | 89 ---- .../out/_next/static/chunks/1z6hg2cw2188l.js | 1 + .../out/_next/static/chunks/1zkw9jo-mbcpr.js | 2 - .../out/_next/static/chunks/1zwab6q9-6or9.js | 3 - .../{21cd_tf87-dwi.js => 1zznuqlfxm47w.js} | 4 +- .../out/_next/static/chunks/2-a_yn53fgb-5.js | 1 - .../out/_next/static/chunks/2-aswp_-wcabc.js | 7 + .../{2_zhbb0j-b2ok.js => 2-ggtqru5hw2h.js} | 2 +- .../out/_next/static/chunks/2-xoa0iuxvv3z.js | 1 - .../out/_next/static/chunks/22a6kuks2ithl.js | 1 + .../out/_next/static/chunks/22dktt8qrt6rf.js | 1 + .../out/_next/static/chunks/25q5-n8l6q1-i.js | 1 - .../out/_next/static/chunks/26o1fp5v-765p.js | 1 + .../out/_next/static/chunks/2783exotql09c.js | 1 + .../out/_next/static/chunks/27ycapobchyai.js | 2 + .../out/_next/static/chunks/28md7sjkucknx.js | 3 + .../out/_next/static/chunks/29g5nuekzzaor.js | 2 + .../out/_next/static/chunks/2_6f9v3gbf0sq.js | 10 + .../out/_next/static/chunks/2_o_2f57j_-wv.js | 7 - .../out/_next/static/chunks/2_r1-ssk6qj_g.js | 10 - .../out/_next/static/chunks/2a-z_e49tyoo9.js | 1 - .../out/_next/static/chunks/2a6gczh1lyd79.js | 1 - .../out/_next/static/chunks/2a8ww5ni5-8w0.js | 1 + .../out/_next/static/chunks/2au_w_kyew5j7.js | 1 + .../out/_next/static/chunks/2b2nukd3odkkk.js | 1 - .../out/_next/static/chunks/2bej9fc7jzdr4.js | 1 + .../out/_next/static/chunks/2bvp1-u5jxc3u.js | 2 + .../out/_next/static/chunks/2cd8z85o5pd_-.js | 11 - .../{02ucg1k1-nq5m.js => 2co6u9hlpqnbf.js} | 4 +- .../out/_next/static/chunks/2di7gurm0ukkn.js | 1 - .../out/_next/static/chunks/2eova-n8-0gr2.js | 1 + .../{2w5wae9j41yja.js => 2ezbbnewrnss1.js} | 2 +- .../out/_next/static/chunks/2f9ut03jhmdi3.js | 7 - .../out/_next/static/chunks/2frpidyqqrenq.js | 10 - .../out/_next/static/chunks/2iio9hgb_u4jj.js | 1 + .../out/_next/static/chunks/2ik5hi7wloa-i.js | 8 + .../out/_next/static/chunks/2iwsg18rpz6hv.js | 1 - .../out/_next/static/chunks/2j0naxq1yw5vq.js | 426 ++++++++++++++++++ .../out/_next/static/chunks/2jv6lfgxgwyk1.js | 1 + .../out/_next/static/chunks/2kcxwg1mpncp6.js | 1 - .../out/_next/static/chunks/2kt_m68ln2fyr.js | 1 + .../out/_next/static/chunks/2kztbq94gb-da.js | 1 + .../{2yjrd-czrb_ji.js => 2l12-7bw-d7fj.js} | 4 +- .../out/_next/static/chunks/2lmbrl05dz2hg.js | 16 + .../out/_next/static/chunks/2lp7vir6udzx-.js | 2 + .../out/_next/static/chunks/2myzu9muw2-3r.js | 1 + .../out/_next/static/chunks/2p3h6991b9qoi.js | 1 - .../out/_next/static/chunks/2prue8z2y58db.js | 2 + .../out/_next/static/chunks/2q3uods7pdkez.js | 2 + .../out/_next/static/chunks/2qeanmy565n9w.js | 1 - .../out/_next/static/chunks/2qtobvowg08en.js | 10 - .../out/_next/static/chunks/2reygs7a48uqw.js | 8 + .../out/_next/static/chunks/2rq8yc88w8h8j.js | 1 + .../out/_next/static/chunks/2se5kcdf7ihc3.js | 1 - .../out/_next/static/chunks/2snefsd_3bsd3.js | 1 + .../out/_next/static/chunks/2stnfrjosi49a.js | 7 - .../out/_next/static/chunks/2sx8luiwv4nh0.js | 420 ----------------- .../{3-9r9qzlv5bdt.js => 2t-1ix26pptm3.js} | 2 +- .../out/_next/static/chunks/2t12wbkaoiud_.js | 1 + .../out/_next/static/chunks/2t2-f7xxigo1d.js | 1 + .../out/_next/static/chunks/2tjbx1ci53n8v.js | 1 + .../out/_next/static/chunks/2u7n8srjka729.js | 1 - .../out/_next/static/chunks/2uc2pi4ob086w.js | 1 - .../out/_next/static/chunks/2unj9g7_hj0qe.js | 1 - .../out/_next/static/chunks/2vnpyhxoamx0f.js | 1 + .../out/_next/static/chunks/2wt_98_ncupdk.js | 17 + .../out/_next/static/chunks/2x2f60ss87d3x.js | 10 + .../out/_next/static/chunks/2x6bixy54rehh.js | 1 - .../out/_next/static/chunks/2x96scis66zmk.js | 1 + .../out/_next/static/chunks/2z165x3dvxa-h.js | 2 + .../out/_next/static/chunks/3-ua8s5qfwii-.js | 1 + .../out/_next/static/chunks/310jfkx44dv17.js | 7 - .../{2l25bmiiw9ixp.js => 31cwj7vkk3gfz.js} | 4 +- .../out/_next/static/chunks/3254j4ut19q6_.css | 1 - .../out/_next/static/chunks/329tel7h1_v2c.js | 1 + .../{3c02m_kr-u94p.js => 32_ulchi2_aad.js} | 2 +- .../out/_next/static/chunks/32m8u3pqnkyca.js | 1 - .../out/_next/static/chunks/32srfurefj1bf.js | 1 - .../out/_next/static/chunks/3320sm6j1jotz.js | 3 + .../out/_next/static/chunks/3344qh2b2vx_1.js | 2 - .../out/_next/static/chunks/33bgg52xnwqaf.js | 1 - .../out/_next/static/chunks/33cg4kshh4bdo.js | 18 - .../out/_next/static/chunks/33vn12igkf9rq.js | 1 - .../{1fmx49l6q8v39.js => 34q6izq3hrlzj.js} | 4 +- .../out/_next/static/chunks/35b6em68yjk2r.js | 2 + .../out/_next/static/chunks/35s0c1u_z6dbt.js | 39 -- .../out/_next/static/chunks/3686mcknnkzkg.js | 10 + .../out/_next/static/chunks/36hifnl0gxdfo.js | 1 + .../out/_next/static/chunks/38tna1p1mxo04.js | 10 + .../out/_next/static/chunks/38y1-1c-sh099.js | 8 - .../out/_next/static/chunks/39cywnvrr19y0.js | 2 + .../out/_next/static/chunks/39f-a-6fivok3.js | 1 - .../out/_next/static/chunks/39hfz67hz-jc-.js | 2 - .../out/_next/static/chunks/3_8m84_gkku_s.js | 7 + .../out/_next/static/chunks/3_fum429at8kg.js | 1 - .../{00-dyuivh_bf-.js => 3ap0aimtf8chq.js} | 2 +- .../out/_next/static/chunks/3avwqea26_z-f.js | 1 + .../out/_next/static/chunks/3b9e6ztqd7gsk.js | 1 + .../{2cbf4k2g_n-5n.js => 3ckrfvcj0b3i7.js} | 2 +- .../out/_next/static/chunks/3d5jzuznlr5b_.js | 8 + .../out/_next/static/chunks/3dqt2-fiow5k8.js | 1 - .../out/_next/static/chunks/3dxpyn2i1l2v1.js | 1 + .../out/_next/static/chunks/3dz2va-0f12cz.js | 1 - .../{2nch9p216bkna.js => 3e1s2b2erubc7.js} | 2 +- .../out/_next/static/chunks/3f-3kisu7wrvc.js | 7 - .../out/_next/static/chunks/3fe1jw-cobw__.js | 1 - .../out/_next/static/chunks/3fgutswe6y5lu.js | 31 -- .../out/_next/static/chunks/3gd2x5p0_azf2.js | 1 + .../out/_next/static/chunks/3gfyp_vs2er4-.js | 2 + .../out/_next/static/chunks/3gj-m4kjq0tei.js | 2 + .../out/_next/static/chunks/3i-q3u8a9gglk.js | 1 + .../out/_next/static/chunks/3i55449at-oj8.js | 1 + .../out/_next/static/chunks/3ib18qm2ox61z.js | 10 + .../out/_next/static/chunks/3ik9o_1siirtw.js | 7 + .../out/_next/static/chunks/3im7o_chegc3_.js | 1 + .../out/_next/static/chunks/3j3eozgtz9ocf.js | 1 + .../out/_next/static/chunks/3jh8pcjszqq_l.js | 8 - .../out/_next/static/chunks/3jstcofmhxj55.js | 1 + .../out/_next/static/chunks/3l-k7aywda972.js | 1 - .../out/_next/static/chunks/3ljwjf5o6ocd8.js | 1 + .../out/_next/static/chunks/3lp21rcjbjj72.js | 10 - .../out/_next/static/chunks/3m8yk9-49kmek.js | 1 + .../out/_next/static/chunks/3msynadpz-qlj.js | 16 + .../out/_next/static/chunks/3nge-phqurkae.js | 8 - .../out/_next/static/chunks/3o8olif8ekmc9.js | 1 + .../{22ujkf10ty06o.js => 3oiooy4p0ux4h.js} | 4 +- .../out/_next/static/chunks/3oto3uw67tztq.js | 1 + .../out/_next/static/chunks/3owgij4waou5f.js | 2 - .../out/_next/static/chunks/3pu9plov1btip.js | 1 + .../out/_next/static/chunks/3qquqa6xl0ci8.js | 420 +++++++++++++++++ .../out/_next/static/chunks/3rxr_fmvlxjkw.js | 1 - .../out/_next/static/chunks/3s7zexogzyux8.js | 19 - .../out/_next/static/chunks/3s7zvty459znj.js | 1 + .../out/_next/static/chunks/3siki_esrcnql.js | 1 + .../out/_next/static/chunks/3srzg1la93pwv.js | 1 - .../out/_next/static/chunks/3subppi3hqa14.js | 47 ++ .../out/_next/static/chunks/3thshb577abuo.js | 1 + .../out/_next/static/chunks/3tva2e_i4hgs3.js | 1 - .../out/_next/static/chunks/3u0ul8_6tdvpn.js | 1 - .../out/_next/static/chunks/3uimfrg6nas4c.js | 3 - .../out/_next/static/chunks/3wmcd3z9nmf1k.js | 10 + .../{3t07wgu2l7b3v.js => 3xi9dzq2-qg67.js} | 2 +- .../{0_6rii-l50y-j.js => 3y674jhwchpcq.js} | 2 +- .../out/_next/static/chunks/3z4gd0n2rmqgw.js | 14 + .../out/_next/static/chunks/41eydn-q2wrd_.js | 1 + .../out/_next/static/chunks/42mnrfftrvhcn.js | 10 - .../out/_next/static/chunks/42o7xvspvvbk_.js | 2 + .../out/_next/static/chunks/43hx90fss3e62.js | 1 + .../out/_next/static/chunks/43ka9o8yln4me.js | 1 + .../out/_not-found/__next._full.txt | 24 +- .../out/_not-found/__next._head.txt | 8 +- .../out/_not-found/__next._index.txt | 14 +- .../_not-found/__next._not-found.__PAGE__.txt | 4 +- .../out/_not-found/__next._not-found.txt | 6 +- .../out/_not-found/__next._tree.txt | 4 +- .../_experimental/out/_not-found/index.html | 2 +- .../_experimental/out/_not-found/index.txt | 24 +- ...KGRhc2hib2FyZCk.access-groups.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.access-groups.txt | 6 +- .../access-groups/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/access-groups/__next._full.txt | 36 +- .../out/access-groups/__next._head.txt | 8 +- .../out/access-groups/__next._index.txt | 14 +- .../out/access-groups/__next._tree.txt | 4 +- .../out/access-groups/index.html | 2 +- .../_experimental/out/access-groups/index.txt | 36 +- ....!KGRhc2hib2FyZCk.admin-panel.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.admin-panel.txt | 6 +- .../admin-panel/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/admin-panel/__next._full.txt | 36 +- .../out/admin-panel/__next._head.txt | 8 +- .../out/admin-panel/__next._index.txt | 14 +- .../out/admin-panel/__next._tree.txt | 4 +- .../_experimental/out/admin-panel/index.html | 2 +- .../_experimental/out/admin-panel/index.txt | 36 +- ..._next.!KGRhc2hib2FyZCk.agents.__PAGE__.txt | 8 +- .../agents/__next.!KGRhc2hib2FyZCk.agents.txt | 6 +- .../out/agents/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../_experimental/out/agents/__next._full.txt | 36 +- .../_experimental/out/agents/__next._head.txt | 8 +- .../out/agents/__next._index.txt | 14 +- .../_experimental/out/agents/__next._tree.txt | 4 +- .../proxy/_experimental/out/agents/index.html | 2 +- .../proxy/_experimental/out/agents/index.txt | 36 +- ...ext.!KGRhc2hib2FyZCk.api-keys.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.api-keys.txt | 6 +- .../out/api-keys/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/api-keys/__next._full.txt | 36 +- .../out/api-keys/__next._head.txt | 8 +- .../out/api-keys/__next._index.txt | 14 +- .../out/api-keys/__next._tree.txt | 4 +- .../_experimental/out/api-keys/index.html | 2 +- .../_experimental/out/api-keys/index.txt | 36 +- ...KGRhc2hib2FyZCk.api-reference.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.api-reference.txt | 6 +- .../api-reference/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/api-reference/__next._full.txt | 34 +- .../out/api-reference/__next._head.txt | 8 +- .../out/api-reference/__next._index.txt | 14 +- .../out/api-reference/__next._tree.txt | 4 +- .../out/api-reference/index.html | 2 +- .../_experimental/out/api-reference/index.txt | 34 +- ...next.!KGRhc2hib2FyZCk.budgets.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.budgets.txt | 6 +- .../out/budgets/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/budgets/__next._full.txt | 36 +- .../out/budgets/__next._head.txt | 8 +- .../out/budgets/__next._index.txt | 14 +- .../out/budgets/__next._tree.txt | 4 +- .../_experimental/out/budgets/index.html | 2 +- .../proxy/_experimental/out/budgets/index.txt | 36 +- ...next.!KGRhc2hib2FyZCk.caching.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.caching.txt | 6 +- .../out/caching/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/caching/__next._full.txt | 36 +- .../out/caching/__next._head.txt | 8 +- .../out/caching/__next._index.txt | 14 +- .../out/caching/__next._tree.txt | 4 +- .../_experimental/out/caching/index.html | 2 +- .../proxy/_experimental/out/caching/index.txt | 36 +- .../_experimental/out/chat/__next._full.txt | 34 +- .../_experimental/out/chat/__next._head.txt | 8 +- .../_experimental/out/chat/__next._index.txt | 14 +- .../_experimental/out/chat/__next._tree.txt | 4 +- .../out/chat/__next.chat.__PAGE__.txt | 8 +- .../_experimental/out/chat/__next.chat.txt | 10 +- .../out/chat/api-keys/__next._full.txt | 34 +- .../out/chat/api-keys/__next._head.txt | 8 +- .../out/chat/api-keys/__next._index.txt | 14 +- .../out/chat/api-keys/__next._tree.txt | 4 +- .../__next.chat.api-keys.__PAGE__.txt | 8 +- .../chat/api-keys/__next.chat.api-keys.txt | 6 +- .../out/chat/api-keys/__next.chat.txt | 10 +- .../out/chat/api-keys/index.html | 2 +- .../_experimental/out/chat/api-keys/index.txt | 34 +- .../out/chat/credentials/__next._full.txt | 34 +- .../out/chat/credentials/__next._head.txt | 8 +- .../out/chat/credentials/__next._index.txt | 14 +- .../out/chat/credentials/__next._tree.txt | 4 +- .../__next.chat.credentials.__PAGE__.txt | 8 +- .../credentials/__next.chat.credentials.txt | 6 +- .../out/chat/credentials/__next.chat.txt | 10 +- .../out/chat/credentials/index.html | 2 +- .../out/chat/credentials/index.txt | 34 +- .../proxy/_experimental/out/chat/index.html | 2 +- .../proxy/_experimental/out/chat/index.txt | 34 +- .../out/chat/integrations/__next._full.txt | 36 +- .../out/chat/integrations/__next._head.txt | 8 +- .../out/chat/integrations/__next._index.txt | 14 +- .../out/chat/integrations/__next._tree.txt | 4 +- .../__next.chat.integrations.__PAGE__.txt | 8 +- .../integrations/__next.chat.integrations.txt | 6 +- .../out/chat/integrations/__next.chat.txt | 10 +- .../out/chat/integrations/index.html | 2 +- .../out/chat/integrations/index.txt | 36 +- .../out/chat/logs/__next._full.txt | 34 +- .../out/chat/logs/__next._head.txt | 8 +- .../out/chat/logs/__next._index.txt | 14 +- .../out/chat/logs/__next._tree.txt | 4 +- .../chat/logs/__next.chat.logs.__PAGE__.txt | 8 +- .../out/chat/logs/__next.chat.logs.txt | 6 +- .../out/chat/logs/__next.chat.txt | 10 +- .../_experimental/out/chat/logs/index.html | 2 +- .../_experimental/out/chat/logs/index.txt | 34 +- .../out/chat/usage/__next._full.txt | 34 +- .../out/chat/usage/__next._head.txt | 8 +- .../out/chat/usage/__next._index.txt | 14 +- .../out/chat/usage/__next._tree.txt | 4 +- .../out/chat/usage/__next.chat.txt | 10 +- .../chat/usage/__next.chat.usage.__PAGE__.txt | 8 +- .../out/chat/usage/__next.chat.usage.txt | 6 +- .../_experimental/out/chat/usage/index.html | 2 +- .../_experimental/out/chat/usage/index.txt | 34 +- .../out/connect/__next._full.txt | 34 +- .../out/connect/__next._head.txt | 8 +- .../out/connect/__next._index.txt | 14 +- .../out/connect/__next._tree.txt | 4 +- .../out/connect/__next.connect.__PAGE__.txt | 8 +- .../out/connect/__next.connect.txt | 10 +- .../_experimental/out/connect/index.html | 2 +- .../proxy/_experimental/out/connect/index.txt | 34 +- ...c2hib2FyZCk.cost-optimization.__PAGE__.txt | 8 +- ...ext.!KGRhc2hib2FyZCk.cost-optimization.txt | 6 +- .../__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/cost-optimization/__next._full.txt | 36 +- .../out/cost-optimization/__next._head.txt | 8 +- .../out/cost-optimization/__next._index.txt | 14 +- .../out/cost-optimization/__next._tree.txt | 4 +- .../out/cost-optimization/index.html | 2 +- .../out/cost-optimization/index.txt | 36 +- ...KGRhc2hib2FyZCk.cost-tracking.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.cost-tracking.txt | 6 +- .../cost-tracking/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/cost-tracking/__next._full.txt | 36 +- .../out/cost-tracking/__next._head.txt | 8 +- .../out/cost-tracking/__next._index.txt | 14 +- .../out/cost-tracking/__next._tree.txt | 4 +- .../out/cost-tracking/index.html | 2 +- .../_experimental/out/cost-tracking/index.txt | 36 +- ...2hib2FyZCk.guardrails-monitor.__PAGE__.txt | 8 +- ...xt.!KGRhc2hib2FyZCk.guardrails-monitor.txt | 6 +- .../__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/guardrails-monitor/__next._full.txt | 36 +- .../out/guardrails-monitor/__next._head.txt | 8 +- .../out/guardrails-monitor/__next._index.txt | 14 +- .../out/guardrails-monitor/__next._tree.txt | 4 +- .../out/guardrails-monitor/index.html | 2 +- .../out/guardrails-monitor/index.txt | 36 +- ...t.!KGRhc2hib2FyZCk.guardrails.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.guardrails.txt | 6 +- .../guardrails/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/guardrails/__next._full.txt | 36 +- .../out/guardrails/__next._head.txt | 8 +- .../out/guardrails/__next._index.txt | 14 +- .../out/guardrails/__next._tree.txt | 4 +- .../_experimental/out/guardrails/index.html | 2 +- .../_experimental/out/guardrails/index.txt | 36 +- litellm/proxy/_experimental/out/index.html | 2 +- litellm/proxy/_experimental/out/index.txt | 36 +- ...2hib2FyZCk.logging-and-alerts.__PAGE__.txt | 8 +- ...xt.!KGRhc2hib2FyZCk.logging-and-alerts.txt | 6 +- .../__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/logging-and-alerts/__next._full.txt | 36 +- .../out/logging-and-alerts/__next._head.txt | 8 +- .../out/logging-and-alerts/__next._index.txt | 14 +- .../out/logging-and-alerts/__next._tree.txt | 4 +- .../out/logging-and-alerts/index.html | 2 +- .../out/logging-and-alerts/index.txt | 36 +- .../_experimental/out/login/__next._full.txt | 28 +- .../_experimental/out/login/__next._head.txt | 8 +- .../_experimental/out/login/__next._index.txt | 14 +- .../_experimental/out/login/__next._tree.txt | 4 +- .../out/login/__next.login.__PAGE__.txt | 8 +- .../_experimental/out/login/__next.login.txt | 6 +- .../proxy/_experimental/out/login/index.html | 2 +- .../proxy/_experimental/out/login/index.txt | 28 +- .../__next.!KGRhc2hib2FyZCk.logs.__PAGE__.txt | 8 +- .../out/logs/__next.!KGRhc2hib2FyZCk.logs.txt | 6 +- .../out/logs/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../_experimental/out/logs/__next._full.txt | 36 +- .../_experimental/out/logs/__next._head.txt | 8 +- .../_experimental/out/logs/__next._index.txt | 14 +- .../_experimental/out/logs/__next._tree.txt | 4 +- .../proxy/_experimental/out/logs/index.html | 2 +- .../proxy/_experimental/out/logs/index.txt | 36 +- ....!KGRhc2hib2FyZCk.mcp-servers.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.mcp-servers.txt | 6 +- .../mcp-servers/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/mcp-servers/__next._full.txt | 36 +- .../out/mcp-servers/__next._head.txt | 8 +- .../out/mcp-servers/__next._index.txt | 14 +- .../out/mcp-servers/__next._tree.txt | 4 +- .../_experimental/out/mcp-servers/index.html | 2 +- .../_experimental/out/mcp-servers/index.txt | 36 +- .../out/mcp/oauth/callback/__next._full.txt | 28 +- .../out/mcp/oauth/callback/__next._head.txt | 8 +- .../out/mcp/oauth/callback/__next._index.txt | 14 +- .../out/mcp/oauth/callback/__next._tree.txt | 4 +- .../__next.mcp.oauth.callback.__PAGE__.txt | 8 +- .../callback/__next.mcp.oauth.callback.txt | 6 +- .../mcp/oauth/callback/__next.mcp.oauth.txt | 6 +- .../out/mcp/oauth/callback/__next.mcp.txt | 6 +- .../out/mcp/oauth/callback/index.html | 2 +- .../out/mcp/oauth/callback/index.txt | 28 +- ..._next.!KGRhc2hib2FyZCk.memory.__PAGE__.txt | 8 +- .../memory/__next.!KGRhc2hib2FyZCk.memory.txt | 6 +- .../out/memory/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../_experimental/out/memory/__next._full.txt | 36 +- .../_experimental/out/memory/__next._head.txt | 8 +- .../out/memory/__next._index.txt | 14 +- .../_experimental/out/memory/__next._tree.txt | 4 +- .../proxy/_experimental/out/memory/index.html | 2 +- .../proxy/_experimental/out/memory/index.txt | 36 +- ...Rhc2hib2FyZCk.model-hub-table.__PAGE__.txt | 8 +- ..._next.!KGRhc2hib2FyZCk.model-hub-table.txt | 6 +- .../__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/model-hub-table/__next._full.txt | 36 +- .../out/model-hub-table/__next._head.txt | 8 +- .../out/model-hub-table/__next._index.txt | 14 +- .../out/model-hub-table/__next._tree.txt | 4 +- .../out/model-hub-table/index.html | 2 +- .../out/model-hub-table/index.txt | 36 +- .../out/model_hub/__next._full.txt | 44 +- .../out/model_hub/__next._head.txt | 8 +- .../out/model_hub/__next._index.txt | 14 +- .../out/model_hub/__next._tree.txt | 4 +- .../model_hub/__next.model_hub.__PAGE__.txt | 8 +- .../out/model_hub/__next.model_hub.txt | 6 +- .../_experimental/out/model_hub/index.html | 2 +- .../_experimental/out/model_hub/index.txt | 44 +- .../out/model_hub_table/__next._full.txt | 58 +-- .../out/model_hub_table/__next._head.txt | 8 +- .../out/model_hub_table/__next._index.txt | 14 +- .../out/model_hub_table/__next._tree.txt | 4 +- .../__next.model_hub_table.__PAGE__.txt | 8 +- .../__next.model_hub_table.txt | 6 +- .../out/model_hub_table/index.html | 2 +- .../out/model_hub_table/index.txt | 58 +-- ...ib2FyZCk.models-and-endpoints.__PAGE__.txt | 8 +- ....!KGRhc2hib2FyZCk.models-and-endpoints.txt | 6 +- .../__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/models-and-endpoints/__next._full.txt | 36 +- .../out/models-and-endpoints/__next._head.txt | 8 +- .../models-and-endpoints/__next._index.txt | 14 +- .../out/models-and-endpoints/__next._tree.txt | 4 +- .../out/models-and-endpoints/index.html | 2 +- .../out/models-and-endpoints/index.txt | 36 +- ...xt.!KGRhc2hib2FyZCk.old-usage.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.old-usage.txt | 6 +- .../out/old-usage/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/old-usage/__next._full.txt | 36 +- .../out/old-usage/__next._head.txt | 8 +- .../out/old-usage/__next._index.txt | 14 +- .../out/old-usage/__next._tree.txt | 4 +- .../_experimental/out/old-usage/index.html | 2 +- .../_experimental/out/old-usage/index.txt | 36 +- .../out/onboarding/__next._full.txt | 28 +- .../out/onboarding/__next._head.txt | 8 +- .../out/onboarding/__next._index.txt | 14 +- .../out/onboarding/__next._tree.txt | 4 +- .../onboarding/__next.onboarding.__PAGE__.txt | 8 +- .../out/onboarding/__next.onboarding.txt | 6 +- .../_experimental/out/onboarding/index.html | 2 +- .../_experimental/out/onboarding/index.txt | 28 +- ...KGRhc2hib2FyZCk.organizations.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.organizations.txt | 6 +- .../organizations/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/organizations/__next._full.txt | 36 +- .../out/organizations/__next._head.txt | 8 +- .../out/organizations/__next._index.txt | 14 +- .../out/organizations/__next._tree.txt | 4 +- .../out/organizations/index.html | 2 +- .../_experimental/out/organizations/index.txt | 36 +- ...t.!KGRhc2hib2FyZCk.playground.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.playground.txt | 6 +- .../playground/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/playground/__next._full.txt | 36 +- .../out/playground/__next._head.txt | 8 +- .../out/playground/__next._index.txt | 14 +- .../out/playground/__next._tree.txt | 4 +- .../_experimental/out/playground/index.html | 2 +- .../_experimental/out/playground/index.txt | 36 +- ...ext.!KGRhc2hib2FyZCk.policies.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.policies.txt | 6 +- .../out/policies/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/policies/__next._full.txt | 36 +- .../out/policies/__next._head.txt | 8 +- .../out/policies/__next._index.txt | 14 +- .../out/policies/__next._tree.txt | 4 +- .../_experimental/out/policies/index.html | 2 +- .../_experimental/out/policies/index.txt | 36 +- ...ext.!KGRhc2hib2FyZCk.projects.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.projects.txt | 6 +- .../out/projects/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/projects/__next._full.txt | 36 +- .../out/projects/__next._head.txt | 8 +- .../out/projects/__next._index.txt | 14 +- .../out/projects/__next._tree.txt | 4 +- .../_experimental/out/projects/index.html | 2 +- .../_experimental/out/projects/index.txt | 36 +- ...next.!KGRhc2hib2FyZCk.prompts.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.prompts.txt | 6 +- .../out/prompts/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/prompts/__next._full.txt | 36 +- .../out/prompts/__next._head.txt | 8 +- .../out/prompts/__next._index.txt | 14 +- .../out/prompts/__next._tree.txt | 4 +- .../_experimental/out/prompts/index.html | 2 +- .../proxy/_experimental/out/prompts/index.txt | 36 +- ...Rhc2hib2FyZCk.router-settings.__PAGE__.txt | 8 +- ..._next.!KGRhc2hib2FyZCk.router-settings.txt | 6 +- .../__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/router-settings/__next._full.txt | 36 +- .../out/router-settings/__next._head.txt | 8 +- .../out/router-settings/__next._index.txt | 14 +- .../out/router-settings/__next._tree.txt | 4 +- .../out/router-settings/index.html | 2 +- .../out/router-settings/index.txt | 36 +- ...!KGRhc2hib2FyZCk.search-tools.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.search-tools.txt | 6 +- .../search-tools/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/search-tools/__next._full.txt | 36 +- .../out/search-tools/__next._head.txt | 8 +- .../out/search-tools/__next._index.txt | 14 +- .../out/search-tools/__next._tree.txt | 4 +- .../_experimental/out/search-tools/index.html | 2 +- .../_experimental/out/search-tools/index.txt | 36 +- ..._next.!KGRhc2hib2FyZCk.skills.__PAGE__.txt | 8 +- .../skills/__next.!KGRhc2hib2FyZCk.skills.txt | 6 +- .../out/skills/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../_experimental/out/skills/__next._full.txt | 36 +- .../_experimental/out/skills/__next._head.txt | 8 +- .../out/skills/__next._index.txt | 14 +- .../_experimental/out/skills/__next._tree.txt | 4 +- .../proxy/_experimental/out/skills/index.html | 2 +- .../proxy/_experimental/out/skills/index.txt | 36 +- ...GRhc2hib2FyZCk.tag-management.__PAGE__.txt | 8 +- ...__next.!KGRhc2hib2FyZCk.tag-management.txt | 6 +- .../__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/tag-management/__next._full.txt | 36 +- .../out/tag-management/__next._head.txt | 8 +- .../out/tag-management/__next._index.txt | 14 +- .../out/tag-management/__next._tree.txt | 4 +- .../out/tag-management/index.html | 2 +- .../out/tag-management/index.txt | 36 +- ...__next.!KGRhc2hib2FyZCk.teams.__PAGE__.txt | 8 +- .../teams/__next.!KGRhc2hib2FyZCk.teams.txt | 6 +- .../out/teams/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../_experimental/out/teams/__next._full.txt | 36 +- .../_experimental/out/teams/__next._head.txt | 8 +- .../_experimental/out/teams/__next._index.txt | 14 +- .../_experimental/out/teams/__next._tree.txt | 4 +- .../proxy/_experimental/out/teams/index.html | 2 +- .../proxy/_experimental/out/teams/index.txt | 36 +- ...KGRhc2hib2FyZCk.tool-policies.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.tool-policies.txt | 6 +- .../tool-policies/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/tool-policies/__next._full.txt | 36 +- .../out/tool-policies/__next._head.txt | 8 +- .../out/tool-policies/__next._index.txt | 14 +- .../out/tool-policies/__next._tree.txt | 4 +- .../out/tool-policies/index.html | 2 +- .../_experimental/out/tool-policies/index.txt | 36 +- ...c2hib2FyZCk.transform-request.__PAGE__.txt | 8 +- ...ext.!KGRhc2hib2FyZCk.transform-request.txt | 6 +- .../__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/transform-request/__next._full.txt | 36 +- .../out/transform-request/__next._head.txt | 8 +- .../out/transform-request/__next._index.txt | 14 +- .../out/transform-request/__next._tree.txt | 4 +- .../out/transform-request/index.html | 2 +- .../out/transform-request/index.txt | 36 +- .../out/ui-theme/__next.!KGRhc2hib2FyZCk.txt | 10 +- ...ext.!KGRhc2hib2FyZCk.ui-theme.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.ui-theme.txt | 6 +- .../out/ui-theme/__next._full.txt | 36 +- .../out/ui-theme/__next._head.txt | 8 +- .../out/ui-theme/__next._index.txt | 14 +- .../out/ui-theme/__next._tree.txt | 4 +- .../_experimental/out/ui-theme/index.html | 2 +- .../_experimental/out/ui-theme/index.txt | 36 +- .../out/usage/__next.!KGRhc2hib2FyZCk.txt | 10 +- ...__next.!KGRhc2hib2FyZCk.usage.__PAGE__.txt | 8 +- .../usage/__next.!KGRhc2hib2FyZCk.usage.txt | 6 +- .../_experimental/out/usage/__next._full.txt | 36 +- .../_experimental/out/usage/__next._head.txt | 8 +- .../_experimental/out/usage/__next._index.txt | 14 +- .../_experimental/out/usage/__next._tree.txt | 4 +- .../proxy/_experimental/out/usage/index.html | 2 +- .../proxy/_experimental/out/usage/index.txt | 36 +- .../out/users/__next.!KGRhc2hib2FyZCk.txt | 10 +- ...__next.!KGRhc2hib2FyZCk.users.__PAGE__.txt | 8 +- .../users/__next.!KGRhc2hib2FyZCk.users.txt | 6 +- .../_experimental/out/users/__next._full.txt | 36 +- .../_experimental/out/users/__next._head.txt | 8 +- .../_experimental/out/users/__next._index.txt | 14 +- .../_experimental/out/users/__next._tree.txt | 4 +- .../proxy/_experimental/out/users/index.html | 2 +- .../proxy/_experimental/out/users/index.txt | 36 +- .../vector-stores/__next.!KGRhc2hib2FyZCk.txt | 10 +- ...KGRhc2hib2FyZCk.vector-stores.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.vector-stores.txt | 6 +- .../out/vector-stores/__next._full.txt | 36 +- .../out/vector-stores/__next._head.txt | 8 +- .../out/vector-stores/__next._index.txt | 14 +- .../out/vector-stores/__next._tree.txt | 4 +- .../out/vector-stores/index.html | 2 +- .../_experimental/out/vector-stores/index.txt | 36 +- .../out/workflows/__next.!KGRhc2hib2FyZCk.txt | 10 +- ...xt.!KGRhc2hib2FyZCk.workflows.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.workflows.txt | 6 +- .../out/workflows/__next._full.txt | 36 +- .../out/workflows/__next._head.txt | 8 +- .../out/workflows/__next._index.txt | 14 +- .../out/workflows/__next._tree.txt | 4 +- .../_experimental/out/workflows/index.html | 2 +- .../_experimental/out/workflows/index.txt | 36 +- 714 files changed, 4856 insertions(+), 4843 deletions(-) rename litellm/proxy/_experimental/out/_next/static/{qXutWsQW5C1Pf62WxTkEI => 8bC_aTV0H1nUtrFDXkfaB}/_buildManifest.js (100%) rename litellm/proxy/_experimental/out/_next/static/{qXutWsQW5C1Pf62WxTkEI => 8bC_aTV0H1nUtrFDXkfaB}/_clientMiddlewareManifest.js (100%) rename litellm/proxy/_experimental/out/_next/static/{qXutWsQW5C1Pf62WxTkEI => 8bC_aTV0H1nUtrFDXkfaB}/_ssgManifest.js (100%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/00g6xfr4yow7h.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/00lwtxl1k_z8t.js rename litellm/proxy/_experimental/out/_next/static/chunks/{10-c3gjiv7yt0.js => 011as3ct2u0nu.js} (62%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/02-alpsjfp5b7.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/025ocjcb8e481.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/03c3h-nx-fb3y.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/03kaz3d0v3z45.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/046q5lwe95zp6.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/048wkdcpsnwne.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/04y2hqzy08peg.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/054z32giulaw7.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/058j9m4b8p4wx.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/05id71gg6oywc.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/05ttqlxo9w0ow.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/06hxe45fjy7x7.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/06q8aep867ss7.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/06sx0oh8weeph.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/08goggic_ad66.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/08k6jolcrw-uw.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/08yk0x-hh3ydk.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0965-angwdvwe.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0_mw8gm-qowti.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0_vlfpb8phl0v.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0a-3zns09ja98.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0ak2vacq91c7k.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0ald9tfsbz2e-.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0am68mi9t9cb6.js rename litellm/proxy/_experimental/out/_next/static/chunks/{3fqu7hpcrtg67.js => 0b4c0ak_j76h0.js} (96%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0bzsgiuwi1q0e.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0cgtkk6qelb_j.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0cj588tfl8vcq.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0d398pudg-p7u.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0d6y38dwy2fvp.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0dh09yfknpuy3.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0dsiq_ok1yngk.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0e3vwdd43gm8b.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0eamb3kk74kws.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0euektnkbx78f.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0f_bhylcfohcm.js rename litellm/proxy/_experimental/out/_next/static/chunks/{3jgsbd_1fz8l8.js => 0gylheyn-59ow.js} (57%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0i25zatajbma2.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0jjbikxye1xv_.js rename litellm/proxy/_experimental/out/_next/static/chunks/{3e4fipm_mrl-n.js => 0jl9haj4wjvj5.js} (82%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0k-74wqsm8dzt.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0kap_rdm2-lem.js rename litellm/proxy/_experimental/out/_next/static/chunks/{007c8g8hmd9qz.js => 0kpote6n3ff9a.js} (54%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0kx52ovlpa34x.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0lf6n96uy4q27.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0m-x8i06te864.js rename litellm/proxy/_experimental/out/_next/static/chunks/{2wzbftaqumx8j.js => 0ma4y3uxzghhw.js} (51%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0maan-7nzqqca.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0nb8zgkq5nq1r.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0nobv49ll5nyv.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0onjeur4drmh-.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0p_yh7pymv-5p.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0q8q-21f1553f.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0qf84m09hg_q8.js rename litellm/proxy/_experimental/out/_next/static/chunks/{0if4h9a-qzqx4.js => 0qkqkii3nce2s.js} (61%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0qtmfaeayrb_n.js rename litellm/proxy/_experimental/out/_next/static/chunks/{30dm_jeoikihy.js => 0r0okx31djc7i.js} (57%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0r9-qx6h8pzi9.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0sm3ln66e4502.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0taea1jhojoz5.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0vvnul8uez9kj.js rename litellm/proxy/_experimental/out/_next/static/chunks/{1t9h71-jh-nt0.js => 0wo6dp1zxhzve.js} (97%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0ww76lz_0cphv.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0xou7v06a6xww.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0y00ve1sk9qox.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0yjg5mjiahhan.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0zduf1gntl_f8.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/111jj26rg98nb.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/11_8skd8xyc2y.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/11_h-ycwasbfv.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/11f7pk3f8kvvz.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/12-bl9aesgwlz.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/12_259ayj2wfc.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/12xzclxtit2xd.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/14aik5-j--wpq.js rename litellm/proxy/_experimental/out/_next/static/chunks/{1r8dr-m94xgwo.js => 14rmfrwspq6qw.js} (78%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/14wu3p48shec_.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/15tw4l45yqt1o.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/169bqf_mz3j8m.css create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/17gy9d71tfqhd.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/17nqbxvhztf3k.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/17ujqh1-hjhsw.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/17z98rk6ti8ed.js rename litellm/proxy/_experimental/out/_next/static/chunks/{0oy4rb8-8c2l2.js => 18wkke_o3faz6.js} (84%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/19283pb0f3m0p.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1964g1_pzq09t.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/19o72uq6r0yc6.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/19wkvbsdat9-w.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1_l2msnvj037n.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1a0bgy7kzrj91.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1abwfud5uqxxq.js rename litellm/proxy/_experimental/out/_next/static/chunks/{1zr7rrk4wkmju.js => 1azbeyb626rh5.js} (84%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1b07b0th52bex.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1bi3j49b6k_jv.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1bwzei67lb34f.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1cat5m5xdpwk8.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1di-caw05k3tq.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1ehpup-6tbb0n.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1fgqa8zynis07.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1gmcfcb5o49sk.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1ioy8obpggx93.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1j3d_14ngm-af.js rename litellm/proxy/_experimental/out/_next/static/chunks/{2e__kz2m84e5w.js => 1jk31lastms2d.js} (71%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1jxl7tej0hrz8.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1k06qtyxeubbb.js rename litellm/proxy/_experimental/out/_next/static/chunks/{22iools_e0k44.js => 1l1182ye657-m.js} (54%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1lk_03oxuq_2k.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1lndy6n7cwvrq.js rename litellm/proxy/_experimental/out/_next/static/chunks/{24quqpgjv0f2h.js => 1lrw-21mmi7hg.js} (69%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1m28zz-ftm1fp.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1mmprbrq4-2gr.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1o8x1l2hhet9i.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1qlouqdmceub4.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1shl79b5yak79.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1t-d4xiuay30_.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1uxrlxeosisc9.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1uy2av_f_ojad.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1v3t7ods75w3l.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1w3671zqgse91.js rename litellm/proxy/_experimental/out/_next/static/chunks/{3js0nq3cf5adx.js => 1wwwkkw13we03.js} (93%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1xuhivu7ukxx1.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1y48qihf_4ttb.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1ye-hq0gakt-m.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1ys3sui-_ujuc.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1z6hg2cw2188l.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1zkw9jo-mbcpr.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1zwab6q9-6or9.js rename litellm/proxy/_experimental/out/_next/static/chunks/{21cd_tf87-dwi.js => 1zznuqlfxm47w.js} (58%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2-a_yn53fgb-5.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2-aswp_-wcabc.js rename litellm/proxy/_experimental/out/_next/static/chunks/{2_zhbb0j-b2ok.js => 2-ggtqru5hw2h.js} (54%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2-xoa0iuxvv3z.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/22a6kuks2ithl.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/22dktt8qrt6rf.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/25q5-n8l6q1-i.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/26o1fp5v-765p.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2783exotql09c.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/27ycapobchyai.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/28md7sjkucknx.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/29g5nuekzzaor.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2_6f9v3gbf0sq.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2_o_2f57j_-wv.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2_r1-ssk6qj_g.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2a-z_e49tyoo9.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2a6gczh1lyd79.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2a8ww5ni5-8w0.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2au_w_kyew5j7.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2b2nukd3odkkk.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2bej9fc7jzdr4.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2bvp1-u5jxc3u.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2cd8z85o5pd_-.js rename litellm/proxy/_experimental/out/_next/static/chunks/{02ucg1k1-nq5m.js => 2co6u9hlpqnbf.js} (67%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2di7gurm0ukkn.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2eova-n8-0gr2.js rename litellm/proxy/_experimental/out/_next/static/chunks/{2w5wae9j41yja.js => 2ezbbnewrnss1.js} (99%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2f9ut03jhmdi3.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2frpidyqqrenq.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2iio9hgb_u4jj.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2ik5hi7wloa-i.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2iwsg18rpz6hv.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2j0naxq1yw5vq.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2jv6lfgxgwyk1.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2kcxwg1mpncp6.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2kt_m68ln2fyr.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2kztbq94gb-da.js rename litellm/proxy/_experimental/out/_next/static/chunks/{2yjrd-czrb_ji.js => 2l12-7bw-d7fj.js} (58%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2lmbrl05dz2hg.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2lp7vir6udzx-.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2myzu9muw2-3r.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2p3h6991b9qoi.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2prue8z2y58db.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2q3uods7pdkez.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2qeanmy565n9w.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2qtobvowg08en.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2reygs7a48uqw.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2rq8yc88w8h8j.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2se5kcdf7ihc3.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2snefsd_3bsd3.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2stnfrjosi49a.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2sx8luiwv4nh0.js rename litellm/proxy/_experimental/out/_next/static/chunks/{3-9r9qzlv5bdt.js => 2t-1ix26pptm3.js} (71%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2t12wbkaoiud_.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2t2-f7xxigo1d.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2tjbx1ci53n8v.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2u7n8srjka729.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2uc2pi4ob086w.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2unj9g7_hj0qe.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2vnpyhxoamx0f.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2wt_98_ncupdk.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2x2f60ss87d3x.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2x6bixy54rehh.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2x96scis66zmk.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2z165x3dvxa-h.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3-ua8s5qfwii-.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/310jfkx44dv17.js rename litellm/proxy/_experimental/out/_next/static/chunks/{2l25bmiiw9ixp.js => 31cwj7vkk3gfz.js} (60%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3254j4ut19q6_.css create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/329tel7h1_v2c.js rename litellm/proxy/_experimental/out/_next/static/chunks/{3c02m_kr-u94p.js => 32_ulchi2_aad.js} (86%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/32m8u3pqnkyca.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/32srfurefj1bf.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3320sm6j1jotz.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3344qh2b2vx_1.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/33bgg52xnwqaf.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/33cg4kshh4bdo.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/33vn12igkf9rq.js rename litellm/proxy/_experimental/out/_next/static/chunks/{1fmx49l6q8v39.js => 34q6izq3hrlzj.js} (65%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/35b6em68yjk2r.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/35s0c1u_z6dbt.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3686mcknnkzkg.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/36hifnl0gxdfo.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/38tna1p1mxo04.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/38y1-1c-sh099.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/39cywnvrr19y0.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/39f-a-6fivok3.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/39hfz67hz-jc-.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3_8m84_gkku_s.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3_fum429at8kg.js rename litellm/proxy/_experimental/out/_next/static/chunks/{00-dyuivh_bf-.js => 3ap0aimtf8chq.js} (99%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3avwqea26_z-f.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3b9e6ztqd7gsk.js rename litellm/proxy/_experimental/out/_next/static/chunks/{2cbf4k2g_n-5n.js => 3ckrfvcj0b3i7.js} (94%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3d5jzuznlr5b_.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3dqt2-fiow5k8.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3dxpyn2i1l2v1.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3dz2va-0f12cz.js rename litellm/proxy/_experimental/out/_next/static/chunks/{2nch9p216bkna.js => 3e1s2b2erubc7.js} (59%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3f-3kisu7wrvc.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3fe1jw-cobw__.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3fgutswe6y5lu.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3gd2x5p0_azf2.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3gfyp_vs2er4-.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3gj-m4kjq0tei.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3i-q3u8a9gglk.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3i55449at-oj8.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3ib18qm2ox61z.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3ik9o_1siirtw.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3im7o_chegc3_.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3j3eozgtz9ocf.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3jh8pcjszqq_l.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3jstcofmhxj55.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3l-k7aywda972.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3ljwjf5o6ocd8.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3lp21rcjbjj72.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3m8yk9-49kmek.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3msynadpz-qlj.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3nge-phqurkae.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3o8olif8ekmc9.js rename litellm/proxy/_experimental/out/_next/static/chunks/{22ujkf10ty06o.js => 3oiooy4p0ux4h.js} (87%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3oto3uw67tztq.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3owgij4waou5f.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3pu9plov1btip.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3qquqa6xl0ci8.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3rxr_fmvlxjkw.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3s7zexogzyux8.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3s7zvty459znj.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3siki_esrcnql.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3srzg1la93pwv.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3subppi3hqa14.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3thshb577abuo.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3tva2e_i4hgs3.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3u0ul8_6tdvpn.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3uimfrg6nas4c.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3wmcd3z9nmf1k.js rename litellm/proxy/_experimental/out/_next/static/chunks/{3t07wgu2l7b3v.js => 3xi9dzq2-qg67.js} (83%) rename litellm/proxy/_experimental/out/_next/static/chunks/{0_6rii-l50y-j.js => 3y674jhwchpcq.js} (68%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3z4gd0n2rmqgw.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/41eydn-q2wrd_.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/42mnrfftrvhcn.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/42o7xvspvvbk_.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/43hx90fss3e62.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/43ka9o8yln4me.js diff --git a/litellm/proxy/_experimental/out/404.html b/litellm/proxy/_experimental/out/404.html index 0a164642dab..954cf7c6041 100644 --- a/litellm/proxy/_experimental/out/404.html +++ b/litellm/proxy/_experimental/out/404.html @@ -1 +1 @@ -404: This page could not be found.LiteLLM Dashboard

404

This page could not be found.

\ No newline at end of file +404: This page could not be found.LiteLLM Dashboard

404

This page could not be found.

\ No newline at end of file diff --git a/litellm/proxy/_experimental/out/404/index.html b/litellm/proxy/_experimental/out/404/index.html index 0a164642dab..954cf7c6041 100644 --- a/litellm/proxy/_experimental/out/404/index.html +++ b/litellm/proxy/_experimental/out/404/index.html @@ -1 +1 @@ -404: This page could not be found.LiteLLM Dashboard

404

This page could not be found.

\ No newline at end of file +404: This page could not be found.LiteLLM Dashboard

404

This page could not be found.

\ No newline at end of file diff --git a/litellm/proxy/_experimental/out/__next.!KGRhc2hib2FyZCk.__PAGE__.txt b/litellm/proxy/_experimental/out/__next.!KGRhc2hib2FyZCk.__PAGE__.txt index c10ced8b6bc..583f4376e5b 100644 --- a/litellm/proxy/_experimental/out/__next.!KGRhc2hib2FyZCk.__PAGE__.txt +++ b/litellm/proxy/_experimental/out/__next.!KGRhc2hib2FyZCk.__PAGE__.txt @@ -1,9 +1,9 @@ 1:"$Sreact.fragment" -2:I[347257,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ClientPageRoot"] -3:I[871135,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","/litellm-asset-prefix/_next/static/chunks/1ioy8obpggx93.js","/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","/litellm-asset-prefix/_next/static/chunks/1zr7rrk4wkmju.js","/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","/litellm-asset-prefix/_next/static/chunks/0fk0i3e2aixp7.js","/litellm-asset-prefix/_next/static/chunks/0zduf1gntl_f8.js","/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","/litellm-asset-prefix/_next/static/chunks/1a0bgy7kzrj91.js","/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","/litellm-asset-prefix/_next/static/chunks/0g8wwba6umbim.js","/litellm-asset-prefix/_next/static/chunks/1di-caw05k3tq.js","/litellm-asset-prefix/_next/static/chunks/00g6xfr4yow7h.js","/litellm-asset-prefix/_next/static/chunks/2kcxwg1mpncp6.js","/litellm-asset-prefix/_next/static/chunks/1fmx49l6q8v39.js","/litellm-asset-prefix/_next/static/chunks/0ww76lz_0cphv.js","/litellm-asset-prefix/_next/static/chunks/2uc2pi4ob086w.js","/litellm-asset-prefix/_next/static/chunks/2hu1vyy-5pv13.js","/litellm-asset-prefix/_next/static/chunks/2l25bmiiw9ixp.js","/litellm-asset-prefix/_next/static/chunks/1uz3jt-tj9lkf.js","/litellm-asset-prefix/_next/static/chunks/3drq2_k-jeio2.js","/litellm-asset-prefix/_next/static/chunks/0dsiq_ok1yngk.js","/litellm-asset-prefix/_next/static/chunks/3srzg1la93pwv.js","/litellm-asset-prefix/_next/static/chunks/17nqbxvhztf3k.js","/litellm-asset-prefix/_next/static/chunks/2c90xukbd3il6.js","/litellm-asset-prefix/_next/static/chunks/0kap_rdm2-lem.js","/litellm-asset-prefix/_next/static/chunks/09l_m9l1emin2.js","/litellm-asset-prefix/_next/static/chunks/1cea03gg5a_c7.js","/litellm-asset-prefix/_next/static/chunks/323l6h8s7ahat.js","/litellm-asset-prefix/_next/static/chunks/112n0hv3cc2rg.js","/litellm-asset-prefix/_next/static/chunks/199uwr871eene.js","/litellm-asset-prefix/_next/static/chunks/105643dvf00hu.js","/litellm-asset-prefix/_next/static/chunks/12wsfsljxg4xv.js","/litellm-asset-prefix/_next/static/chunks/22iools_e0k44.js","/litellm-asset-prefix/_next/static/chunks/0am68mi9t9cb6.js","/litellm-asset-prefix/_next/static/chunks/23-g73xaw3kap.js"],"default"] -6:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"OutletBoundary"] +2:I[347257,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ClientPageRoot"] +3:I[871135,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","/litellm-asset-prefix/_next/static/chunks/28md7sjkucknx.js","/litellm-asset-prefix/_next/static/chunks/2x96scis66zmk.js","/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","/litellm-asset-prefix/_next/static/chunks/1azbeyb626rh5.js","/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","/litellm-asset-prefix/_next/static/chunks/0vvnul8uez9kj.js","/litellm-asset-prefix/_next/static/chunks/0g8wwba6umbim.js","/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","/litellm-asset-prefix/_next/static/chunks/0fk0i3e2aixp7.js","/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","/litellm-asset-prefix/_next/static/chunks/17gy9d71tfqhd.js","/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","/litellm-asset-prefix/_next/static/chunks/0kx52ovlpa34x.js","/litellm-asset-prefix/_next/static/chunks/3drq2_k-jeio2.js","/litellm-asset-prefix/_next/static/chunks/2vnpyhxoamx0f.js","/litellm-asset-prefix/_next/static/chunks/323l6h8s7ahat.js","/litellm-asset-prefix/_next/static/chunks/3ib18qm2ox61z.js","/litellm-asset-prefix/_next/static/chunks/112n0hv3cc2rg.js","/litellm-asset-prefix/_next/static/chunks/199uwr871eene.js","/litellm-asset-prefix/_next/static/chunks/1cea03gg5a_c7.js","/litellm-asset-prefix/_next/static/chunks/1abwfud5uqxxq.js","/litellm-asset-prefix/_next/static/chunks/1uz3jt-tj9lkf.js","/litellm-asset-prefix/_next/static/chunks/2hu1vyy-5pv13.js","/litellm-asset-prefix/_next/static/chunks/105643dvf00hu.js","/litellm-asset-prefix/_next/static/chunks/23-g73xaw3kap.js","/litellm-asset-prefix/_next/static/chunks/2c90xukbd3il6.js","/litellm-asset-prefix/_next/static/chunks/12wsfsljxg4xv.js","/litellm-asset-prefix/_next/static/chunks/09l_m9l1emin2.js","/litellm-asset-prefix/_next/static/chunks/31cwj7vkk3gfz.js","/litellm-asset-prefix/_next/static/chunks/111jj26rg98nb.js","/litellm-asset-prefix/_next/static/chunks/3pu9plov1btip.js","/litellm-asset-prefix/_next/static/chunks/1l1182ye657-m.js","/litellm-asset-prefix/_next/static/chunks/1lndy6n7cwvrq.js","/litellm-asset-prefix/_next/static/chunks/2kt_m68ln2fyr.js","/litellm-asset-prefix/_next/static/chunks/0onjeur4drmh-.js"],"default"] +6:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"OutletBoundary"] 7:"$Sreact.suspense" -0:{"rsc":["$","$1","c",{"children":[["$","$L2",null,{"Component":"$3","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@4","$@5"]}}],[["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/00g6xfr4yow7h.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/2kcxwg1mpncp6.js","async":true}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/1fmx49l6q8v39.js","async":true}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/0ww76lz_0cphv.js","async":true}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/2uc2pi4ob086w.js","async":true}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/2hu1vyy-5pv13.js","async":true}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/2l25bmiiw9ixp.js","async":true}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/1uz3jt-tj9lkf.js","async":true}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/3drq2_k-jeio2.js","async":true}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/0dsiq_ok1yngk.js","async":true}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/3srzg1la93pwv.js","async":true}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/17nqbxvhztf3k.js","async":true}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/2c90xukbd3il6.js","async":true}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/0kap_rdm2-lem.js","async":true}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/09l_m9l1emin2.js","async":true}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/1cea03gg5a_c7.js","async":true}],["$","script","script-16",{"src":"/litellm-asset-prefix/_next/static/chunks/323l6h8s7ahat.js","async":true}],["$","script","script-17",{"src":"/litellm-asset-prefix/_next/static/chunks/112n0hv3cc2rg.js","async":true}],["$","script","script-18",{"src":"/litellm-asset-prefix/_next/static/chunks/199uwr871eene.js","async":true}],["$","script","script-19",{"src":"/litellm-asset-prefix/_next/static/chunks/105643dvf00hu.js","async":true}],["$","script","script-20",{"src":"/litellm-asset-prefix/_next/static/chunks/12wsfsljxg4xv.js","async":true}],["$","script","script-21",{"src":"/litellm-asset-prefix/_next/static/chunks/22iools_e0k44.js","async":true}],["$","script","script-22",{"src":"/litellm-asset-prefix/_next/static/chunks/0am68mi9t9cb6.js","async":true}],["$","script","script-23",{"src":"/litellm-asset-prefix/_next/static/chunks/23-g73xaw3kap.js","async":true}]],["$","$L6",null,{"children":["$","$7",null,{"name":"Next.MetadataOutlet","children":"$@8"}]}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"qXutWsQW5C1Pf62WxTkEI"} +0:{"rsc":["$","$1","c",{"children":[["$","$L2",null,{"Component":"$3","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@4","$@5"]}}],[["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/0kx52ovlpa34x.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/3drq2_k-jeio2.js","async":true}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/2vnpyhxoamx0f.js","async":true}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/323l6h8s7ahat.js","async":true}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/3ib18qm2ox61z.js","async":true}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/112n0hv3cc2rg.js","async":true}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/199uwr871eene.js","async":true}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/1cea03gg5a_c7.js","async":true}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/1abwfud5uqxxq.js","async":true}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/1uz3jt-tj9lkf.js","async":true}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/2hu1vyy-5pv13.js","async":true}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/105643dvf00hu.js","async":true}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/23-g73xaw3kap.js","async":true}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/2c90xukbd3il6.js","async":true}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/12wsfsljxg4xv.js","async":true}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/09l_m9l1emin2.js","async":true}],["$","script","script-16",{"src":"/litellm-asset-prefix/_next/static/chunks/31cwj7vkk3gfz.js","async":true}],["$","script","script-17",{"src":"/litellm-asset-prefix/_next/static/chunks/111jj26rg98nb.js","async":true}],["$","script","script-18",{"src":"/litellm-asset-prefix/_next/static/chunks/3pu9plov1btip.js","async":true}],["$","script","script-19",{"src":"/litellm-asset-prefix/_next/static/chunks/1l1182ye657-m.js","async":true}],["$","script","script-20",{"src":"/litellm-asset-prefix/_next/static/chunks/1lndy6n7cwvrq.js","async":true}],["$","script","script-21",{"src":"/litellm-asset-prefix/_next/static/chunks/2kt_m68ln2fyr.js","async":true}],["$","script","script-22",{"src":"/litellm-asset-prefix/_next/static/chunks/0onjeur4drmh-.js","async":true}]],["$","$L6",null,{"children":["$","$7",null,{"name":"Next.MetadataOutlet","children":"$@8"}]}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"8bC_aTV0H1nUtrFDXkfaB"} 4:{} 5:"$0:rsc:props:children:0:props:serverProvidedParams:params" 8:null diff --git a/litellm/proxy/_experimental/out/__next.!KGRhc2hib2FyZCk.txt b/litellm/proxy/_experimental/out/__next.!KGRhc2hib2FyZCk.txt index ef8a75b27ce..ec0a813adc3 100644 --- a/litellm/proxy/_experimental/out/__next.!KGRhc2hib2FyZCk.txt +++ b/litellm/proxy/_experimental/out/__next.!KGRhc2hib2FyZCk.txt @@ -1,7 +1,7 @@ 1:"$Sreact.fragment" -2:I[92825,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ClientSegmentRoot"] -3:I[216370,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","/litellm-asset-prefix/_next/static/chunks/1ioy8obpggx93.js","/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","/litellm-asset-prefix/_next/static/chunks/1zr7rrk4wkmju.js","/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","/litellm-asset-prefix/_next/static/chunks/0fk0i3e2aixp7.js","/litellm-asset-prefix/_next/static/chunks/0zduf1gntl_f8.js","/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","/litellm-asset-prefix/_next/static/chunks/1a0bgy7kzrj91.js","/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","/litellm-asset-prefix/_next/static/chunks/0g8wwba6umbim.js","/litellm-asset-prefix/_next/static/chunks/1di-caw05k3tq.js"],"default"] -4:I[339756,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] -5:I[837457,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] -0:{"rsc":["$","$1","c",{"children":[[["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/1ioy8obpggx93.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","async":true}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/1zr7rrk4wkmju.js","async":true}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","async":true}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","async":true}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/0fk0i3e2aixp7.js","async":true}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/0zduf1gntl_f8.js","async":true}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","async":true}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","async":true}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","async":true}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","async":true}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","async":true}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/1a0bgy7kzrj91.js","async":true}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","async":true}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/0g8wwba6umbim.js","async":true}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/1di-caw05k3tq.js","async":true}]],["$","$L2",null,{"Component":"$3","slots":{"children":["$","$L4",null,{"parallelRouterKey":"children","template":["$","$L5",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]},"serverProvidedParams":{"params":{},"promises":["$@6"]}}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"qXutWsQW5C1Pf62WxTkEI"} +2:I[92825,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ClientSegmentRoot"] +3:I[216370,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","/litellm-asset-prefix/_next/static/chunks/28md7sjkucknx.js","/litellm-asset-prefix/_next/static/chunks/2x96scis66zmk.js","/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","/litellm-asset-prefix/_next/static/chunks/1azbeyb626rh5.js","/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","/litellm-asset-prefix/_next/static/chunks/0vvnul8uez9kj.js","/litellm-asset-prefix/_next/static/chunks/0g8wwba6umbim.js","/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","/litellm-asset-prefix/_next/static/chunks/0fk0i3e2aixp7.js","/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","/litellm-asset-prefix/_next/static/chunks/17gy9d71tfqhd.js","/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js"],"default"] +4:I[339756,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] +5:I[837457,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] +0:{"rsc":["$","$1","c",{"children":[[["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/28md7sjkucknx.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/2x96scis66zmk.js","async":true}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","async":true}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/1azbeyb626rh5.js","async":true}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","async":true}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/0vvnul8uez9kj.js","async":true}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/0g8wwba6umbim.js","async":true}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","async":true}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","async":true}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","async":true}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","async":true}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","async":true}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/0fk0i3e2aixp7.js","async":true}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","async":true}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/17gy9d71tfqhd.js","async":true}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","async":true}]],["$","$L2",null,{"Component":"$3","slots":{"children":["$","$L4",null,{"parallelRouterKey":"children","template":["$","$L5",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]},"serverProvidedParams":{"params":{},"promises":["$@6"]}}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"8bC_aTV0H1nUtrFDXkfaB"} 6:"$0:rsc:props:children:1:props:serverProvidedParams:params" diff --git a/litellm/proxy/_experimental/out/__next._full.txt b/litellm/proxy/_experimental/out/__next._full.txt index 3ee486db39b..8b9e5e629da 100644 --- a/litellm/proxy/_experimental/out/__next._full.txt +++ b/litellm/proxy/_experimental/out/__next._full.txt @@ -1,32 +1,32 @@ 1:"$Sreact.fragment" -2:I[867271,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] -3:I[71195,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] -4:I[557951,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"AuthProvider"] -5:I[339756,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] -6:I[837457,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] -7:I[92825,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ClientSegmentRoot"] -8:I[216370,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","/litellm-asset-prefix/_next/static/chunks/1ioy8obpggx93.js","/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","/litellm-asset-prefix/_next/static/chunks/1zr7rrk4wkmju.js","/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","/litellm-asset-prefix/_next/static/chunks/0fk0i3e2aixp7.js","/litellm-asset-prefix/_next/static/chunks/0zduf1gntl_f8.js","/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","/litellm-asset-prefix/_next/static/chunks/1a0bgy7kzrj91.js","/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","/litellm-asset-prefix/_next/static/chunks/0g8wwba6umbim.js","/litellm-asset-prefix/_next/static/chunks/1di-caw05k3tq.js"],"default"] -e:I[168027,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default",1] +2:I[867271,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] +3:I[71195,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] +4:I[557951,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"AuthProvider"] +5:I[339756,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] +6:I[837457,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] +7:I[92825,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ClientSegmentRoot"] +8:I[216370,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","/litellm-asset-prefix/_next/static/chunks/28md7sjkucknx.js","/litellm-asset-prefix/_next/static/chunks/2x96scis66zmk.js","/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","/litellm-asset-prefix/_next/static/chunks/1azbeyb626rh5.js","/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","/litellm-asset-prefix/_next/static/chunks/0vvnul8uez9kj.js","/litellm-asset-prefix/_next/static/chunks/0g8wwba6umbim.js","/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","/litellm-asset-prefix/_next/static/chunks/0fk0i3e2aixp7.js","/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","/litellm-asset-prefix/_next/static/chunks/17gy9d71tfqhd.js","/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js"],"default"] +e:I[168027,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default",1] :HL["/litellm-asset-prefix/_next/static/chunks/1kid9zr1--h6y.css","style"] -:HL["/litellm-asset-prefix/_next/static/chunks/3254j4ut19q6_.css","style"] +:HL["/litellm-asset-prefix/_next/static/chunks/169bqf_mz3j8m.css","style"] :HL["/litellm-asset-prefix/_next/static/media/83afe278b6a6bb3c-s.p.2bn3s6zvc0dyp.woff2","font",{"crossOrigin":"","type":"font/woff2"}] -0:{"P":null,"c":["",""],"q":"","i":false,"f":[[["",{"children":["(dashboard)",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",16],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/1kid9zr1--h6y.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/3254j4ut19q6_.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"inter_5972bc34-module__OU16Qa__className","children":["$","$L2",null,{"children":["$","$L3",null,{"children":["$","$L4",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L6",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]}]}]]}],{"children":[["$","$1","c",{"children":[[["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/1ioy8obpggx93.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/1zr7rrk4wkmju.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/0fk0i3e2aixp7.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/0zduf1gntl_f8.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","async":true,"nonce":"$undefined"}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","async":true,"nonce":"$undefined"}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","async":true,"nonce":"$undefined"}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/1a0bgy7kzrj91.js","async":true,"nonce":"$undefined"}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","async":true,"nonce":"$undefined"}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/0g8wwba6umbim.js","async":true,"nonce":"$undefined"}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/1di-caw05k3tq.js","async":true,"nonce":"$undefined"}]],["$","$L7",null,{"Component":"$8","slots":{"children":["$","$L5",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":"$L9","templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$La","forbidden":"$undefined","unauthorized":"$undefined"}]},"serverProvidedParams":{"params":{},"promises":["$@b"]}}]]}],{"children":["$Lc",{},null,false,null]},null,false,null]},null,false,null],"$Ld",false]],"m":"$undefined","G":["$e",["$Lf","$L10"]],"S":true,"h":null,"s":"$undefined","l":"$undefined","p":"$undefined","d":"$undefined","b":"qXutWsQW5C1Pf62WxTkEI"} -11:I[347257,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ClientPageRoot"] -12:I[871135,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","/litellm-asset-prefix/_next/static/chunks/1ioy8obpggx93.js","/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","/litellm-asset-prefix/_next/static/chunks/1zr7rrk4wkmju.js","/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","/litellm-asset-prefix/_next/static/chunks/0fk0i3e2aixp7.js","/litellm-asset-prefix/_next/static/chunks/0zduf1gntl_f8.js","/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","/litellm-asset-prefix/_next/static/chunks/1a0bgy7kzrj91.js","/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","/litellm-asset-prefix/_next/static/chunks/0g8wwba6umbim.js","/litellm-asset-prefix/_next/static/chunks/1di-caw05k3tq.js","/litellm-asset-prefix/_next/static/chunks/00g6xfr4yow7h.js","/litellm-asset-prefix/_next/static/chunks/2kcxwg1mpncp6.js","/litellm-asset-prefix/_next/static/chunks/1fmx49l6q8v39.js","/litellm-asset-prefix/_next/static/chunks/0ww76lz_0cphv.js","/litellm-asset-prefix/_next/static/chunks/2uc2pi4ob086w.js","/litellm-asset-prefix/_next/static/chunks/2hu1vyy-5pv13.js","/litellm-asset-prefix/_next/static/chunks/2l25bmiiw9ixp.js","/litellm-asset-prefix/_next/static/chunks/1uz3jt-tj9lkf.js","/litellm-asset-prefix/_next/static/chunks/3drq2_k-jeio2.js","/litellm-asset-prefix/_next/static/chunks/0dsiq_ok1yngk.js","/litellm-asset-prefix/_next/static/chunks/3srzg1la93pwv.js","/litellm-asset-prefix/_next/static/chunks/17nqbxvhztf3k.js","/litellm-asset-prefix/_next/static/chunks/2c90xukbd3il6.js","/litellm-asset-prefix/_next/static/chunks/0kap_rdm2-lem.js","/litellm-asset-prefix/_next/static/chunks/09l_m9l1emin2.js","/litellm-asset-prefix/_next/static/chunks/1cea03gg5a_c7.js","/litellm-asset-prefix/_next/static/chunks/323l6h8s7ahat.js","/litellm-asset-prefix/_next/static/chunks/112n0hv3cc2rg.js","/litellm-asset-prefix/_next/static/chunks/199uwr871eene.js","/litellm-asset-prefix/_next/static/chunks/105643dvf00hu.js","/litellm-asset-prefix/_next/static/chunks/12wsfsljxg4xv.js","/litellm-asset-prefix/_next/static/chunks/22iools_e0k44.js","/litellm-asset-prefix/_next/static/chunks/0am68mi9t9cb6.js","/litellm-asset-prefix/_next/static/chunks/23-g73xaw3kap.js"],"default"] -15:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"OutletBoundary"] +0:{"P":null,"c":["",""],"q":"","i":false,"f":[[["",{"children":["(dashboard)",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",16],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/1kid9zr1--h6y.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/169bqf_mz3j8m.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"inter_5972bc34-module__OU16Qa__className","children":["$","$L2",null,{"children":["$","$L3",null,{"children":["$","$L4",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L6",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]}]}]]}],{"children":[["$","$1","c",{"children":[[["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/28md7sjkucknx.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/2x96scis66zmk.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/1azbeyb626rh5.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/0vvnul8uez9kj.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/0g8wwba6umbim.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","async":true,"nonce":"$undefined"}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","async":true,"nonce":"$undefined"}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","async":true,"nonce":"$undefined"}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/0fk0i3e2aixp7.js","async":true,"nonce":"$undefined"}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","async":true,"nonce":"$undefined"}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/17gy9d71tfqhd.js","async":true,"nonce":"$undefined"}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","async":true,"nonce":"$undefined"}]],["$","$L7",null,{"Component":"$8","slots":{"children":["$","$L5",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":"$L9","templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$La","forbidden":"$undefined","unauthorized":"$undefined"}]},"serverProvidedParams":{"params":{},"promises":["$@b"]}}]]}],{"children":["$Lc",{},null,false,null]},null,false,null]},null,false,null],"$Ld",false]],"m":"$undefined","G":["$e",["$Lf","$L10"]],"S":true,"h":null,"s":"$undefined","l":"$undefined","p":"$undefined","d":"$undefined","b":"8bC_aTV0H1nUtrFDXkfaB"} +11:I[347257,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ClientPageRoot"] +12:I[871135,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","/litellm-asset-prefix/_next/static/chunks/28md7sjkucknx.js","/litellm-asset-prefix/_next/static/chunks/2x96scis66zmk.js","/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","/litellm-asset-prefix/_next/static/chunks/1azbeyb626rh5.js","/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","/litellm-asset-prefix/_next/static/chunks/0vvnul8uez9kj.js","/litellm-asset-prefix/_next/static/chunks/0g8wwba6umbim.js","/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","/litellm-asset-prefix/_next/static/chunks/0fk0i3e2aixp7.js","/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","/litellm-asset-prefix/_next/static/chunks/17gy9d71tfqhd.js","/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","/litellm-asset-prefix/_next/static/chunks/0kx52ovlpa34x.js","/litellm-asset-prefix/_next/static/chunks/3drq2_k-jeio2.js","/litellm-asset-prefix/_next/static/chunks/2vnpyhxoamx0f.js","/litellm-asset-prefix/_next/static/chunks/323l6h8s7ahat.js","/litellm-asset-prefix/_next/static/chunks/3ib18qm2ox61z.js","/litellm-asset-prefix/_next/static/chunks/112n0hv3cc2rg.js","/litellm-asset-prefix/_next/static/chunks/199uwr871eene.js","/litellm-asset-prefix/_next/static/chunks/1cea03gg5a_c7.js","/litellm-asset-prefix/_next/static/chunks/1abwfud5uqxxq.js","/litellm-asset-prefix/_next/static/chunks/1uz3jt-tj9lkf.js","/litellm-asset-prefix/_next/static/chunks/2hu1vyy-5pv13.js","/litellm-asset-prefix/_next/static/chunks/105643dvf00hu.js","/litellm-asset-prefix/_next/static/chunks/23-g73xaw3kap.js","/litellm-asset-prefix/_next/static/chunks/2c90xukbd3il6.js","/litellm-asset-prefix/_next/static/chunks/12wsfsljxg4xv.js","/litellm-asset-prefix/_next/static/chunks/09l_m9l1emin2.js","/litellm-asset-prefix/_next/static/chunks/31cwj7vkk3gfz.js","/litellm-asset-prefix/_next/static/chunks/111jj26rg98nb.js","/litellm-asset-prefix/_next/static/chunks/3pu9plov1btip.js","/litellm-asset-prefix/_next/static/chunks/1l1182ye657-m.js","/litellm-asset-prefix/_next/static/chunks/1lndy6n7cwvrq.js","/litellm-asset-prefix/_next/static/chunks/2kt_m68ln2fyr.js","/litellm-asset-prefix/_next/static/chunks/0onjeur4drmh-.js"],"default"] +15:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"OutletBoundary"] 16:"$Sreact.suspense" -18:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ViewportBoundary"] -1a:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"MetadataBoundary"] +18:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ViewportBoundary"] +1a:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"MetadataBoundary"] 9:["$","$L6",null,{}] a:[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],[]] -c:["$","$1","c",{"children":[["$","$L11",null,{"Component":"$12","serverProvidedParams":{"searchParams":{},"params":"$0:f:0:1:1:children:0:props:children:1:props:serverProvidedParams:params","promises":["$@13","$@14"]}}],[["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/00g6xfr4yow7h.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/2kcxwg1mpncp6.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/1fmx49l6q8v39.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/0ww76lz_0cphv.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/2uc2pi4ob086w.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/2hu1vyy-5pv13.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/2l25bmiiw9ixp.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/1uz3jt-tj9lkf.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/3drq2_k-jeio2.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/0dsiq_ok1yngk.js","async":true,"nonce":"$undefined"}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/3srzg1la93pwv.js","async":true,"nonce":"$undefined"}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/17nqbxvhztf3k.js","async":true,"nonce":"$undefined"}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/2c90xukbd3il6.js","async":true,"nonce":"$undefined"}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/0kap_rdm2-lem.js","async":true,"nonce":"$undefined"}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/09l_m9l1emin2.js","async":true,"nonce":"$undefined"}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/1cea03gg5a_c7.js","async":true,"nonce":"$undefined"}],["$","script","script-16",{"src":"/litellm-asset-prefix/_next/static/chunks/323l6h8s7ahat.js","async":true,"nonce":"$undefined"}],["$","script","script-17",{"src":"/litellm-asset-prefix/_next/static/chunks/112n0hv3cc2rg.js","async":true,"nonce":"$undefined"}],["$","script","script-18",{"src":"/litellm-asset-prefix/_next/static/chunks/199uwr871eene.js","async":true,"nonce":"$undefined"}],["$","script","script-19",{"src":"/litellm-asset-prefix/_next/static/chunks/105643dvf00hu.js","async":true,"nonce":"$undefined"}],["$","script","script-20",{"src":"/litellm-asset-prefix/_next/static/chunks/12wsfsljxg4xv.js","async":true,"nonce":"$undefined"}],["$","script","script-21",{"src":"/litellm-asset-prefix/_next/static/chunks/22iools_e0k44.js","async":true,"nonce":"$undefined"}],["$","script","script-22",{"src":"/litellm-asset-prefix/_next/static/chunks/0am68mi9t9cb6.js","async":true,"nonce":"$undefined"}],["$","script","script-23",{"src":"/litellm-asset-prefix/_next/static/chunks/23-g73xaw3kap.js","async":true,"nonce":"$undefined"}]],["$","$L15",null,{"children":["$","$16",null,{"name":"Next.MetadataOutlet","children":"$@17"}]}]]}] +c:["$","$1","c",{"children":[["$","$L11",null,{"Component":"$12","serverProvidedParams":{"searchParams":{},"params":"$0:f:0:1:1:children:0:props:children:1:props:serverProvidedParams:params","promises":["$@13","$@14"]}}],[["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/0kx52ovlpa34x.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/3drq2_k-jeio2.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/2vnpyhxoamx0f.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/323l6h8s7ahat.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/3ib18qm2ox61z.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/112n0hv3cc2rg.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/199uwr871eene.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/1cea03gg5a_c7.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/1abwfud5uqxxq.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/1uz3jt-tj9lkf.js","async":true,"nonce":"$undefined"}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/2hu1vyy-5pv13.js","async":true,"nonce":"$undefined"}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/105643dvf00hu.js","async":true,"nonce":"$undefined"}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/23-g73xaw3kap.js","async":true,"nonce":"$undefined"}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/2c90xukbd3il6.js","async":true,"nonce":"$undefined"}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/12wsfsljxg4xv.js","async":true,"nonce":"$undefined"}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/09l_m9l1emin2.js","async":true,"nonce":"$undefined"}],["$","script","script-16",{"src":"/litellm-asset-prefix/_next/static/chunks/31cwj7vkk3gfz.js","async":true,"nonce":"$undefined"}],["$","script","script-17",{"src":"/litellm-asset-prefix/_next/static/chunks/111jj26rg98nb.js","async":true,"nonce":"$undefined"}],["$","script","script-18",{"src":"/litellm-asset-prefix/_next/static/chunks/3pu9plov1btip.js","async":true,"nonce":"$undefined"}],["$","script","script-19",{"src":"/litellm-asset-prefix/_next/static/chunks/1l1182ye657-m.js","async":true,"nonce":"$undefined"}],["$","script","script-20",{"src":"/litellm-asset-prefix/_next/static/chunks/1lndy6n7cwvrq.js","async":true,"nonce":"$undefined"}],["$","script","script-21",{"src":"/litellm-asset-prefix/_next/static/chunks/2kt_m68ln2fyr.js","async":true,"nonce":"$undefined"}],["$","script","script-22",{"src":"/litellm-asset-prefix/_next/static/chunks/0onjeur4drmh-.js","async":true,"nonce":"$undefined"}]],["$","$L15",null,{"children":["$","$16",null,{"name":"Next.MetadataOutlet","children":"$@17"}]}]]}] d:["$","$1","h",{"children":[null,["$","$L18",null,{"children":"$L19"}],["$","div",null,{"hidden":true,"children":["$","$L1a",null,{"children":["$","$16",null,{"name":"Next.Metadata","children":"$L1b"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}] f:["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/1kid9zr1--h6y.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}] -10:["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/3254j4ut19q6_.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}] +10:["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/169bqf_mz3j8m.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}] b:"$0:f:0:1:1:children:0:props:children:1:props:serverProvidedParams:params" 13:{} 14:"$0:f:0:1:1:children:0:props:children:1:props:serverProvidedParams:params" 19:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]] -1c:I[27201,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"IconMark"] +1c:I[27201,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"IconMark"] 17:null 1b:[["$","title","0",{"children":"LiteLLM Dashboard"}],["$","meta","1",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.3arlap5n8tyzg.ico","sizes":"48x48","type":"image/x-icon"}],["$","link","3",{"rel":"icon","href":"/get_favicon"}],["$","$L1c","4",{}]] diff --git a/litellm/proxy/_experimental/out/__next._head.txt b/litellm/proxy/_experimental/out/__next._head.txt index 9b12cf54d0c..2a08c7adf14 100644 --- a/litellm/proxy/_experimental/out/__next._head.txt +++ b/litellm/proxy/_experimental/out/__next._head.txt @@ -1,6 +1,6 @@ 1:"$Sreact.fragment" -2:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ViewportBoundary"] -3:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"MetadataBoundary"] +2:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ViewportBoundary"] +3:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"MetadataBoundary"] 4:"$Sreact.suspense" -5:I[27201,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"IconMark"] -0:{"rsc":["$","$1","h",{"children":[null,["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"LiteLLM Dashboard"}],["$","meta","1",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.3arlap5n8tyzg.ico","sizes":"48x48","type":"image/x-icon"}],["$","link","3",{"rel":"icon","href":"/get_favicon"}],["$","$L5","4",{}]]}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"qXutWsQW5C1Pf62WxTkEI"} +5:I[27201,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"IconMark"] +0:{"rsc":["$","$1","h",{"children":[null,["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"LiteLLM Dashboard"}],["$","meta","1",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.3arlap5n8tyzg.ico","sizes":"48x48","type":"image/x-icon"}],["$","link","3",{"rel":"icon","href":"/get_favicon"}],["$","$L5","4",{}]]}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"8bC_aTV0H1nUtrFDXkfaB"} diff --git a/litellm/proxy/_experimental/out/__next._index.txt b/litellm/proxy/_experimental/out/__next._index.txt index 8649901b01b..365bbbdcfef 100644 --- a/litellm/proxy/_experimental/out/__next._index.txt +++ b/litellm/proxy/_experimental/out/__next._index.txt @@ -1,9 +1,9 @@ 1:"$Sreact.fragment" -2:I[867271,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] -3:I[71195,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] -4:I[557951,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"AuthProvider"] -5:I[339756,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] -6:I[837457,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] +2:I[867271,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] +3:I[71195,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] +4:I[557951,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"AuthProvider"] +5:I[339756,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] +6:I[837457,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] :HL["/litellm-asset-prefix/_next/static/chunks/1kid9zr1--h6y.css","style"] -:HL["/litellm-asset-prefix/_next/static/chunks/3254j4ut19q6_.css","style"] -0:{"rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/1kid9zr1--h6y.css","precedence":"next"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/3254j4ut19q6_.css","precedence":"next"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","async":true}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","async":true}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"inter_5972bc34-module__OU16Qa__className","children":["$","$L2",null,{"children":["$","$L3",null,{"children":["$","$L4",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","template":["$","$L6",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]}]}]}]}]}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"qXutWsQW5C1Pf62WxTkEI"} +:HL["/litellm-asset-prefix/_next/static/chunks/169bqf_mz3j8m.css","style"] +0:{"rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/1kid9zr1--h6y.css","precedence":"next"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/169bqf_mz3j8m.css","precedence":"next"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/32_ulchi2_aad.js","async":true}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","async":true}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"inter_5972bc34-module__OU16Qa__className","children":["$","$L2",null,{"children":["$","$L3",null,{"children":["$","$L4",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","template":["$","$L6",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]}]}]}]}]}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"8bC_aTV0H1nUtrFDXkfaB"} diff --git a/litellm/proxy/_experimental/out/__next._tree.txt b/litellm/proxy/_experimental/out/__next._tree.txt index db0015f1f41..442efb21b45 100644 --- a/litellm/proxy/_experimental/out/__next._tree.txt +++ b/litellm/proxy/_experimental/out/__next._tree.txt @@ -1,4 +1,4 @@ :HL["/litellm-asset-prefix/_next/static/chunks/1kid9zr1--h6y.css","style"] -:HL["/litellm-asset-prefix/_next/static/chunks/3254j4ut19q6_.css","style"] +:HL["/litellm-asset-prefix/_next/static/chunks/169bqf_mz3j8m.css","style"] :HL["/litellm-asset-prefix/_next/static/media/83afe278b6a6bb3c-s.p.2bn3s6zvc0dyp.woff2","font",{"crossOrigin":"","type":"font/woff2"}] -0:{"tree":{"name":"","param":null,"prefetchHints":16,"slots":{"children":{"name":"(dashboard)","param":null,"prefetchHints":0,"slots":{"children":{"name":"__PAGE__","param":null,"prefetchHints":0,"slots":null}}}}},"staleTime":300,"buildId":"qXutWsQW5C1Pf62WxTkEI"} +0:{"tree":{"name":"","param":null,"prefetchHints":16,"slots":{"children":{"name":"(dashboard)","param":null,"prefetchHints":0,"slots":{"children":{"name":"__PAGE__","param":null,"prefetchHints":0,"slots":null}}}}},"staleTime":300,"buildId":"8bC_aTV0H1nUtrFDXkfaB"} diff --git a/litellm/proxy/_experimental/out/_next/static/qXutWsQW5C1Pf62WxTkEI/_buildManifest.js b/litellm/proxy/_experimental/out/_next/static/8bC_aTV0H1nUtrFDXkfaB/_buildManifest.js similarity index 100% rename from litellm/proxy/_experimental/out/_next/static/qXutWsQW5C1Pf62WxTkEI/_buildManifest.js rename to litellm/proxy/_experimental/out/_next/static/8bC_aTV0H1nUtrFDXkfaB/_buildManifest.js diff --git a/litellm/proxy/_experimental/out/_next/static/qXutWsQW5C1Pf62WxTkEI/_clientMiddlewareManifest.js b/litellm/proxy/_experimental/out/_next/static/8bC_aTV0H1nUtrFDXkfaB/_clientMiddlewareManifest.js similarity index 100% rename from litellm/proxy/_experimental/out/_next/static/qXutWsQW5C1Pf62WxTkEI/_clientMiddlewareManifest.js rename to litellm/proxy/_experimental/out/_next/static/8bC_aTV0H1nUtrFDXkfaB/_clientMiddlewareManifest.js diff --git a/litellm/proxy/_experimental/out/_next/static/qXutWsQW5C1Pf62WxTkEI/_ssgManifest.js b/litellm/proxy/_experimental/out/_next/static/8bC_aTV0H1nUtrFDXkfaB/_ssgManifest.js similarity index 100% rename from litellm/proxy/_experimental/out/_next/static/qXutWsQW5C1Pf62WxTkEI/_ssgManifest.js rename to litellm/proxy/_experimental/out/_next/static/8bC_aTV0H1nUtrFDXkfaB/_ssgManifest.js diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/00g6xfr4yow7h.js b/litellm/proxy/_experimental/out/_next/static/chunks/00g6xfr4yow7h.js deleted file mode 100644 index 248cab25929..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/00g6xfr4yow7h.js +++ /dev/null @@ -1 +0,0 @@ -(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,263005,e=>{"use strict";var t=e.i(843476);e.s(["PageHeader",0,function({title:e,subtitle:a,icon:l,actions:i}){return(0,t.jsxs)("div",{className:"flex flex-wrap items-start justify-between gap-4",children:[(0,t.jsxs)("div",{className:"flex items-center gap-2.5",children:[null!=l&&(0,t.jsx)("span",{className:"flex flex-none items-center text-foreground",children:l}),(0,t.jsxs)("div",{className:"min-w-0",children:[(0,t.jsx)("h1",{className:"text-xl font-semibold tracking-tight text-foreground",children:e}),null!=a&&(0,t.jsx)("p",{className:"mt-0.5 text-sm text-muted-foreground",children:a})]})]}),null!=i&&(0,t.jsx)("div",{className:"flex items-center gap-2",children:i})]})}])},655063,e=>{"use strict";var t=e.i(399029),a=e.i(271645);e.s(["useDebouncedValue",0,function(e,l,i){let[r,s,n]=(0,t.useDebouncedState)(e,l,i);return(0,a.useEffect)(()=>{s(e)},[e,s]),[r,n]}])},624687,e=>{"use strict";var t=e.i(843476),a=e.i(271645),l=e.i(115504);let i=a.forwardRef(({className:e,...a},i)=>(0,t.jsx)("textarea",{ref:i,"data-slot":"textarea",className:(0,l.cn)("flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",e),...a}));i.displayName="Textarea",e.s(["Textarea",0,i])},950594,e=>{"use strict";var t=e.i(843476),a=e.i(271645),l=e.i(115504),i=e.i(519455),r=e.i(793479),s=e.i(624687);let n=(0,l.cva)({base:"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",variants:{align:{"inline-start":"order-first pl-2 has-[>button]:-ml-1 has-[>kbd]:ml-[-0.15rem]","inline-end":"order-last pr-2 has-[>button]:-mr-1 has-[>kbd]:mr-[-0.15rem]","block-start":"order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2","block-end":"order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2"}},defaultVariants:{align:"inline-start"}}),o=(0,l.cva)({base:"flex items-center gap-2 text-sm shadow-none",variants:{size:{xs:"h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",sm:"","icon-xs":"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0","icon-sm":"size-8 p-0 has-[>svg]:p-0"}},defaultVariants:{size:"xs"}}),d=a.forwardRef(({className:e,type:a="button",variant:r="ghost",size:s="xs",...n},d)=>(0,t.jsx)(i.Button,{ref:d,type:a,"data-size":s,variant:r,className:(0,l.cn)(o({size:s}),e),...n}));d.displayName="InputGroupButton";let u=a.forwardRef(({className:e,...a},i)=>(0,t.jsx)(r.Input,{ref:i,"data-slot":"input-group-control",className:(0,l.cn)("flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",e),...a}));u.displayName="InputGroupInput",a.forwardRef(({className:e,...a},i)=>(0,t.jsx)(s.Textarea,{ref:i,"data-slot":"input-group-control",className:(0,l.cn)("flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",e),...a})).displayName="InputGroupTextarea",e.s(["InputGroup",0,function({className:e,...a}){return(0,t.jsx)("div",{"data-slot":"input-group",role:"group",className:(0,l.cn)("group/input-group relative flex h-9 w-full min-w-0 items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",e),...a})},"InputGroupAddon",0,function({className:e,align:a="inline-start",...i}){return(0,t.jsx)("div",{role:"group","data-slot":"input-group-addon","data-align":a,className:(0,l.cn)(n({align:a}),e),onClick:e=>{e.target.closest("button")||e.currentTarget.parentElement?.querySelector("input")?.focus()},...i})},"InputGroupButton",0,d,"InputGroupInput",0,u,"InputGroupText",0,function({className:e,...a}){return(0,t.jsx)("span",{className:(0,l.cn)("flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",e),...a})}])},552546,e=>{"use strict";var t=e.i(843476),a=e.i(131792);let l=(e,t)=>{let a=t.trim().toLowerCase();return!a||e.label.toLowerCase().includes(a)||(e.sublabel?.toLowerCase().includes(a)??!1)};e.s(["SearchSelect",0,function({options:e,value:i,onValueChange:r,placeholder:s="Select…",emptyText:n="No results",disabled:o=!1,className:d}){let u=e.find(e=>e.value===i)??null;return(0,t.jsxs)(a.Combobox,{items:e,value:u,onValueChange:e=>r(e?.value??""),isItemEqualToValue:(e,t)=>e.value===t.value,itemToStringLabel:e=>e.label,filter:l,disabled:o,children:[(0,t.jsx)(a.ComboboxInput,{placeholder:s,showClear:null!=i&&""!==i,className:`w-full ${d??""}`}),(0,t.jsxs)(a.ComboboxContent,{children:[(0,t.jsx)(a.ComboboxEmpty,{children:n}),(0,t.jsx)(a.ComboboxList,{children:e=>(0,t.jsx)(a.ComboboxItem,{value:e,children:(0,t.jsxs)("span",{className:"flex min-w-0 flex-col",children:[(0,t.jsx)("span",{className:"truncate",children:e.label}),null!=e.sublabel&&""!==e.sublabel&&(0,t.jsx)("span",{className:"truncate text-xs text-muted-foreground",children:e.sublabel})]})},e.value)})]})]})}])},611363,e=>{"use strict";e.s(["navigateWithParams",0,function(e){let t=new URLSearchParams(window.location.search);e(t);let a=t.toString(),l=a?`${window.location.pathname}?${a}`:window.location.pathname;window.history.pushState(null,"",l)}])},502501,e=>{"use strict";var t=e.i(843476),a=e.i(785242),l=e.i(135214),i=e.i(268004),r=e.i(309426),s=e.i(350967),n=e.i(947293),o=e.i(271645),d=e.i(602869);let u=async(e,t,a,l,i)=>{i("Admin"!=a&&"Admin Viewer"!=a?await (0,d.teamListCall)(e,l?.organization_id||null,t):await (0,d.teamListCall)(e,l?.organization_id||null))};var c=e.i(702597),m=e.i(618566),g=e.i(611363),p=e.i(266027),x=e.i(207082),h=e.i(109799),f=e.i(741466);e.i(707701);var b=e.i(807235),v=e.i(981080),y=e.i(531649),_=e.i(552546),w=e.i(263005),k=e.i(793479),j=e.i(655063),S=e.i(465261),C=e.i(20147),I=e.i(827252),N=e.i(282786),z=e.i(898586),D=e.i(494862),T=e.i(302747);e.i(622826);var U=e.i(200208),E=e.i(399536),A=e.i(997422),R=e.i(547227),K=e.i(630500),V=e.i(112179),M=e.i(304911);let L=[{id:"spend",label:"Spend"},{id:"max_budget",label:"Budget"}],P=({userAlias:e,userEmail:a,userId:l,width:i})=>{let r=e||a||l,s="default_user_id"===l,n=(0,t.jsx)("div",{className:"flex flex-col gap-2 text-xs min-w-[200px] max-w-[300px]",children:[{label:"User Alias",value:e},{label:"User Email",value:a},{label:"User ID",value:l}].map(({label:e,value:a})=>(0,t.jsxs)("div",{className:"flex flex-col min-w-0",children:[(0,t.jsx)("span",{className:"text-gray-400",children:e}),a?(0,t.jsx)(z.Typography.Text,{className:"font-mono text-xs",ellipsis:{tooltip:a},copyable:!0,children:a}):(0,t.jsx)("span",{className:"font-mono",children:"-"})]},e))});return!s||e||a?(0,t.jsx)(N.Popover,{content:n,trigger:"hover",placement:"bottomLeft",children:(0,t.jsx)("span",{className:"font-mono text-xs truncate block cursor-default",style:{maxWidth:i,overflow:"hidden"},children:r||"-"})}):(0,t.jsx)(N.Popover,{content:n,trigger:"hover",placement:"bottomLeft",children:(0,t.jsx)("span",{className:"cursor-default",children:(0,t.jsx)(M.default,{userId:l})})})},B=({label:e,tooltip:a})=>(0,t.jsxs)("span",{className:"flex items-center gap-1",children:[e,(0,t.jsx)(N.Popover,{content:a,trigger:"hover",children:(0,t.jsx)(I.InfoCircleOutlined,{className:"text-gray-400 text-xs cursor-help"})})]}),O={token:!1,organization_alias:!1,created_by:!1,updated_at:!1,expires:!1,rate_limits:!1},F=[{id:"created_at",desc:!0}],G={team_id:"Team",org_id:"Organization",user_id:"User ID",key_hash:"Key ID"};function H({headerActions:e}){let i,r,s,{data:n}=(0,h.useOrganizations)(),u=(0,o.useMemo)(()=>n??[],[n]),{data:c}=(0,a.useAllTeams)(),I=(0,o.useMemo)(()=>c??[],[c]),{keyId:N,openKey:z,close:M}=(i=(0,m.useSearchParams)(),r=(0,o.useCallback)(e=>{(0,g.navigateWithParams)(t=>{t.set("key",e)})},[]),s=(0,o.useCallback)(()=>{(0,g.navigateWithParams)(e=>{e.delete("key")})},[]),{keyId:i?.get("key")??null,openKey:r,close:s}),[W,q]=(0,o.useState)(F),[$,J]=(0,o.useState)({pageIndex:0,pageSize:50}),[Q,X]=(0,o.useState)([]),[Y,Z]=(0,o.useState)(!1),[ee,et]=(0,o.useState)(""),[ea]=(0,j.useDebouncedValue)(ee,{wait:f.DEBOUNCE_WAIT_MS}),el=(0,o.useCallback)(e=>{let t=Q.find(t=>t.id===e);return"string"==typeof t?.value&&t.value.trim()?t.value.trim():void 0},[Q]),ei=W[0]?.id,er=(e=>{let t=e[0];if(t)return t.desc?"desc":"asc"})(W),es={teamID:el("team_id"),organizationID:el("org_id"),selectedKeyAlias:ea.trim()||void 0,userID:el("user_id"),keyHash:el("key_hash"),sortBy:ei,sortOrder:er,expand:"user"},{data:en,isPending:eo,isFetching:ed,refetch:eu}=(0,x.useKeys)($.pageIndex+1,$.pageSize,es),ec=(0,o.useMemo)(()=>en?.keys??[],[en]),em=en?.total_count??0,eg=(0,o.useCallback)(e=>{et(e),J(e=>({...e,pageIndex:0}))},[]),ep=(0,o.useCallback)(e=>{q(e),J(e=>({...e,pageIndex:0}))},[]),ex=(0,o.useCallback)(e=>{X(e),J(e=>({...e,pageIndex:0}))},[]),eh=(0,o.useMemo)(()=>(({allTeams:e,organizations:a,onSelectKey:l})=>[{id:"key_alias",accessorKey:"key_alias",meta:{title:"Key",renderSkeleton:()=>(0,t.jsxs)("div",{className:"flex flex-col gap-1 py-1",children:[(0,t.jsx)(T.Skeleton,{className:"h-4 w-32"}),(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)(T.Skeleton,{className:"h-3 w-20"}),(0,t.jsx)(T.Skeleton,{className:"h-5 w-16 rounded-full"})]})]})},header:({column:e})=>(0,t.jsx)(D.DataTableSortHeader,{column:e,title:"Key",variant:"header-cycle"}),size:260,enableSorting:!0,cell:({row:e})=>{let a=(e=>{if(!0===e.blocked)return{tone:"error",label:"Blocked",tooltip:e.metadata?.scim_blocked===!0?"Blocked by SCIM (external identity provider deactivated or deleted the owning user).":"Blocked. Requests using this key will be rejected with 401."};let t=e.expires?Date.parse(e.expires):NaN;return!Number.isNaN(t)&&tl(e.original)})}},{id:"token",accessorKey:"token",meta:{title:"Key ID"},header:({column:e})=>(0,t.jsx)(D.DataTableSortHeader,{column:e,title:"Key ID",variant:"header-cycle"}),size:120,enableSorting:!0,cell:e=>(0,t.jsx)(E.IdCell,{value:e.getValue(),onClick:()=>l(e.row.original)})},{id:"team_alias",accessorKey:"team_id",meta:{title:"Team"},header:"Team",size:120,enableSorting:!1,cell:a=>{let l=a.getValue();if(!l)return"-";let i=e.find(e=>e.team_id===l),r=i?.team_alias||l,s=a.cell.column.getSize();return(0,t.jsx)("span",{className:"font-mono text-xs truncate block",style:{maxWidth:s,overflow:"hidden"},children:r})}},{id:"organization_alias",accessorKey:"org_id",meta:{title:"Organization"},header:"Organization",size:140,enableSorting:!1,cell:e=>{let l=e.getValue();if(!l)return"-";let i=a.find(e=>e.organization_id===l),r=i?.organization_alias||l,s=e.cell.column.getSize();return(0,t.jsx)("span",{className:"font-mono text-xs truncate block",style:{maxWidth:s,overflow:"hidden"},children:r})}},{id:"user",accessorKey:"user",meta:{title:"User"},header:()=>(0,t.jsx)(B,{label:"User",tooltip:"Displays the first available value: User Alias, User Email, or User ID."}),size:160,enableSorting:!1,cell:({row:e})=>{let a=e.original;return(0,t.jsx)(P,{userAlias:a.user?.user_alias??null,userEmail:a.user?.user_email??a.user_email??null,userId:a.user_id??null,width:160})}},{id:"created_at",accessorKey:"created_at",meta:{title:"Created At"},header:({column:e})=>(0,t.jsx)(D.DataTableSortHeader,{column:e,title:"Created At",variant:"header-cycle"}),size:120,enableSorting:!0,cell:e=>(0,t.jsx)(U.DateCell,{value:e.getValue(),precision:"date"})},{id:"created_by",accessorKey:"created_by",meta:{title:"Created By"},header:"Created By",size:160,enableSorting:!1,cell:e=>{let a=e.getValue();if(!a)return"-";let l=e.row.original.created_by_user;return(0,t.jsx)(P,{userAlias:l?.user_alias??null,userEmail:l?.user_email??null,userId:a,width:160})}},{id:"updated_at",accessorKey:"updated_at",meta:{title:"Updated At"},header:({column:e})=>(0,t.jsx)(D.DataTableSortHeader,{column:e,title:"Updated At",variant:"header-cycle"}),size:120,enableSorting:!0,cell:e=>(0,t.jsx)(U.DateCell,{value:e.getValue(),precision:"date",fallback:"Never"})},{id:"last_active",accessorKey:"last_active",meta:{title:"Last Active"},header:()=>(0,t.jsx)(B,{label:"Last Active",tooltip:"This is a new field and is not backfilled. Only new key usage will update this value."}),size:130,enableSorting:!1,cell:e=>(0,t.jsx)(U.DateCell,{value:e.getValue(),precision:"date",fallback:"Unknown"})},{id:"expires",accessorKey:"expires",meta:{title:"Expires"},header:"Expires",size:120,enableSorting:!1,cell:e=>(0,t.jsx)(U.DateCell,{value:e.getValue(),precision:"date",fallback:"Never"})},{id:"spend",accessorKey:"spend",meta:{title:"Spend / Budget",skeleton:"meter"},header:({table:e})=>(0,t.jsx)(D.DataTableMultiSortHeader,{table:e,fields:L}),size:180,enableSorting:!0,cell:({row:a})=>{let l=a.original.team_id,i=e.find(e=>e.team_id===l);return(0,t.jsx)(K.SpendBudgetCell,{spend:a.original.spend,maxBudget:a.original.max_budget,teamMaxBudget:i?.max_budget??null})}},{id:"budget_reset_at",accessorKey:"budget_reset_at",meta:{title:"Budget Reset"},header:"Budget Reset",size:130,enableSorting:!1,cell:e=>(0,t.jsx)(U.DateCell,{value:e.getValue(),fallback:"Never"})},{id:"models",accessorKey:"models",meta:{title:"Models",skeleton:"chips"},header:"Models",size:220,enableSorting:!1,cell:e=>(0,t.jsx)(R.ModelsCell,{models:e.getValue(),allowedRoutes:e.row.original.allowed_routes,keyType:e.row.original.key_type})},{id:"rate_limits",meta:{title:"Rate Limits"},header:"Rate Limits",size:140,enableSorting:!1,cell:({row:e})=>{let a=e.original;return(0,t.jsxs)("div",{className:"text-xs",children:[(0,t.jsxs)("div",{children:["TPM: ",null!==a.tpm_limit?a.tpm_limit:"Unlimited"]}),(0,t.jsxs)("div",{children:["RPM: ",null!==a.rpm_limit?a.rpm_limit:"Unlimited"]})]})}}])({allTeams:I,organizations:u,onSelectKey:e=>z(e.token)}),[I,u,z]),ef=(0,o.useMemo)(()=>ec.find(e=>e.token===N),[ec,N]),{data:eb,isError:ev}=function(e,t){let{accessToken:a}=(0,l.default)();return(0,p.useQuery)({queryKey:[...x.keyKeys.detail(e??""),a],queryFn:async()=>{if(!a||!e)throw Error("Missing access token or key id");return{...(await (0,d.keyInfoV1Call)(a,e)).info,token:e,api_key:e}},enabled:!!(a&&e)&&(t?.enabled??!0)})}(N,{enabled:!ef}),ey=ef??eb,e_=(0,o.useMemo)(()=>I.map(e=>({label:e.team_alias||e.team_id,value:e.team_id,sublabel:e.team_alias?e.team_id:void 0})),[I]),ew=(0,o.useMemo)(()=>u.filter(e=>e.organization_id).map(e=>{let t=e.organization_id;return{label:e.organization_alias||t,value:t,sublabel:e.organization_alias?t:void 0}}),[u]),ek=(0,o.useCallback)((e,t)=>{let a=String(t);return"team_id"===e?I.find(e=>e.team_id===a)?.team_alias||a:"org_id"===e&&u.find(e=>e.organization_id===a)?.organization_alias||a},[I,u]);return N?ey||ev?(0,t.jsx)("div",{className:"w-full h-full overflow-hidden",children:(0,t.jsx)(C.default,{keyId:N,onClose:M,keyData:ey,teams:I,onDelete:eu})}):(0,t.jsx)("div",{className:"p-4 text-sm text-muted-foreground",children:"Loading key..."}):(0,t.jsxs)("div",{className:"flex h-full flex-col gap-4 overflow-hidden py-2",children:[(0,t.jsx)(w.PageHeader,{icon:(0,t.jsx)(S.KeyRound,{className:"size-5"}),title:"Virtual Keys",subtitle:"Every key that authenticates requests to the gateway."}),e,(0,t.jsx)(b.DataTable,{data:ec,columns:eh,getRowId:e=>e.token,defaultColumnVisibility:O,sortingMode:"server",sorting:W,onSortingChange:ep,paginationMode:"server",pagination:$,onPaginationChange:J,rowCount:em,filterMode:"server",columnFilters:Q,onColumnFiltersChange:ex,enableColumnResizing:!0,columnResizeMode:"onChange",isLoading:eo,loadingMessage:"Loading keys...",noDataMessage:"No keys found",maxBodyHeight:"calc(75vh - 210px)",size:"compact",toolbar:e=>(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(y.DataTableToolbar,{table:e,searchValue:ee,onSearchChange:eg,searchPlaceholder:"Search by key alias…",onRefresh:()=>eu?.(),isRefreshing:ed,onOpenFilters:()=>Z(!0),filterLabels:G,formatFilterValue:ek}),(0,t.jsx)(v.DataTableFilterDrawer,{table:e,open:Y,onOpenChange:Z,title:"Filters",description:"Narrow down virtual keys",children:({get:e,set:a})=>(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(v.DataTableFilterField,{label:"Team",children:(0,t.jsx)(_.SearchSelect,{options:e_,value:e("team_id")||void 0,onValueChange:e=>a("team_id",e),placeholder:"Select a team…",emptyText:"No teams found"})}),(0,t.jsx)(v.DataTableFilterField,{label:"Organization",children:(0,t.jsx)(_.SearchSelect,{options:ew,value:e("org_id")||void 0,onValueChange:e=>a("org_id",e),placeholder:"Select an organization…",emptyText:"No organizations found"})}),(0,t.jsx)(v.DataTableFilterField,{label:"User ID",children:(0,t.jsx)(k.Input,{value:e("user_id")??"",onChange:e=>a("user_id",e.target.value),placeholder:"Enter User ID…"})}),(0,t.jsx)(v.DataTableFilterField,{label:"Key ID",children:(0,t.jsx)(k.Input,{value:e("key_hash")??"",onChange:e=>a("key_hash",e.target.value),placeholder:"Enter Key ID…"})})]})})]})})]})}let W=({userID:e,userRole:a,teams:l,keys:m,setUserRole:g,userEmail:p,setUserEmail:x,setTeams:h,setKeys:f,premiumUser:b,addKey:v,createClicked:y,autoOpenCreate:_,prefillData:w})=>{let[k,j]=(0,o.useState)(null),[S,C]=(0,o.useState)(null),I=(0,i.getCookie)("token"),[N,z]=(0,o.useState)(null),[D,T]=(0,o.useState)(null),[U,E]=(0,o.useState)([]),[A,R]=(0,o.useState)(null),[K,V]=(0,o.useState)(null);function M(){(0,i.clearTokenCookies)();let e=(0,d.getProxyBaseUrl)(),t=e?`${e}/sso/key/generate`:"/sso/key/generate";return window.location.href=t,null}if((0,o.useEffect)(()=>{let e=()=>{let e=sessionStorage.getItem("token");sessionStorage.clear(),e&&sessionStorage.setItem("token",e)};return window.addEventListener("beforeunload",e),()=>window.removeEventListener("beforeunload",e)},[]),(0,o.useEffect)(()=>{if(I){let e=(0,n.jwtDecode)(I);e&&(z(e.key),e.user_role&&g(function(e){if(!e)return"Undefined Role";switch(e.toLowerCase()){case"app_owner":case"demo_app_owner":return"App Owner";case"app_admin":case"proxy_admin":return"Admin";case"proxy_admin_viewer":return"Admin Viewer";case"app_user":return"App User";case"internal_user":return"Internal User";case"internal_user_viewer":return"Internal Viewer";default:return"Unknown Role"}}(e.user_role)),e.user_email&&x(e.user_email))}if(e&&N&&a&&!k){let t=sessionStorage.getItem("userModels"+e);t?E(JSON.parse(t)):((async()=>{try{let t=await (0,d.getProxyUISettings)(N);R(t);let l=await (0,d.userGetInfoV2)(N,e);j(l),sessionStorage.setItem("userSpendData"+e,JSON.stringify(l));let i=(await (0,d.modelAvailableCall)(N,e,a)).data.map(e=>e.id);E(i),sessionStorage.setItem("userModels"+e,JSON.stringify(i))}catch(e){console.error("There was an error fetching the data",e),e.message.includes("Invalid proxy server token passed")&&M()}})(),u(N,e,a,S,h))}},[e,I,N,a]),(0,o.useEffect)(()=>{N&&(async()=>{try{await (0,d.keyInfoCall)(N,[N])}catch(e){e.message.includes("Invalid proxy server token passed")&&M()}})()},[N]),(0,o.useEffect)(()=>{N&&u(N,e,a,S,h)},[S]),(0,o.useEffect)(()=>{if(null!==m&&null!=K&&null!==K.team_id){let e=0;for(let t of m)K.hasOwnProperty("team_id")&&null!==t.team_id&&t.team_id===K.team_id&&(e+=t.spend);T(e)}else if(null!==m){let e=0;for(let t of m)e+=t.spend;T(e)}},[K]),null==I)return M(),null;try{let e=(0,n.jwtDecode)(I).exp,t=Math.floor(Date.now()/1e3);if(e&&t>=e)return M(),null}catch(e){return console.error("Error decoding token:",e),(0,i.clearTokenCookies)(),M(),null}if(null==N)return null;if(null==e)return(0,t.jsx)("h1",{children:"User ID is not set"});null==a&&g("App Owner");let L="Admin Viewer"!==a&&"proxy_admin_viewer"!==a;return(0,t.jsx)("div",{className:"mx-4 h-[75vh]",children:(0,t.jsx)(s.Grid,{numItems:1,className:"gap-2 p-8 w-full mt-2",children:(0,t.jsx)(r.Col,{numColSpan:1,className:"flex flex-col gap-2",children:(0,t.jsx)(H,{headerActions:L?(0,t.jsx)(c.default,{team:K,teams:l,data:m,addKey:v,autoOpenCreate:_,prefillData:w},K?K.team_id:null):void 0})})})})};var q=e.i(557951);e.s(["default",0,function(){let{userId:e,userRole:i,userEmail:r,accessToken:s,premiumUser:n}=(0,l.default)(),{setUserRole:d,setUserEmail:u}=(0,q.useAuth)(),c=(0,m.useSearchParams)(),[g,p]=(0,o.useState)(null),[x,h]=(0,o.useState)([]),[f,b]=(0,o.useState)(!1),v="true"===c.get("create"),y=(0,o.useMemo)(()=>{if(!v)return;let e=c.get("owned_by"),t=c.get("team_id"),a=c.get("key_alias"),l=c.get("models"),i=c.get("key_type");if(!e&&!t&&!a&&!l&&!i)return;let r=e&&["you","service_account","another_user"].includes(e)?e:void 0,s=i&&["default","llm_api","management"].includes(i)?i:void 0,n=a?a.trim().slice(0,256):void 0,o=l?l.split(",").slice(0,100).map(e=>e.trim().slice(0,256)).filter(e=>e.length>0):void 0;return{owned_by:r,team_id:t?.trim()||void 0,key_alias:n,models:o&&o.length>0?o:void 0,key_type:s}},[c,v]);return(0,o.useEffect)(()=>{s&&e&&i&&(0,a.teamListCall)(s,1,100,{userID:"Admin"!==i&&"Admin Viewer"!==i?e:null}).then(e=>p(e.teams??[])).catch(console.error)},[s,e,i]),(0,t.jsx)(W,{userID:e,userRole:i,premiumUser:n??!1,teams:g,keys:x,setUserRole:d,userEmail:r,setUserEmail:u,setTeams:p,setKeys:h,addKey:e=>{h(t=>t?[...t,e]:[e]),b(e=>!e)},createClicked:f,autoOpenCreate:v,prefillData:y})}],502501)},871135,e=>{"use strict";var t=e.i(843476),a=e.i(502501),l=e.i(936578),i=e.i(602869),r=e.i(207082),s=e.i(708347),n=e.i(557951),o=e.i(321836),d=e.i(571353),u=e.i(618566),c=e.i(271645);function m(){let{authLoading:e,token:m,userRole:g,userID:p}=(0,n.useAuth)(),x=(0,u.useRouter)(),h=(0,u.useSearchParams)(),f=h.get("page"),b=(0,c.useRef)(!1),v=(0,c.useRef)(!1),y=!1===e&&null===m;(0,c.useEffect)(()=>{if(y){(0,o.storeReturnUrl)();let e=(0,o.getLoginUrl)(i.proxyBaseUrl||""),t=(0,o.buildLoginUrlWithReturn)(e);window.location.replace(t)}},[y]);let _=null!==f&&f in d.MIGRATED_PAGES;(0,c.useEffect)(()=>{!e&&_&&x.replace((0,d.migratedHref)(d.MIGRATED_PAGES[f]))},[e,_,f,x]),(0,c.useEffect)(()=>{if(e||!m||b.current)return;b.current=!0;let t=(0,o.consumeReturnUrl)();if(t&&(0,o.isValidReturnUrl)(t)){let e=new URL(t,window.location.origin);if(e.origin!==window.location.origin)return;let a=window.location.href;(0,o.normalizeUrlForCompare)(t)!==(0,o.normalizeUrlForCompare)(a)&&(v.current=!0,window.location.replace(e.href))}},[e,m]),(0,c.useEffect)(()=>{m||(b.current=!1,v.current=!1)},[m]);let w="success"===h.get("login"),k=!e&&!!m,j=w&&k&&""===g,S=w&&k&&s.internalUserRoles.includes(g),{data:C,isLoading:I}=(0,r.useKeys)(1,1,{userID:p},S),N=S&&!I&&C?.keys?.length===0,z=S&&I||N;(0,c.useEffect)(()=>{N&&!v.current&&x.replace((0,d.migratedHref)("connect"))},[N,x]);let D=y||_||j||z;return e||D?(0,t.jsx)(l.default,{}):(0,t.jsx)(a.default,{})}e.s(["default",0,function(){return(0,t.jsx)(c.Suspense,{fallback:(0,t.jsx)(l.default,{}),children:(0,t.jsx)(m,{})})}])}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/00lwtxl1k_z8t.js b/litellm/proxy/_experimental/out/_next/static/chunks/00lwtxl1k_z8t.js new file mode 100644 index 00000000000..12cc40b8fd0 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/00lwtxl1k_z8t.js @@ -0,0 +1 @@ +(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,540626,e=>{"use strict";let t;var n,i=e.i(271645);let s=(0,i.createContext)(null);function o(e,t){if(Object.is(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;if(e instanceof Map&&t instanceof Map){if(e.size!==t.size)return!1;for(let[n,i]of e)if(!t.has(n)||!Object.is(i,t.get(n)))return!1;return!0}if(e instanceof Set&&t instanceof Set){if(e.size!==t.size)return!1;for(let n of e)if(!t.has(n))return!1;return!0}if(e instanceof Date&&t instanceof Date)return e.getTime()===t.getTime();let n=l(e);if(n.length!==l(t).length)return!1;for(let i=0;ie,n){let s=n?.compare??r,o=(0,i.useCallback)(t=>{let{unsubscribe:n}=e.subscribe(t);return n},[e]),l=(0,i.useCallback)(()=>e.get(),[e]);return(0,a.useSyncExternalStoreWithSelector)(o,l,l,t,s)}function d(e,...t){return"function"==typeof e?e(...t):e}var u=class{#e=!0;#t;#n;#i;#s;#o;#l;#a;#r=0;#c=5;#d=!1;#u=!1;#g=null;#h=()=>{this.debugLog("Connected to event bus"),this.#o=!0,this.#d=!1,this.debugLog("Emitting queued events",this.#s),this.#s.forEach(e=>this.emitEventToBus(e)),this.#s=[],this.stopConnectLoop(),this.#n().removeEventListener("tanstack-connect-success",this.#h)};#v=()=>{if(this.#r{this.#d||(this.#d=!0,this.#n().addEventListener("tanstack-connect-success",this.#h),this.#v())};constructor({pluginId:e,debug:t=!1,enabled:n=!0,reconnectEveryMs:i=300}){this.#t=e,this.#e=n,this.#n=this.getGlobalTarget,this.#i=t,this.debugLog(" Initializing event subscription for plugin",this.#t),this.#s=[],this.#o=!1,this.#u=!1,this.#l=null,this.#a=i}startConnectLoop(){null!==this.#l||this.#o||(this.debugLog(`Starting connect loop (every ${this.#a}ms)`),this.#l=setInterval(this.#v,this.#a))}stopConnectLoop(){this.#d=!1,null!==this.#l&&(clearInterval(this.#l),this.#l=null,this.#s=[],this.debugLog("Stopped connect loop"))}debugLog(...e){this.#i&&console.log(`🌴 [tanstack-devtools:${this.#t}-plugin]`,...e)}getGlobalTarget(){if("u">typeof globalThis&&globalThis.__TANSTACK_EVENT_TARGET__)return this.debugLog("Using global event target"),globalThis.__TANSTACK_EVENT_TARGET__;if("u">typeof window&&void 0!==window.addEventListener)return this.debugLog("Using window as event target"),window;let e="u">typeof EventTarget?new EventTarget:void 0;return void 0===e||void 0===e.addEventListener?(this.debugLog("No event mechanism available, running in non-web environment"),{addEventListener:()=>{},removeEventListener:()=>{},dispatchEvent:()=>!1}):(this.debugLog("Using new EventTarget as fallback"),e)}getPluginId(){return this.#t}dispatchCustomEventShim(e,t){try{let n=new Event(e,{detail:t});this.#n().dispatchEvent(n)}catch(e){this.debugLog("Failed to dispatch shim event")}}dispatchCustomEvent(e,t){try{this.#n().dispatchEvent(new CustomEvent(e,{detail:t}))}catch(n){this.dispatchCustomEventShim(e,t)}}emitEventToBus(e){this.debugLog("Emitting event to client bus",e),this.dispatchCustomEvent("tanstack-dispatch-event",e)}createEventPayload(e,t){return{type:`${this.#t}:${e}`,payload:t,pluginId:this.#t}}emit(e,t){if(!this.#e)return void this.debugLog("Event bus client is disabled, not emitting event",e,t);if(this.#g&&(this.debugLog("Emitting event to internal event target",e,t),this.#g.dispatchEvent(new CustomEvent(`${this.#t}:${e}`,{detail:this.createEventPayload(e,t)}))),this.#u)return void this.debugLog("Previously failed to connect, not emitting to bus");if(!this.#o){this.debugLog("Bus not available, will be pushed as soon as connected"),this.#s.push(this.createEventPayload(e,t)),"u">typeof CustomEvent&&!this.#d&&(this.#p(),this.startConnectLoop());return}return this.emitEventToBus(this.createEventPayload(e,t))}on(e,t,n){let i=n?.withEventTarget??!1,s=`${this.#t}:${e}`;if(i&&(this.#g||(this.#g=new EventTarget),this.#g.addEventListener(s,e=>{t(e.detail)})),!this.#e)return this.debugLog("Event bus client is disabled, not registering event",s),()=>{};let o=e=>{this.debugLog("Received event from bus",e.detail),t(e.detail)};return this.#n().addEventListener(s,o),this.debugLog("Registered event to bus",s),()=>{i&&this.#g?.removeEventListener(s,o),this.#n().removeEventListener(s,o)}}onAll(e){if(!this.#e)return this.debugLog("Event bus client is disabled, not registering event"),()=>{};let t=t=>{e(t.detail)};return this.#n().addEventListener("tanstack-devtools-global",t),()=>this.#n().removeEventListener("tanstack-devtools-global",t)}onAllPluginEvents(e){if(!this.#e)return this.debugLog("Event bus client is disabled, not registering event"),()=>{};let t=t=>{let n=t.detail;this.#t&&n.pluginId!==this.#t||e(n)};return this.#n().addEventListener("tanstack-devtools-global",t),()=>this.#n().removeEventListener("tanstack-devtools-global",t)}};let g=new Map;function h(e){if(void 0!==e)try{return JSON.parse(JSON.stringify(e))}catch{return null}}let v=new class extends u{constructor(e){super({pluginId:"pacer",debug:e?.debug,reconnectEveryMs:1e3})}},p=((n={})[n.None=0]="None",n[n.Mutable=1]="Mutable",n[n.Watching=2]="Watching",n[n.RecursedCheck=4]="RecursedCheck",n[n.Recursed=8]="Recursed",n[n.Dirty=16]="Dirty",n[n.Pending=32]="Pending",n);function f(e,t,n){let i="object"==typeof e,s=i?e:void 0;return{next:(i?e.next:e)?.bind(s),error:(i?e.error:t)?.bind(s),complete:(i?e.complete:n)?.bind(s)}}let m=[],b=0,{link:E,unlink:T,propagate:y,checkDirty:S,shallowPropagate:x}=function({update:e,notify:t,unwatched:n}){return{link:function(e,t,n){let i=t.depsTail;if(void 0!==i&&i.dep===e)return;let s=void 0!==i?i.nextDep:t.deps;if(void 0!==s&&s.dep===e){s.version=n,t.depsTail=s;return}let o=e.subsTail;if(void 0!==o&&o.version===n&&o.sub===t)return;let l=t.depsTail=e.subsTail={version:n,dep:e,sub:t,prevDep:i,nextDep:s,prevSub:o,nextSub:void 0};void 0!==s&&(s.prevDep=l),void 0!==i?i.nextDep=l:t.deps=l,void 0!==o?o.nextSub=l:e.subs=l},unlink:function(e,t=e.sub){let i=e.dep,s=e.prevDep,o=e.nextDep,l=e.nextSub,a=e.prevSub;return void 0!==o?o.prevDep=s:t.depsTail=s,void 0!==s?s.nextDep=o:t.deps=o,void 0!==l?l.prevSub=a:i.subsTail=a,void 0!==a?a.nextSub=l:void 0===(i.subs=l)&&n(i),o},propagate:function(e){let n,i=e.nextSub;e:for(;;){let s=e.sub,o=s.flags;if(o&(p.RecursedCheck|p.Recursed|p.Dirty|p.Pending)?o&(p.RecursedCheck|p.Recursed)?o&p.RecursedCheck?!(o&(p.Dirty|p.Pending))&&function(e,t){let n=t.depsTail;for(;void 0!==n;){if(n===e)return!0;n=n.prevDep}return!1}(e,s)?(s.flags=o|(p.Recursed|p.Pending),o&=p.Mutable):o=p.None:s.flags=o&~p.Recursed|p.Pending:o=p.None:s.flags=o|p.Pending,o&p.Watching&&t(s),o&p.Mutable){let t=s.subs;if(void 0!==t){let s=(e=t).nextSub;void 0!==s&&(n={value:i,prev:n},i=s);continue}}if(void 0!==(e=i)){i=e.nextSub;continue}for(;void 0!==n;)if(e=n.value,n=n.prev,void 0!==e){i=e.nextSub;continue e}break}},checkDirty:function(t,n){let s,o=0,l=!1;e:for(;;){let a=t.dep,r=a.flags;if(n.flags&p.Dirty)l=!0;else if((r&(p.Mutable|p.Dirty))==(p.Mutable|p.Dirty)){if(e(a)){let e=a.subs;void 0!==e.nextSub&&i(e),l=!0}}else if((r&(p.Mutable|p.Pending))==(p.Mutable|p.Pending)){(void 0!==t.nextSub||void 0!==t.prevSub)&&(s={value:t,prev:s}),t=a.deps,n=a,++o;continue}if(!l){let e=t.nextDep;if(void 0!==e){t=e;continue}}for(;o--;){let o=n.subs,a=void 0!==o.nextSub;if(a?(t=s.value,s=s.prev):t=o,l){if(e(n)){a&&i(o),n=t.sub;continue}l=!1}else n.flags&=~p.Pending;n=t.sub;let r=t.nextDep;if(void 0!==r){t=r;continue e}}return l}},shallowPropagate:i};function i(e){do{let n=e.sub,i=n.flags;(i&(p.Pending|p.Dirty))===p.Pending&&(n.flags=i|p.Dirty,(i&(p.Watching|p.RecursedCheck))===p.Watching&&t(n))}while(void 0!==(e=e.nextSub))}}({update:e=>e._update(),notify(e){m[L++]=e,e.flags&=~p.Watching},unwatched(e){void 0!==e.depsTail&&(e.depsTail=void 0,e.flags=p.Mutable|p.Dirty,I(e))}}),C=0,L=0;function I(e){let t=e.depsTail,n=void 0!==t?t.nextDep:e.deps;for(;void 0!==n;)n=T(n,e)}var w=class{constructor(e,n){this.atom=function(e){let n="function"==typeof e,i={_snapshot:n?void 0:e,subs:void 0,subsTail:void 0,deps:void 0,depsTail:void 0,flags:n?p.None:p.Mutable,get:()=>(void 0!==t&&E(i,t,b),i._snapshot),subscribe(e){var n;let s,o,l=f(e),a={current:!1},r=(n=()=>{i.get(),a.current?l.next?.(i._snapshot):a.current=!0},s=()=>{let e=t;t=o,++b,o.depsTail=void 0,o.flags=p.Watching|p.RecursedCheck;try{return n()}finally{t=e,o.flags&=~p.RecursedCheck,I(o)}},o={deps:void 0,depsTail:void 0,subs:void 0,subsTail:void 0,flags:p.Watching|p.RecursedCheck,notify(){let e=this.flags;e&p.Dirty||e&p.Pending&&S(this.deps,this)?s():this.flags=p.Watching},stop(){this.flags=p.None,this.depsTail=void 0,I(this)}},s(),o);return{unsubscribe:()=>{r.stop()}}},_update(s){let o=t,l=(void 0)??Object.is;if(n)t=i,++b,i.depsTail=void 0;else if(void 0===s)return!1;n&&(i.flags=p.Mutable|p.RecursedCheck);try{let t=i._snapshot,o="function"==typeof s?s(t):void 0===s&&n?e(t):s;if(void 0===t||!l(t,o))return i._snapshot=o,!0;return!1}finally{t=o,n&&(i.flags&=~p.RecursedCheck),I(i)}}};return n?(i.flags=p.Mutable|p.Dirty,i.get=function(){let e=i.flags;if(e&p.Dirty||e&p.Pending&&S(i.deps,i)){if(i._update()){let e=i.subs;void 0!==e&&x(e)}}else e&p.Pending&&(i.flags=e&~p.Pending);return void 0!==t&&E(i,t,b),i._snapshot}):i.set=function(e){if(i._update(e)){let e=i.subs;if(void 0!==e&&(y(e),x(e),1)){for(;C{this.options={...this.options,...e},this.#m()||this.cancel()},this.#b=e=>{this.store.setState(t=>{let n={...t,...e},{isPending:i}=n;return{...n,status:this.#m()?i?"pending":"idle":"disabled"}}),((e,t)=>{let n=t.key;if(n){var i,s;g.set(n,t),v.emit(e,{key:(i={...t,key:n}).key,store:{state:h("function"==typeof(s=i.store).get?s.get():s.state)},options:h(i.options)})}})("Debouncer",this)},this.#m=()=>!!d(this.options.enabled,this),this.#E=()=>d(this.options.wait,this),this.maybeExecute=(...e)=>{if(!this.#m())return;this.#b({maybeExecuteCount:this.store.state.maybeExecuteCount+1});let t=!1;this.options.leading&&this.store.state.canLeadingExecute&&(this.#b({canLeadingExecute:!1}),t=!0,this.#T(...e)),this.options.trailing&&this.#b({isPending:!0,lastArgs:e}),this.#f&&clearTimeout(this.#f),this.#f=setTimeout(()=>{this.#b({canLeadingExecute:!0}),this.options.trailing&&!t&&this.#T(...e)},this.#E())},this.#T=(...e)=>{this.#m()&&(this.fn(...e),this.#b({executionCount:this.store.state.executionCount+1,isPending:!1,lastArgs:void 0}),this.options.onExecute?.(e,this))},this.flush=()=>{this.store.state.isPending&&this.store.state.lastArgs&&(this.#y(),this.#T(...this.store.state.lastArgs))},this.#y=()=>{this.#f&&(clearTimeout(this.#f),this.#f=void 0)},this.cancel=()=>{this.#y(),this.#b({canLeadingExecute:!0,isPending:!1})},this.reset=()=>{this.#b(M())},this.key=t.key,this.options={...k,...t},this.#b(this.options.initialState??{}),this.key&&v.on("d-Debouncer",e=>{e.payload.key===this.key&&(this.#b(e.payload.store.state),this.setOptions(e.payload.options))})}#b;#m;#E;#T;#y};e.s(["useDebouncer",0,function(e,t,n=()=>({})){let l={...((0,i.useContext)(s)?.defaultOptions??{}).debouncer,...t},[a]=(0,i.useState)(()=>{let t=new D(e,l);return t.Subscribe=function(e){let n=c(t.store,e.selector,{compare:o});return"function"==typeof e.children?e.children(n):e.children},t});a.fn=e,a.setOptions(l),(0,i.useEffect)(()=>()=>{l.onUnmount?l.onUnmount(a):a.cancel()},[]);let r=c(a.store,n,{compare:o});return(0,i.useMemo)(()=>({...a,state:r}),[a,r])}],540626)},741466,e=>{"use strict";e.s(["DEBOUNCE_WAIT_MS",0,300])},399029,e=>{"use strict";var t=e.i(540626),n=e.i(271645);e.s(["useDebouncedState",0,function(e,i,s){let[o,l]=(0,n.useState)(e),a=(0,t.useDebouncer)(l,i,s);return[o,a.maybeExecute,a]}])},663435,e=>{"use strict";var t=e.i(843476),n=e.i(271645),i=e.i(199133),s=e.i(898586),o=e.i(56456),l=e.i(399029),a=e.i(785242),r=e.i(741466);let{Text:c}=s.Typography;e.s(["default",0,({value:e,onChange:s,onTeamSelect:d,disabled:u,organizationId:g,pageSize:h=20})=>{let[v,p]=(0,n.useState)(""),[f,m]=(0,l.useDebouncedState)("",{wait:r.DEBOUNCE_WAIT_MS}),{data:b,fetchNextPage:E,hasNextPage:T,isFetchingNextPage:y,isLoading:S}=(0,a.useInfiniteTeams)(h,f||void 0,g),x=(0,n.useMemo)(()=>{if(!b?.pages)return[];let e=new Set,t=[];for(let n of b.pages)for(let i of n.teams)e.has(i.team_id)||(e.add(i.team_id),t.push(i));return t},[b]);return(0,t.jsx)(i.Select,{showSearch:!0,placeholder:"Search or select a team",value:e||void 0,onChange:e=>{s?.(e??""),d&&d(e?x.find(t=>t.team_id===e)??null:null)},disabled:u,allowClear:!0,filterOption:!1,onSearch:e=>{p(e),m(e)},searchValue:v,onPopupScroll:e=>{let t=e.currentTarget;(t.scrollTop+t.clientHeight)/t.scrollHeight>=.8&&T&&!y&&E()},loading:S,notFoundContent:S?(0,t.jsx)(o.LoadingOutlined,{spin:!0}):"No teams found","data-testid":"team-dropdown",popupRender:e=>(0,t.jsxs)(t.Fragment,{children:[e,y&&(0,t.jsx)("div",{style:{textAlign:"center",padding:8},children:(0,t.jsx)(o.LoadingOutlined,{spin:!0})})]}),children:x.map(e=>(0,t.jsxs)(i.Select.Option,{value:e.team_id,children:[(0,t.jsx)("span",{className:"font-medium",children:e.team_alias})," ",(0,t.jsxs)(c,{type:"secondary",children:["(",e.team_id,")"]})]},e.team_id))})}])},350967,46757,e=>{"use strict";var t=e.i(290571),n=e.i(444755),i=e.i(673706),s=e.i(271645);let o={0:"grid-cols-none",1:"grid-cols-1",2:"grid-cols-2",3:"grid-cols-3",4:"grid-cols-4",5:"grid-cols-5",6:"grid-cols-6",7:"grid-cols-7",8:"grid-cols-8",9:"grid-cols-9",10:"grid-cols-10",11:"grid-cols-11",12:"grid-cols-12"},l={0:"sm:grid-cols-none",1:"sm:grid-cols-1",2:"sm:grid-cols-2",3:"sm:grid-cols-3",4:"sm:grid-cols-4",5:"sm:grid-cols-5",6:"sm:grid-cols-6",7:"sm:grid-cols-7",8:"sm:grid-cols-8",9:"sm:grid-cols-9",10:"sm:grid-cols-10",11:"sm:grid-cols-11",12:"sm:grid-cols-12"},a={0:"md:grid-cols-none",1:"md:grid-cols-1",2:"md:grid-cols-2",3:"md:grid-cols-3",4:"md:grid-cols-4",5:"md:grid-cols-5",6:"md:grid-cols-6",7:"md:grid-cols-7",8:"md:grid-cols-8",9:"md:grid-cols-9",10:"md:grid-cols-10",11:"md:grid-cols-11",12:"md:grid-cols-12"},r={0:"lg:grid-cols-none",1:"lg:grid-cols-1",2:"lg:grid-cols-2",3:"lg:grid-cols-3",4:"lg:grid-cols-4",5:"lg:grid-cols-5",6:"lg:grid-cols-6",7:"lg:grid-cols-7",8:"lg:grid-cols-8",9:"lg:grid-cols-9",10:"lg:grid-cols-10",11:"lg:grid-cols-11",12:"lg:grid-cols-12"};e.s(["colSpan",0,{1:"col-span-1",2:"col-span-2",3:"col-span-3",4:"col-span-4",5:"col-span-5",6:"col-span-6",7:"col-span-7",8:"col-span-8",9:"col-span-9",10:"col-span-10",11:"col-span-11",12:"col-span-12",13:"col-span-13"},"colSpanLg",0,{1:"lg:col-span-1",2:"lg:col-span-2",3:"lg:col-span-3",4:"lg:col-span-4",5:"lg:col-span-5",6:"lg:col-span-6",7:"lg:col-span-7",8:"lg:col-span-8",9:"lg:col-span-9",10:"lg:col-span-10",11:"lg:col-span-11",12:"lg:col-span-12",13:"lg:col-span-13"},"colSpanMd",0,{1:"md:col-span-1",2:"md:col-span-2",3:"md:col-span-3",4:"md:col-span-4",5:"md:col-span-5",6:"md:col-span-6",7:"md:col-span-7",8:"md:col-span-8",9:"md:col-span-9",10:"md:col-span-10",11:"md:col-span-11",12:"md:col-span-12",13:"md:col-span-13"},"colSpanSm",0,{1:"sm:col-span-1",2:"sm:col-span-2",3:"sm:col-span-3",4:"sm:col-span-4",5:"sm:col-span-5",6:"sm:col-span-6",7:"sm:col-span-7",8:"sm:col-span-8",9:"sm:col-span-9",10:"sm:col-span-10",11:"sm:col-span-11",12:"sm:col-span-12",13:"sm:col-span-13"},"gridCols",0,o,"gridColsLg",0,r,"gridColsMd",0,a,"gridColsSm",0,l],46757);let c=(0,i.makeClassName)("Grid"),d=(e,t)=>e&&Object.keys(t).includes(String(e))?t[e]:"",u=s.default.forwardRef((e,i)=>{let{numItems:u=1,numItemsSm:g,numItemsMd:h,numItemsLg:v,children:p,className:f}=e,m=(0,t.__rest)(e,["numItems","numItemsSm","numItemsMd","numItemsLg","children","className"]),b=d(u,o),E=d(g,l),T=d(h,a),y=d(v,r),S=(0,n.tremorTwMerge)(b,E,T,y);return s.default.createElement("div",Object.assign({ref:i,className:(0,n.tremorTwMerge)(c("root"),"grid",S,f)},m),p)});u.displayName="Grid",e.s(["Grid",0,u],350967)},519756,e=>{"use strict";e.i(247167);var t=e.i(931067),n=e.i(271645);let 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"};var s=e.i(9583),o=n.forwardRef(function(e,o){return n.createElement(s.default,(0,t.default)({},e,{ref:o,icon:i}))});e.s(["UploadOutlined",0,o],519756)},184163,e=>{"use strict";e.i(247167);var t=e.i(931067),n=e.i(271645);let 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"};var s=e.i(9583),o=n.forwardRef(function(e,o){return n.createElement(s.default,(0,t.default)({},e,{ref:o,icon:i}))});e.s(["default",0,o],184163)},597440,e=>{"use strict";e.i(247167);var t=e.i(931067),n=e.i(271645);let 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"};var s=e.i(9583),o=n.forwardRef(function(e,o){return n.createElement(s.default,(0,t.default)({},e,{ref:o,icon:i}))});e.s(["default",0,o],597440)},955135,e=>{"use strict";var t=e.i(597440);e.s(["DeleteOutlined",()=>t.default])},993914,e=>{"use strict";e.i(247167);var t=e.i(931067),n=e.i(271645);let 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"};var s=e.i(9583),o=n.forwardRef(function(e,o){return n.createElement(s.default,(0,t.default)({},e,{ref:o,icon:i}))});e.s(["FileTextOutlined",0,o],993914)}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/10-c3gjiv7yt0.js b/litellm/proxy/_experimental/out/_next/static/chunks/011as3ct2u0nu.js similarity index 62% rename from litellm/proxy/_experimental/out/_next/static/chunks/10-c3gjiv7yt0.js rename to litellm/proxy/_experimental/out/_next/static/chunks/011as3ct2u0nu.js index 8eab6e37d9a..b0a8c9490b7 100644 --- a/litellm/proxy/_experimental/out/_next/static/chunks/10-c3gjiv7yt0.js +++ b/litellm/proxy/_experimental/out/_next/static/chunks/011as3ct2u0nu.js @@ -1,8 +1,8 @@ -(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,951047,380883,925395,268416,865296,320311,e=>{"use strict";e.s([],951047),e.i(247167);var t=e.i(271645),n=e.i(896499),r=e.i(146376),a=e.i(733332);let i=t.createContext(void 0);e.s(["TooltipRootContext",0,i,"useTooltipRootContext",0,function(e){let n=t.useContext(i);if(void 0===n&&!e)throw Error((0,a.default)(72));return n}],380883);var o=e.i(574735),l=e.i(667865),s=e.i(229315),u=e.i(647554),c=e.i(157940);function d(e){return null!=e&&null!=e.clientX}var p=e.i(17989),g=e.i(675606),f=e.i(264111),m=e.i(176782),h=e.i(616269),x=e.i(301252),b=e.i(56434),y=e.i(116786),v=e.i(990627);let C={...y.popupStoreSelectors,disabled:(0,h.createSelector)(e=>e.disabled),instantType:(0,h.createSelector)(e=>e.instantType),isInstantPhase:(0,h.createSelector)(e=>e.isInstantPhase),trackCursorAxis:(0,h.createSelector)(e=>e.trackCursorAxis),disableHoverablePopup:(0,h.createSelector)(e=>e.disableHoverablePopup),lastOpenChangeReason:(0,h.createSelector)(e=>e.openChangeReason),closeOnClick:(0,h.createSelector)(e=>e.closeOnClick),closeDelay:(0,h.createSelector)(e=>e.closeDelay),hasViewport:(0,h.createSelector)(e=>e.hasViewport)};class S extends x.ReactStore{constructor(e,n,r=!1){const a=new v.PopupTriggerMap,i={...(0,y.createInitialPopupStoreState)(),disabled:!1,instantType:void 0,isInstantPhase:!1,trackCursorAxis:"none",disableHoverablePopup:!1,openChangeReason:null,closeOnClick:!0,closeDelay:0,hasViewport:!1,...e};i.floatingRootContext=(0,y.createPopupFloatingRootContext)(a,n,r),super(i,{popupRef:t.createRef(),onOpenChange:void 0,onOpenChangeComplete:void 0,triggerElements:a},C)}setOpen=(e,t)=>{(0,f.applyPopupOpenChange)(this,e,t,{extraState:{openChangeReason:t.reason}})};cancelPendingOpen(e){this.state.floatingRootContext.dispatchOpenChange(!1,(0,g.createChangeEventDetails)(b.REASONS.triggerPress,e))}static useStore(e,t){return(0,f.usePopupStore)(e,(e,n)=>new S(t,e,n)).store}}e.s(["TooltipStore",0,S],925395);var E=e.i(843476);let $=(0,n.fastComponent)(function(e){let{disabled:n=!1,defaultOpen:a=!1,open:o,disableHoverablePopup:l=!1,trackCursorAxis:s="none",actionsRef:u,onOpenChange:c,onOpenChangeComplete:d,handle:p,triggerId:m,defaultTriggerId:h=null,children:x}=e,y=S.useStore(p?.store,{open:a,openProp:o,activeTriggerId:h,triggerIdProp:m});(0,f.useInitialOpenSync)(y,o,a,h),y.useControlledProp("openProp",o),y.useControlledProp("triggerIdProp",m),y.useContextCallback("onOpenChange",c),y.useContextCallback("onOpenChangeComplete",d);let v=y.useState("open"),C=!n&&v,$=y.useState("activeTriggerId"),R=y.useState("mounted"),j=y.useState("payload");y.useSyncedValues({trackCursorAxis:s,disableHoverablePopup:l}),y.useSyncedValue("disabled",n),(0,f.useImplicitActiveTrigger)(y,{closeOnActiveTriggerUnmount:!0});let{forceUnmount:w,transitionStatus:T}=(0,f.useOpenStateTransitions)(C,y),P=y.useState("isInstantPhase"),k=y.useState("instantType"),N=y.useState("lastOpenChangeReason"),M=t.useRef(null);(0,r.useIsoLayoutEffect)(()=>{v&&n&&y.setOpen(!1,(0,g.createChangeEventDetails)(b.REASONS.disabled))},[v,n,y]),(0,r.useIsoLayoutEffect)(()=>{"ending"===T&&N===b.REASONS.none||"ending"!==T&&P?("delay"!==k&&(M.current=k),y.set("instantType","delay")):null!==M.current&&(y.set("instantType",M.current),M.current=null)},[T,P,N,k,y]),(0,r.useIsoLayoutEffect)(()=>{C&&null==$&&y.set("payload",void 0)},[y,$,C]);let A=t.useCallback(()=>{y.setOpen(!1,(0,g.createChangeEventDetails)(b.REASONS.imperativeAction))},[y]);t.useImperativeHandle(u,()=>({unmount:w,close:A}),[w,A]);let I=C||R||!n&&"none"!==s;return(0,E.jsxs)(i.Provider,{value:y,children:[I&&(0,E.jsx)(O,{store:y,disabled:n,trackCursorAxis:s}),"function"==typeof x?x({payload:j}):x]})});function O({store:e,disabled:n,trackCursorAxis:r}){let a=e.useState("floatingRootContext"),i=(0,p.useDismiss)(a,{enabled:!n,referencePress:()=>e.select("closeOnClick")}),g=function(e,n={}){let{enabled:r=!0,axis:a="both"}=n,i="rootStore"in e?e.rootStore:e,p=i.useState("open"),g=i.useState("floatingElement"),f=i.useState("domReferenceElement"),m=i.context.dataRef,h=t.useRef(!1),x=t.useRef(null),[b,y]=t.useState(),[v,C]=t.useState([]),S=(0,l.useStableCallback)(e=>{i.set("positionReference",e)}),E=(0,l.useStableCallback)((e,t,n)=>{if(!h.current&&(!m.current.openEvent||d(m.current.openEvent))){var r,o;let l,s,u;i.set("positionReference",(r=n??f,o={x:e,y:t,axis:a,dataRef:m,pointerType:b},l=null,s=null,u=!1,{contextElement:r||void 0,getBoundingClientRect(){let e=r?.getBoundingClientRect()||{width:0,height:0,x:0,y:0},t="x"===o.axis||"both"===o.axis,n="y"===o.axis||"both"===o.axis,a=["mouseenter","mousemove"].includes(o.dataRef.current.openEvent?.type||"")&&"touch"!==o.pointerType,i=e.width,c=e.height,d=e.x,p=e.y;return null==l&&o.x&&t&&(l=e.x-o.x),null==s&&o.y&&n&&(s=e.y-o.y),d-=l||0,p-=s||0,i=0,c=0,!u||a?(i="y"===o.axis?e.width:0,c="x"===o.axis?e.height:0,d=t&&null!=o.x?o.x:d,p=n&&null!=o.y?o.y:p):u&&!a&&(c="x"===o.axis?e.height:c,i="y"===o.axis?e.width:i),u=!0,{width:i,height:c,x:d,y:p,top:p,right:d+i,bottom:p+c,left:d}}}))}}),$=(0,l.useStableCallback)(e=>{p?x.current||(E(e.clientX,e.clientY,e.currentTarget),C([])):E(e.clientX,e.clientY,e.currentTarget)}),O=(0,c.isMouseLikePointerType)(b)?g:p;t.useEffect(()=>{if(!r)return void S(f);if(!O)return;function e(){x.current?.(),x.current=null}let t=(0,s.getWindow)(g);return!m.current.openEvent||d(m.current.openEvent)?x.current=(0,o.addEventListener)(t,"mousemove",function(t){let n=(0,u.getTarget)(t);(0,u.contains)(g,n)?e():E(t.clientX,t.clientY)}):S(f),e},[O,r,g,m,f,i,E,S,v]),t.useEffect(()=>()=>{i.set("positionReference",null)},[i]),t.useEffect(()=>{r&&!g&&(h.current=!1)},[r,g]),t.useEffect(()=>{!r&&p&&(h.current=!0)},[r,p]);let R=t.useMemo(()=>{function e(e){y(e.pointerType)}return{onPointerDown:e,onPointerEnter:e,onMouseMove:$,onMouseEnter:$}},[$]);return t.useMemo(()=>r?{reference:R,trigger:R}:{},[r,R])}(a,{enabled:!n&&"none"!==r,axis:"none"===r?void 0:r}),h=t.useMemo(()=>(0,m.mergeProps)(g.reference,i.reference),[g.reference,i.reference]),x=t.useMemo(()=>(0,m.mergeProps)(g.trigger,i.trigger),[g.trigger,i.trigger]),b=t.useMemo(()=>(0,m.mergeProps)(f.FOCUSABLE_POPUP_PROPS,g.floating,i.floating),[g.floating,i.floating]);return(0,f.usePopupInteractionProps)(e,{activeTriggerProps:h,inactiveTriggerProps:x,popupProps:b}),null}e.s(["TooltipRoot",0,$],268416);let R=t.createContext(void 0);e.s(["TooltipProviderContext",0,R,"useTooltipProviderContext",0,function(){return t.useContext(R)}],865296);var j=e.i(439957),w=e.i(944681);let T=t.createContext({hasProvider:!1,timeoutMs:0,delayRef:{current:0},initialDelayRef:{current:0},timeout:new j.Timeout,currentIdRef:{current:null},currentContextRef:{current:null}});e.s(["FloatingDelayGroup",0,function(e){let{children:n,delay:a,timeoutMs:i=0}=e,o=t.useRef(a),l=t.useRef(a),s=t.useRef(null),u=t.useRef(null),c=(0,j.useTimeout)();return(0,r.useIsoLayoutEffect)(()=>{if(l.current=a,!s.current){o.current=a;return}o.current={open:(0,w.getDelay)(o.current,"open"),close:(0,w.getDelay)(a,"close")}},[a,s,o,l]),(0,E.jsx)(T.Provider,{value:t.useMemo(()=>({hasProvider:!0,delayRef:o,initialDelayRef:l,currentIdRef:s,timeoutMs:i,currentContextRef:u,timeout:c}),[i,c]),children:n})},"useDelayGroup",0,function(e,n={open:!1}){let{open:a}=n,i="rootStore"in e?e.rootStore:e,o=i.useState("floatingId"),{currentIdRef:l,delayRef:s,timeoutMs:u,initialDelayRef:c,currentContextRef:d,hasProvider:p,timeout:f}=t.useContext(T),[m,h]=t.useState(!1),x=t.useRef(a),y=t.useRef(!1);return(0,r.useIsoLayoutEffect)(()=>{x.current=a},[a]),(0,r.useIsoLayoutEffect)(()=>()=>{y.current=!0},[]),(0,r.useIsoLayoutEffect)(()=>{function e(){y.current||h(!1),d.current?.setIsInstantPhase(!1),l.current=null,d.current=null,s.current=c.current,f.clear()}if(l.current&&!a&&l.current===o){if(h(!1),u)return f.start(u,()=>{i.select("open")||l.current&&l.current!==o||e()}),()=>{(x.current||l.current!==o)&&f.clear()};e()}},[a,o,l,s,u,c,d,f,i]),(0,r.useIsoLayoutEffect)(()=>{if(!a)return;let e=d.current,t=l.current;f.clear(),d.current={onOpenChange:i.setOpen,setIsInstantPhase:h},l.current=o,s.current={open:0,close:(0,w.getDelay)(c.current,"close")},null!==t&&t!==o?(h(!0),e?.setIsInstantPhase(!0),e?.onOpenChange(!1,(0,g.createChangeEventDetails)(b.REASONS.none))):(h(!1),e?.setIsInstantPhase(!1))},[a,o,i,l,s,c,d,f]),(0,r.useIsoLayoutEffect)(()=>()=>{l.current===o&&(d.current=null,x.current)&&(l.current=null,s.current=c.current,f.clear())},[d,l,s,o,c,f]),t.useMemo(()=>({hasProvider:p,delayRef:s,isInstantPhase:m}),[p,s,m])}],320311)},413082,e=>{"use strict";var t=e.i(271645),n=e.i(574735),r=e.i(328744),a=e.i(365420),i=e.i(108868),o=e.i(439957),l=e.i(229315),s=e.i(451321),u=e.i(647554),c=e.i(596296),d=e.i(675606),p=e.i(56434);let g=r.platform.os.mac&&r.platform.engine.webkit;e.s(["useFocus",0,function(e,r={}){let{enabled:f=!0,delay:m}=r,h="rootStore"in e?e.rootStore:e,{events:x,dataRef:b}=h.context,y=t.useRef(!1),v=t.useRef(null),C=t.useRef(!0),S=(0,o.useTimeout)();t.useEffect(()=>{let e=h.select("domReferenceElement");if(!f)return;let t=(0,l.getWindow)(e);return(0,a.mergeCleanups)((0,n.addEventListener)(t,"blur",function(){let e=h.select("domReferenceElement");!h.select("open")&&(0,l.isHTMLElement)(e)&&e===(0,u.activeElement)((0,i.ownerDocument)(e))&&(y.current=!0)}),g&&(0,n.addEventListener)(t,"keydown",function(){C.current=!0},!0),g&&(0,n.addEventListener)(t,"pointerdown",function(){C.current=!1},!0))},[h,f]),t.useEffect(()=>{if(f)return x.on("openchange",e),()=>{x.off("openchange",e)};function e(e){if(e.reason===p.REASONS.triggerPress||e.reason===p.REASONS.escapeKey){let e=h.select("domReferenceElement");(0,l.isElement)(e)&&(v.current=e,y.current=!0)}}},[x,f,h]);let E=t.useMemo(()=>{function e(){y.current=!1,v.current=null}return{onMouseLeave(){e()},onFocus(t){let n=t.currentTarget;if(y.current){if(v.current===n)return;e()}let r=(0,u.getTarget)(t.nativeEvent);if((0,l.isElement)(r)){if(g&&!t.relatedTarget){if(!C.current&&!(0,c.isTypeableElement)(r))return}else if(!(0,c.matchesFocusVisible)(r))return}let a=(0,c.isTargetInsideEnabledTrigger)(t.relatedTarget,h.context.triggerElements),{nativeEvent:i,currentTarget:o}=t,s="function"==typeof m?m():m;h.select("open")&&a||0===s||void 0===s?h.setOpen(!0,(0,d.createChangeEventDetails)(p.REASONS.triggerFocus,i,o)):S.start(s,()=>{y.current||h.setOpen(!0,(0,d.createChangeEventDetails)(p.REASONS.triggerFocus,i,o))})},onBlur(t){e();let n=t.relatedTarget,r=t.nativeEvent,a=(0,l.isElement)(n)&&n.hasAttribute((0,s.createAttribute)("focus-guard"))&&"outside"===n.getAttribute("data-type");S.start(0,()=>{let e=h.select("domReferenceElement"),t=(0,u.activeElement)((0,i.ownerDocument)(e));if(!n&&t===e||(0,u.contains)(b.current.floatingContext?.refs.floating.current,t)||(0,u.contains)(e,t)||a)return;let o=n??t;(0,c.isTargetInsideEnabledTrigger)(o,h.context.triggerElements)||h.setOpen(!1,(0,d.createChangeEventDetails)(p.REASONS.triggerFocus,r))})}}},[b,m,h,S]);return t.useMemo(()=>f?{reference:E,trigger:E}:{},[f,E])}])},746798,378680,e=>{"use strict";var t,n,r=e.i(843476);e.i(951047);var a=e.i(268416);e.i(247167);var i=e.i(733332),o=e.i(271645),l=e.i(229315),s=e.i(896499),u=e.i(439957),c=e.i(446265),d=e.i(380883),p=e.i(405005),g=e.i(552245),f=e.i(264111),m=e.i(788015),h=e.i(865296),x=e.i(650316),b=e.i(320311),y=e.i(413082),v=e.i(872135),C=e.i(647554),S=e.i(157940),E=e.i(675606),$=e.i(56434);let O=((t={})[t.popupOpen=p.CommonTriggerDataAttributes.popupOpen]="popupOpen",t.triggerDisabled="data-trigger-disabled",t);var R=e.i(673752);let j="data-base-ui-tooltip-trigger";function w(e){if("composedPath"in e){let t=e.composedPath();for(let e=0;e"ending"===F.select("transitionStatus"),shouldOpen:()=>!er.current}),eu=(0,y.useFocus)(B,{enabled:!Z}).reference,ec=F.useState("triggerProps",Q),ed=Q||"none"!==et;return(0,g.useRenderElement)("button",e,{state:{open:L},ref:[t,_,z],props:[es,eu,ed?ec:void 0,{onMouseOver(e){(e=>{let t,n=er.current,r=w(e),a=(er.current=t=el(r),t&&(Y.openChangeTimeout.clear(),Y.restTimeout.clear(),Y.restTimeoutPending=!1,ea.clear()),t),i=z.current,o=i&&r&&(0,C.contains)(i,r);if(a&&F.select("open")&&F.select("lastOpenChangeReason")===$.REASONS.triggerHover)return F.setOpen(!1,(0,E.createChangeEventDetails)($.REASONS.triggerHover,e));if(n&&!a&&o&&!ee.current&&!F.select("open")&&i&&(0,S.isMouseLikePointerType)(ei.current)){let t=()=>{er.current||ee.current||F.select("open")||F.setOpen(!0,(0,E.createChangeEventDetails)($.REASONS.triggerHover,e,i))},n=eo();0===n?(ea.clear(),t()):ea.start(n,t)}})(e.nativeEvent)},onFocus(e){el(w(e.nativeEvent))&&e.preventBaseUIHandler()},onMouseLeave(){er.current=!1,ea.clear(),ei.current=void 0},onPointerEnter(e){ei.current=e.pointerType},onPointerDown(e){ei.current=e.pointerType,F.set("closeOnClick",N),N&&!F.select("open")&&F.cancelPendingOpen(e.nativeEvent)},onClick(e){N&&!F.select("open")&&F.cancelPendingOpen(e.nativeEvent)},id:q,[O.triggerDisabled]:Z?"":void 0,[j]:Z?void 0:""},I],stateAttributesMapping:p.triggerOpenStateMapping})}),P=o.createContext(void 0);var k=e.i(174080),N=e.i(726674);let M=o.forwardRef(function(e,t){let{children:n,container:a,className:i,render:l,style:s,...u}=e,{portalNode:c,portalSubtree:d}=(0,N.useFloatingPortalNode)({container:a,ref:t,componentProps:e,elementProps:u});return d||c?(0,r.jsxs)(o.Fragment,{children:[d,c&&k.createPortal(n,c)]}):null});e.s(["FloatingPortalLite",0,M],378680);let A=o.forwardRef(function(e,t){let{keepMounted:n=!1,...a}=e;return(0,d.useTooltipRootContext)().useState("mounted")||n?(0,r.jsx)(P.Provider,{value:n,children:(0,r.jsx)(M,{ref:t,...a})}):null}),I=o.createContext(void 0);function D(){let e=o.useContext(I);if(void 0===e)throw Error((0,i.default)(71));return e}var F=e.i(329365),q=e.i(638396),H=e.i(360495),L=e.i(789579);let B=o.forwardRef(function(e,t){let{render:n,className:a,anchor:l,positionMethod:s="absolute",side:u="top",align:c="center",sideOffset:p=0,alignOffset:g=0,collisionBoundary:f="clipping-ancestors",collisionPadding:m=5,arrowPadding:h=5,sticky:x=!1,disableAnchorTracking:b=!1,collisionAvoidance:y=q.POPUP_COLLISION_AVOIDANCE,style:v,...C}=e,S=(0,d.useTooltipRootContext)(),E=function(){let e=o.useContext(P);if(void 0===e)throw Error((0,i.default)(70));return e}(),$=S.useState("open"),O=S.useState("mounted"),R=S.useState("trackCursorAxis"),j=S.useState("disableHoverablePopup"),w=S.useState("floatingRootContext"),T=S.useState("instantType"),k=S.useState("transitionStatus"),N=S.useState("hasViewport"),M=(0,F.useAnchorPositioning)({anchor:l,positionMethod:s,floatingRootContext:w,mounted:O,side:u,sideOffset:p,align:c,alignOffset:g,collisionBoundary:f,collisionPadding:m,sticky:x,arrowPadding:h,disableAnchorTracking:b,keepMounted:E,collisionAvoidance:y,adaptiveOrigin:N?H.adaptiveOrigin:void 0}),A=o.useMemo(()=>({open:$,side:M.side,align:M.align,anchorHidden:M.anchorHidden,instant:"none"!==R?"tracking-cursor":T}),[$,M.side,M.align,M.anchorHidden,R,T]),D=(0,L.usePositioner)(e,A,{styles:M.positionerStyles,transitionStatus:k,props:C,refs:[t,S.useStateSetter("positionerElement")],hidden:!O,inert:!$||"both"===R||j});return(0,r.jsx)(I.Provider,{value:M,children:D})});var z=e.i(209407),W=e.i(137584),K=e.i(815982),_=e.i(431157);let Q={...p.popupStateMapping,...z.transitionStatusMapping},V=o.forwardRef(function(e,t){let{render:n,className:r,style:a,...i}=e,o=(0,d.useTooltipRootContext)(),{side:l,align:s}=D(),u=o.useState("open"),c=o.useState("instantType"),p=o.useState("transitionStatus"),f=o.useState("popupProps"),m=o.useState("floatingRootContext"),h=o.useState("disabled"),x=o.useState("closeDelay");(0,W.useOpenChangeComplete)({open:u,ref:o.context.popupRef,onComplete(){u&&o.context.onOpenChangeComplete?.(!0)}}),(0,_.useHoverFloatingInteraction)(m,{enabled:!h,closeDelay:x});let b=o.useStateSetter("popupElement");return(0,g.useRenderElement)("div",e,{state:{open:u,side:l,align:s,instant:c,transitionStatus:p},ref:[t,o.context.popupRef,b],props:[f,(0,K.getDisabledMountTransitionStyles)(p),i],stateAttributesMapping:Q})}),G=o.forwardRef(function(e,t){let{render:n,className:r,style:a,...i}=e,o=(0,d.useTooltipRootContext)(),{arrowRef:l,side:s,align:u,arrowUncentered:c,arrowStyles:f}=D(),m=o.useState("open"),h=o.useState("instantType");return(0,g.useRenderElement)("div",e,{state:{open:m,side:s,align:u,uncentered:c,instant:h},ref:[t,l],props:[{style:f,"aria-hidden":!0},i],stateAttributesMapping:p.popupStateMapping})}),U=((n={}).popupWidth="--popup-width",n.popupHeight="--popup-height",n);var X=e.i(818390);let Y={activationDirection:e=>e?{"data-activation-direction":e}:null},J=o.forwardRef(function(e,t){let{render:n,className:r,style:a,children:i,...o}=e,l=(0,d.useTooltipRootContext)(),s=D(),u=l.useState("instantType"),{children:c,state:p}=(0,X.usePopupViewport)({store:l,side:s.side,cssVars:U,children:i}),f={activationDirection:p.activationDirection,transitioning:p.transitioning,instant:u};return(0,g.useRenderElement)("div",e,{state:f,ref:t,props:[o,{children:c}],stateAttributesMapping:Y})});var Z=e.i(925395);class ee{constructor(){this.store=new Z.TooltipStore}open(e){let t=e?this.store.context.triggerElements.getById(e):void 0;if(e&&!t)throw Error((0,i.default)(81,e));this.store.setOpen(!0,(0,E.createChangeEventDetails)($.REASONS.imperativeAction,void 0,t))}close(){this.store.setOpen(!1,(0,E.createChangeEventDetails)($.REASONS.imperativeAction,void 0,void 0))}get isOpen(){return this.store.select("open")}}e.s(["Arrow",0,G,"Handle",0,ee,"Popup",0,V,"Portal",0,A,"Positioner",0,B,"Provider",0,function(e){let{delay:t,closeDelay:n,timeout:a=400}=e,i=o.useMemo(()=>({delay:t,closeDelay:n}),[t,n]),l=o.useMemo(()=>({open:t,close:n}),[t,n]);return(0,r.jsx)(h.TooltipProviderContext.Provider,{value:i,children:(0,r.jsx)(b.FloatingDelayGroup,{delay:l,timeoutMs:a,children:e.children})})},"Root",()=>a.TooltipRoot,"Trigger",0,T,"Viewport",0,J,"createHandle",0,function(){return new ee}],599643);var et=e.i(599643),et=et,en=e.i(115504);e.s(["Tooltip",0,function({...e}){return(0,r.jsx)(et.Root,{"data-slot":"tooltip",...e})},"TooltipContent",0,function({className:e,side:t="top",sideOffset:n=4,align:a="center",alignOffset:i=0,children:o,...l}){return(0,r.jsx)(et.Portal,{children:(0,r.jsx)(et.Positioner,{align:a,alignOffset:i,side:t,sideOffset:n,className:"isolate z-50",children:(0,r.jsxs)(et.Popup,{"data-slot":"tooltip-content",className:(0,en.cn)("z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",e),...l,children:[o,(0,r.jsx)(et.Arrow,{className:"z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5"})]})})})},"TooltipProvider",0,function({delay:e=0,...t}){return(0,r.jsx)(et.Provider,{"data-slot":"tooltip-provider",delay:e,...t})},"TooltipTrigger",0,function({...e}){return(0,r.jsx)(et.Trigger,{"data-slot":"tooltip-trigger",...e})}],746798)},112179,581070,e=>{"use strict";var t=e.i(843476),n=e.i(487486),r=e.i(115504),a=e.i(746798);function i({content:e,trigger:n}){return(0,t.jsx)(a.TooltipProvider,{delay:300,children:(0,t.jsxs)(a.Tooltip,{children:[(0,t.jsx)(a.TooltipTrigger,{render:n}),(0,t.jsx)(a.TooltipContent,{children:e})]})})}e.s(["CellTooltip",0,i],581070);let o={success:"border-green-200 bg-green-50 text-green-600",error:"border-red-200 bg-red-50 text-red-600",warning:"border-amber-200 bg-amber-50 text-amber-600",neutral:"border-gray-200 bg-gray-50 text-gray-600",info:"border-blue-200 bg-blue-50 text-blue-600"};e.s(["StatusBadge",0,function({tone:e,label:a,tooltip:l,dataTestId:s}){let u=(0,t.jsx)(n.Badge,{variant:"outline","data-testid":s,className:(0,r.cn)("whitespace-nowrap font-normal",o[e]),children:a});return l?(0,t.jsx)(i,{content:l,trigger:u}):u}],112179)},185793,e=>{"use strict";e.i(247167);var t=e.i(271645),n=e.i(343794),r=e.i(242064),a=e.i(529681);let i=e=>{let{prefixCls:r,className:a,style:i,size:o,shape:l}=e,s=(0,n.default)({[`${r}-lg`]:"large"===o,[`${r}-sm`]:"small"===o}),u=(0,n.default)({[`${r}-circle`]:"circle"===l,[`${r}-square`]:"square"===l,[`${r}-round`]:"round"===l}),c=t.useMemo(()=>"number"==typeof o?{width:o,height:o,lineHeight:`${o}px`}:{},[o]);return t.createElement("span",{className:(0,n.default)(r,s,u,a),style:Object.assign(Object.assign({},c),i)})};e.i(296059);var o=e.i(694758),l=e.i(915654),s=e.i(246422),u=e.i(838378);let c=new o.Keyframes("ant-skeleton-loading",{"0%":{backgroundPosition:"100% 50%"},"100%":{backgroundPosition:"0 50%"}}),d=e=>({height:e,lineHeight:(0,l.unit)(e)}),p=e=>Object.assign({width:e},d(e)),g=(e,t)=>Object.assign({width:t(e).mul(5).equal(),minWidth:t(e).mul(5).equal()},d(e)),f=e=>Object.assign({width:e},d(e)),m=(e,t,n)=>{let{skeletonButtonCls:r}=e;return{[`${n}${r}-circle`]:{width:t,minWidth:t,borderRadius:"50%"},[`${n}${r}-round`]:{borderRadius:t}}},h=(e,t)=>Object.assign({width:t(e).mul(2).equal(),minWidth:t(e).mul(2).equal()},d(e)),x=(0,s.genStyleHooks)("Skeleton",e=>{let{componentCls:t,calc:n}=e;return(e=>{let{componentCls:t,skeletonAvatarCls:n,skeletonTitleCls:r,skeletonParagraphCls:a,skeletonButtonCls:i,skeletonInputCls:o,skeletonImageCls:l,controlHeight:s,controlHeightLG:u,controlHeightSM:d,gradientFromColor:x,padding:b,marginSM:y,borderRadius:v,titleHeight:C,blockRadius:S,paragraphLiHeight:E,controlHeightXS:$,paragraphMarginTop:O}=e;return{[t]:{display:"table",width:"100%",[`${t}-header`]:{display:"table-cell",paddingInlineEnd:b,verticalAlign:"top",[n]:Object.assign({display:"inline-block",verticalAlign:"top",background:x},p(s)),[`${n}-circle`]:{borderRadius:"50%"},[`${n}-lg`]:Object.assign({},p(u)),[`${n}-sm`]:Object.assign({},p(d))},[`${t}-content`]:{display:"table-cell",width:"100%",verticalAlign:"top",[r]:{width:"100%",height:C,background:x,borderRadius:S,[`+ ${a}`]:{marginBlockStart:d}},[a]:{padding:0,"> li":{width:"100%",height:E,listStyle:"none",background:x,borderRadius:S,"+ li":{marginBlockStart:$}}},[`${a}> li:last-child:not(:first-child):not(:nth-child(2))`]:{width:"61%"}},[`&-round ${t}-content`]:{[`${r}, ${a} > li`]:{borderRadius:v}}},[`${t}-with-avatar ${t}-content`]:{[r]:{marginBlockStart:y,[`+ ${a}`]:{marginBlockStart:O}}},[`${t}${t}-element`]:Object.assign(Object.assign(Object.assign(Object.assign({display:"inline-block",width:"auto"},(e=>{let{borderRadiusSM:t,skeletonButtonCls:n,controlHeight:r,controlHeightLG:a,controlHeightSM:i,gradientFromColor:o,calc:l}=e;return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({[n]:Object.assign({display:"inline-block",verticalAlign:"top",background:o,borderRadius:t,width:l(r).mul(2).equal(),minWidth:l(r).mul(2).equal()},h(r,l))},m(e,r,n)),{[`${n}-lg`]:Object.assign({},h(a,l))}),m(e,a,`${n}-lg`)),{[`${n}-sm`]:Object.assign({},h(i,l))}),m(e,i,`${n}-sm`))})(e)),(e=>{let{skeletonAvatarCls:t,gradientFromColor:n,controlHeight:r,controlHeightLG:a,controlHeightSM:i}=e;return{[t]:Object.assign({display:"inline-block",verticalAlign:"top",background:n},p(r)),[`${t}${t}-circle`]:{borderRadius:"50%"},[`${t}${t}-lg`]:Object.assign({},p(a)),[`${t}${t}-sm`]:Object.assign({},p(i))}})(e)),(e=>{let{controlHeight:t,borderRadiusSM:n,skeletonInputCls:r,controlHeightLG:a,controlHeightSM:i,gradientFromColor:o,calc:l}=e;return{[r]:Object.assign({display:"inline-block",verticalAlign:"top",background:o,borderRadius:n},g(t,l)),[`${r}-lg`]:Object.assign({},g(a,l)),[`${r}-sm`]:Object.assign({},g(i,l))}})(e)),(e=>{let{skeletonImageCls:t,imageSizeBase:n,gradientFromColor:r,borderRadiusSM:a,calc:i}=e;return{[t]:Object.assign(Object.assign({display:"inline-flex",alignItems:"center",justifyContent:"center",verticalAlign:"middle",background:r,borderRadius:a},f(i(n).mul(2).equal())),{[`${t}-path`]:{fill:"#bfbfbf"},[`${t}-svg`]:Object.assign(Object.assign({},f(n)),{maxWidth:i(n).mul(4).equal(),maxHeight:i(n).mul(4).equal()}),[`${t}-svg${t}-svg-circle`]:{borderRadius:"50%"}}),[`${t}${t}-circle`]:{borderRadius:"50%"}}})(e)),[`${t}${t}-block`]:{width:"100%",[i]:{width:"100%"},[o]:{width:"100%"}},[`${t}${t}-active`]:{[` +(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,951047,380883,925395,268416,865296,320311,e=>{"use strict";e.s([],951047),e.i(247167);var t=e.i(271645),n=e.i(896499),r=e.i(146376),a=e.i(733332);let i=t.createContext(void 0);e.s(["TooltipRootContext",0,i,"useTooltipRootContext",0,function(e){let n=t.useContext(i);if(void 0===n&&!e)throw Error((0,a.default)(72));return n}],380883);var o=e.i(574735),l=e.i(667865),s=e.i(229315),u=e.i(647554),c=e.i(157940);function d(e){return null!=e&&null!=e.clientX}var p=e.i(17989),g=e.i(675606),f=e.i(264111),m=e.i(176782),h=e.i(616269),x=e.i(301252),y=e.i(56434),b=e.i(116786),v=e.i(990627);let C={...b.popupStoreSelectors,disabled:(0,h.createSelector)(e=>e.disabled),instantType:(0,h.createSelector)(e=>e.instantType),isInstantPhase:(0,h.createSelector)(e=>e.isInstantPhase),trackCursorAxis:(0,h.createSelector)(e=>e.trackCursorAxis),disableHoverablePopup:(0,h.createSelector)(e=>e.disableHoverablePopup),lastOpenChangeReason:(0,h.createSelector)(e=>e.openChangeReason),closeOnClick:(0,h.createSelector)(e=>e.closeOnClick),closeDelay:(0,h.createSelector)(e=>e.closeDelay),hasViewport:(0,h.createSelector)(e=>e.hasViewport)};class S extends x.ReactStore{constructor(e,n,r=!1){const a=new v.PopupTriggerMap,i={...(0,b.createInitialPopupStoreState)(),disabled:!1,instantType:void 0,isInstantPhase:!1,trackCursorAxis:"none",disableHoverablePopup:!1,openChangeReason:null,closeOnClick:!0,closeDelay:0,hasViewport:!1,...e};i.floatingRootContext=(0,b.createPopupFloatingRootContext)(a,n,r),super(i,{popupRef:t.createRef(),onOpenChange:void 0,onOpenChangeComplete:void 0,triggerElements:a},C)}setOpen=(e,t)=>{(0,f.applyPopupOpenChange)(this,e,t,{extraState:{openChangeReason:t.reason}})};cancelPendingOpen(e){this.state.floatingRootContext.dispatchOpenChange(!1,(0,g.createChangeEventDetails)(y.REASONS.triggerPress,e))}static useStore(e,t){return(0,f.usePopupStore)(e,(e,n)=>new S(t,e,n)).store}}e.s(["TooltipStore",0,S],925395);var E=e.i(843476);let $=(0,n.fastComponent)(function(e){let{disabled:n=!1,defaultOpen:a=!1,open:o,disableHoverablePopup:l=!1,trackCursorAxis:s="none",actionsRef:u,onOpenChange:c,onOpenChangeComplete:d,handle:p,triggerId:m,defaultTriggerId:h=null,children:x}=e,b=S.useStore(p?.store,{open:a,openProp:o,activeTriggerId:h,triggerIdProp:m});(0,f.useInitialOpenSync)(b,o,a,h),b.useControlledProp("openProp",o),b.useControlledProp("triggerIdProp",m),b.useContextCallback("onOpenChange",c),b.useContextCallback("onOpenChangeComplete",d);let v=b.useState("open"),C=!n&&v,$=b.useState("activeTriggerId"),R=b.useState("mounted"),j=b.useState("payload");b.useSyncedValues({trackCursorAxis:s,disableHoverablePopup:l}),b.useSyncedValue("disabled",n),(0,f.useImplicitActiveTrigger)(b,{closeOnActiveTriggerUnmount:!0});let{forceUnmount:w,transitionStatus:T}=(0,f.useOpenStateTransitions)(C,b),P=b.useState("isInstantPhase"),k=b.useState("instantType"),N=b.useState("lastOpenChangeReason"),A=t.useRef(null);(0,r.useIsoLayoutEffect)(()=>{v&&n&&b.setOpen(!1,(0,g.createChangeEventDetails)(y.REASONS.disabled))},[v,n,b]),(0,r.useIsoLayoutEffect)(()=>{"ending"===T&&N===y.REASONS.none||"ending"!==T&&P?("delay"!==k&&(A.current=k),b.set("instantType","delay")):null!==A.current&&(b.set("instantType",A.current),A.current=null)},[T,P,N,k,b]),(0,r.useIsoLayoutEffect)(()=>{C&&null==$&&b.set("payload",void 0)},[b,$,C]);let M=t.useCallback(()=>{b.setOpen(!1,(0,g.createChangeEventDetails)(y.REASONS.imperativeAction))},[b]);t.useImperativeHandle(u,()=>({unmount:w,close:M}),[w,M]);let I=C||R||!n&&"none"!==s;return(0,E.jsxs)(i.Provider,{value:b,children:[I&&(0,E.jsx)(O,{store:b,disabled:n,trackCursorAxis:s}),"function"==typeof x?x({payload:j}):x]})});function O({store:e,disabled:n,trackCursorAxis:r}){let a=e.useState("floatingRootContext"),i=(0,p.useDismiss)(a,{enabled:!n,referencePress:()=>e.select("closeOnClick")}),g=function(e,n={}){let{enabled:r=!0,axis:a="both"}=n,i="rootStore"in e?e.rootStore:e,p=i.useState("open"),g=i.useState("floatingElement"),f=i.useState("domReferenceElement"),m=i.context.dataRef,h=t.useRef(!1),x=t.useRef(null),[y,b]=t.useState(),[v,C]=t.useState([]),S=(0,l.useStableCallback)(e=>{i.set("positionReference",e)}),E=(0,l.useStableCallback)((e,t,n)=>{if(!h.current&&(!m.current.openEvent||d(m.current.openEvent))){var r,o;let l,s,u;i.set("positionReference",(r=n??f,o={x:e,y:t,axis:a,dataRef:m,pointerType:y},l=null,s=null,u=!1,{contextElement:r||void 0,getBoundingClientRect(){let e=r?.getBoundingClientRect()||{width:0,height:0,x:0,y:0},t="x"===o.axis||"both"===o.axis,n="y"===o.axis||"both"===o.axis,a=["mouseenter","mousemove"].includes(o.dataRef.current.openEvent?.type||"")&&"touch"!==o.pointerType,i=e.width,c=e.height,d=e.x,p=e.y;return null==l&&o.x&&t&&(l=e.x-o.x),null==s&&o.y&&n&&(s=e.y-o.y),d-=l||0,p-=s||0,i=0,c=0,!u||a?(i="y"===o.axis?e.width:0,c="x"===o.axis?e.height:0,d=t&&null!=o.x?o.x:d,p=n&&null!=o.y?o.y:p):u&&!a&&(c="x"===o.axis?e.height:c,i="y"===o.axis?e.width:i),u=!0,{width:i,height:c,x:d,y:p,top:p,right:d+i,bottom:p+c,left:d}}}))}}),$=(0,l.useStableCallback)(e=>{p?x.current||(E(e.clientX,e.clientY,e.currentTarget),C([])):E(e.clientX,e.clientY,e.currentTarget)}),O=(0,c.isMouseLikePointerType)(y)?g:p;t.useEffect(()=>{if(!r)return void S(f);if(!O)return;function e(){x.current?.(),x.current=null}let t=(0,s.getWindow)(g);return!m.current.openEvent||d(m.current.openEvent)?x.current=(0,o.addEventListener)(t,"mousemove",function(t){let n=(0,u.getTarget)(t);(0,u.contains)(g,n)?e():E(t.clientX,t.clientY)}):S(f),e},[O,r,g,m,f,i,E,S,v]),t.useEffect(()=>()=>{i.set("positionReference",null)},[i]),t.useEffect(()=>{r&&!g&&(h.current=!1)},[r,g]),t.useEffect(()=>{!r&&p&&(h.current=!0)},[r,p]);let R=t.useMemo(()=>{function e(e){b(e.pointerType)}return{onPointerDown:e,onPointerEnter:e,onMouseMove:$,onMouseEnter:$}},[$]);return t.useMemo(()=>r?{reference:R,trigger:R}:{},[r,R])}(a,{enabled:!n&&"none"!==r,axis:"none"===r?void 0:r}),h=t.useMemo(()=>(0,m.mergeProps)(g.reference,i.reference),[g.reference,i.reference]),x=t.useMemo(()=>(0,m.mergeProps)(g.trigger,i.trigger),[g.trigger,i.trigger]),y=t.useMemo(()=>(0,m.mergeProps)(f.FOCUSABLE_POPUP_PROPS,g.floating,i.floating),[g.floating,i.floating]);return(0,f.usePopupInteractionProps)(e,{activeTriggerProps:h,inactiveTriggerProps:x,popupProps:y}),null}e.s(["TooltipRoot",0,$],268416);let R=t.createContext(void 0);e.s(["TooltipProviderContext",0,R,"useTooltipProviderContext",0,function(){return t.useContext(R)}],865296);var j=e.i(439957),w=e.i(944681);let T=t.createContext({hasProvider:!1,timeoutMs:0,delayRef:{current:0},initialDelayRef:{current:0},timeout:new j.Timeout,currentIdRef:{current:null},currentContextRef:{current:null}});e.s(["FloatingDelayGroup",0,function(e){let{children:n,delay:a,timeoutMs:i=0}=e,o=t.useRef(a),l=t.useRef(a),s=t.useRef(null),u=t.useRef(null),c=(0,j.useTimeout)();return(0,r.useIsoLayoutEffect)(()=>{if(l.current=a,!s.current){o.current=a;return}o.current={open:(0,w.getDelay)(o.current,"open"),close:(0,w.getDelay)(a,"close")}},[a,s,o,l]),(0,E.jsx)(T.Provider,{value:t.useMemo(()=>({hasProvider:!0,delayRef:o,initialDelayRef:l,currentIdRef:s,timeoutMs:i,currentContextRef:u,timeout:c}),[i,c]),children:n})},"useDelayGroup",0,function(e,n={open:!1}){let{open:a}=n,i="rootStore"in e?e.rootStore:e,o=i.useState("floatingId"),{currentIdRef:l,delayRef:s,timeoutMs:u,initialDelayRef:c,currentContextRef:d,hasProvider:p,timeout:f}=t.useContext(T),[m,h]=t.useState(!1),x=t.useRef(a),b=t.useRef(!1);return(0,r.useIsoLayoutEffect)(()=>{x.current=a},[a]),(0,r.useIsoLayoutEffect)(()=>()=>{b.current=!0},[]),(0,r.useIsoLayoutEffect)(()=>{function e(){b.current||h(!1),d.current?.setIsInstantPhase(!1),l.current=null,d.current=null,s.current=c.current,f.clear()}if(l.current&&!a&&l.current===o){if(h(!1),u)return f.start(u,()=>{i.select("open")||l.current&&l.current!==o||e()}),()=>{(x.current||l.current!==o)&&f.clear()};e()}},[a,o,l,s,u,c,d,f,i]),(0,r.useIsoLayoutEffect)(()=>{if(!a)return;let e=d.current,t=l.current;f.clear(),d.current={onOpenChange:i.setOpen,setIsInstantPhase:h},l.current=o,s.current={open:0,close:(0,w.getDelay)(c.current,"close")},null!==t&&t!==o?(h(!0),e?.setIsInstantPhase(!0),e?.onOpenChange(!1,(0,g.createChangeEventDetails)(y.REASONS.none))):(h(!1),e?.setIsInstantPhase(!1))},[a,o,i,l,s,c,d,f]),(0,r.useIsoLayoutEffect)(()=>()=>{l.current===o&&(d.current=null,x.current)&&(l.current=null,s.current=c.current,f.clear())},[d,l,s,o,c,f]),t.useMemo(()=>({hasProvider:p,delayRef:s,isInstantPhase:m}),[p,s,m])}],320311)},413082,e=>{"use strict";var t=e.i(271645),n=e.i(574735),r=e.i(328744),a=e.i(365420),i=e.i(108868),o=e.i(439957),l=e.i(229315),s=e.i(451321),u=e.i(647554),c=e.i(596296),d=e.i(675606),p=e.i(56434);let g=r.platform.os.mac&&r.platform.engine.webkit;e.s(["useFocus",0,function(e,r={}){let{enabled:f=!0,delay:m}=r,h="rootStore"in e?e.rootStore:e,{events:x,dataRef:y}=h.context,b=t.useRef(!1),v=t.useRef(null),C=t.useRef(!0),S=(0,o.useTimeout)();t.useEffect(()=>{let e=h.select("domReferenceElement");if(!f)return;let t=(0,l.getWindow)(e);return(0,a.mergeCleanups)((0,n.addEventListener)(t,"blur",function(){let e=h.select("domReferenceElement");!h.select("open")&&(0,l.isHTMLElement)(e)&&e===(0,u.activeElement)((0,i.ownerDocument)(e))&&(b.current=!0)}),g&&(0,n.addEventListener)(t,"keydown",function(){C.current=!0},!0),g&&(0,n.addEventListener)(t,"pointerdown",function(){C.current=!1},!0))},[h,f]),t.useEffect(()=>{if(f)return x.on("openchange",e),()=>{x.off("openchange",e)};function e(e){if(e.reason===p.REASONS.triggerPress||e.reason===p.REASONS.escapeKey){let e=h.select("domReferenceElement");(0,l.isElement)(e)&&(v.current=e,b.current=!0)}}},[x,f,h]);let E=t.useMemo(()=>{function e(){b.current=!1,v.current=null}return{onMouseLeave(){e()},onFocus(t){let n=t.currentTarget;if(b.current){if(v.current===n)return;e()}let r=(0,u.getTarget)(t.nativeEvent);if((0,l.isElement)(r)){if(g&&!t.relatedTarget){if(!C.current&&!(0,c.isTypeableElement)(r))return}else if(!(0,c.matchesFocusVisible)(r))return}let a=(0,c.isTargetInsideEnabledTrigger)(t.relatedTarget,h.context.triggerElements),{nativeEvent:i,currentTarget:o}=t,s="function"==typeof m?m():m;h.select("open")&&a||0===s||void 0===s?h.setOpen(!0,(0,d.createChangeEventDetails)(p.REASONS.triggerFocus,i,o)):S.start(s,()=>{b.current||h.setOpen(!0,(0,d.createChangeEventDetails)(p.REASONS.triggerFocus,i,o))})},onBlur(t){e();let n=t.relatedTarget,r=t.nativeEvent,a=(0,l.isElement)(n)&&n.hasAttribute((0,s.createAttribute)("focus-guard"))&&"outside"===n.getAttribute("data-type");S.start(0,()=>{let e=h.select("domReferenceElement"),t=(0,u.activeElement)((0,i.ownerDocument)(e));if(!n&&t===e||(0,u.contains)(y.current.floatingContext?.refs.floating.current,t)||(0,u.contains)(e,t)||a)return;let o=n??t;(0,c.isTargetInsideEnabledTrigger)(o,h.context.triggerElements)||h.setOpen(!1,(0,d.createChangeEventDetails)(p.REASONS.triggerFocus,r))})}}},[y,m,h,S]);return t.useMemo(()=>f?{reference:E,trigger:E}:{},[f,E])}])},746798,378680,e=>{"use strict";var t,n,r=e.i(843476);e.i(951047);var a=e.i(268416);e.i(247167);var i=e.i(733332),o=e.i(271645),l=e.i(229315),s=e.i(896499),u=e.i(439957),c=e.i(446265),d=e.i(380883),p=e.i(405005),g=e.i(552245),f=e.i(264111),m=e.i(788015),h=e.i(865296),x=e.i(650316),y=e.i(320311),b=e.i(413082),v=e.i(872135),C=e.i(647554),S=e.i(157940),E=e.i(675606),$=e.i(56434);let O=((t={})[t.popupOpen=p.CommonTriggerDataAttributes.popupOpen]="popupOpen",t.triggerDisabled="data-trigger-disabled",t);var R=e.i(673752);let j="data-base-ui-tooltip-trigger";function w(e){if("composedPath"in e){let t=e.composedPath();for(let e=0;e"ending"===F.select("transitionStatus"),shouldOpen:()=>!er.current}),eu=(0,b.useFocus)(B,{enabled:!Z}).reference,ec=F.useState("triggerProps",_),ed=_||"none"!==et;return(0,g.useRenderElement)("button",e,{state:{open:L},ref:[t,Q,z],props:[es,eu,ed?ec:void 0,{onMouseOver(e){(e=>{let t,n=er.current,r=w(e),a=(er.current=t=el(r),t&&(Y.openChangeTimeout.clear(),Y.restTimeout.clear(),Y.restTimeoutPending=!1,ea.clear()),t),i=z.current,o=i&&r&&(0,C.contains)(i,r);if(a&&F.select("open")&&F.select("lastOpenChangeReason")===$.REASONS.triggerHover)return F.setOpen(!1,(0,E.createChangeEventDetails)($.REASONS.triggerHover,e));if(n&&!a&&o&&!ee.current&&!F.select("open")&&i&&(0,S.isMouseLikePointerType)(ei.current)){let t=()=>{er.current||ee.current||F.select("open")||F.setOpen(!0,(0,E.createChangeEventDetails)($.REASONS.triggerHover,e,i))},n=eo();0===n?(ea.clear(),t()):ea.start(n,t)}})(e.nativeEvent)},onFocus(e){el(w(e.nativeEvent))&&e.preventBaseUIHandler()},onMouseLeave(){er.current=!1,ea.clear(),ei.current=void 0},onPointerEnter(e){ei.current=e.pointerType},onPointerDown(e){ei.current=e.pointerType,F.set("closeOnClick",N),N&&!F.select("open")&&F.cancelPendingOpen(e.nativeEvent)},onClick(e){N&&!F.select("open")&&F.cancelPendingOpen(e.nativeEvent)},id:q,[O.triggerDisabled]:Z?"":void 0,[j]:Z?void 0:""},I],stateAttributesMapping:p.triggerOpenStateMapping})}),P=o.createContext(void 0);var k=e.i(174080),N=e.i(726674);let A=o.forwardRef(function(e,t){let{children:n,container:a,className:i,render:l,style:s,...u}=e,{portalNode:c,portalSubtree:d}=(0,N.useFloatingPortalNode)({container:a,ref:t,componentProps:e,elementProps:u});return d||c?(0,r.jsxs)(o.Fragment,{children:[d,c&&k.createPortal(n,c)]}):null});e.s(["FloatingPortalLite",0,A],378680);let M=o.forwardRef(function(e,t){let{keepMounted:n=!1,...a}=e;return(0,d.useTooltipRootContext)().useState("mounted")||n?(0,r.jsx)(P.Provider,{value:n,children:(0,r.jsx)(A,{ref:t,...a})}):null}),I=o.createContext(void 0);function D(){let e=o.useContext(I);if(void 0===e)throw Error((0,i.default)(71));return e}var F=e.i(329365),q=e.i(638396),H=e.i(360495),L=e.i(789579);let B=o.forwardRef(function(e,t){let{render:n,className:a,anchor:l,positionMethod:s="absolute",side:u="top",align:c="center",sideOffset:p=0,alignOffset:g=0,collisionBoundary:f="clipping-ancestors",collisionPadding:m=5,arrowPadding:h=5,sticky:x=!1,disableAnchorTracking:y=!1,collisionAvoidance:b=q.POPUP_COLLISION_AVOIDANCE,style:v,...C}=e,S=(0,d.useTooltipRootContext)(),E=function(){let e=o.useContext(P);if(void 0===e)throw Error((0,i.default)(70));return e}(),$=S.useState("open"),O=S.useState("mounted"),R=S.useState("trackCursorAxis"),j=S.useState("disableHoverablePopup"),w=S.useState("floatingRootContext"),T=S.useState("instantType"),k=S.useState("transitionStatus"),N=S.useState("hasViewport"),A=(0,F.useAnchorPositioning)({anchor:l,positionMethod:s,floatingRootContext:w,mounted:O,side:u,sideOffset:p,align:c,alignOffset:g,collisionBoundary:f,collisionPadding:m,sticky:x,arrowPadding:h,disableAnchorTracking:y,keepMounted:E,collisionAvoidance:b,adaptiveOrigin:N?H.adaptiveOrigin:void 0}),M=o.useMemo(()=>({open:$,side:A.side,align:A.align,anchorHidden:A.anchorHidden,instant:"none"!==R?"tracking-cursor":T}),[$,A.side,A.align,A.anchorHidden,R,T]),D=(0,L.usePositioner)(e,M,{styles:A.positionerStyles,transitionStatus:k,props:C,refs:[t,S.useStateSetter("positionerElement")],hidden:!O,inert:!$||"both"===R||j});return(0,r.jsx)(I.Provider,{value:A,children:D})});var z=e.i(209407),K=e.i(137584),W=e.i(815982),Q=e.i(431157);let _={...p.popupStateMapping,...z.transitionStatusMapping},V=o.forwardRef(function(e,t){let{render:n,className:r,style:a,...i}=e,o=(0,d.useTooltipRootContext)(),{side:l,align:s}=D(),u=o.useState("open"),c=o.useState("instantType"),p=o.useState("transitionStatus"),f=o.useState("popupProps"),m=o.useState("floatingRootContext"),h=o.useState("disabled"),x=o.useState("closeDelay");(0,K.useOpenChangeComplete)({open:u,ref:o.context.popupRef,onComplete(){u&&o.context.onOpenChangeComplete?.(!0)}}),(0,Q.useHoverFloatingInteraction)(m,{enabled:!h,closeDelay:x});let y=o.useStateSetter("popupElement");return(0,g.useRenderElement)("div",e,{state:{open:u,side:l,align:s,instant:c,transitionStatus:p},ref:[t,o.context.popupRef,y],props:[f,(0,W.getDisabledMountTransitionStyles)(p),i],stateAttributesMapping:_})}),U=o.forwardRef(function(e,t){let{render:n,className:r,style:a,...i}=e,o=(0,d.useTooltipRootContext)(),{arrowRef:l,side:s,align:u,arrowUncentered:c,arrowStyles:f}=D(),m=o.useState("open"),h=o.useState("instantType");return(0,g.useRenderElement)("div",e,{state:{open:m,side:s,align:u,uncentered:c,instant:h},ref:[t,l],props:[{style:f,"aria-hidden":!0},i],stateAttributesMapping:p.popupStateMapping})}),G=((n={}).popupWidth="--popup-width",n.popupHeight="--popup-height",n);var X=e.i(818390);let Y={activationDirection:e=>e?{"data-activation-direction":e}:null},J=o.forwardRef(function(e,t){let{render:n,className:r,style:a,children:i,...o}=e,l=(0,d.useTooltipRootContext)(),s=D(),u=l.useState("instantType"),{children:c,state:p}=(0,X.usePopupViewport)({store:l,side:s.side,cssVars:G,children:i}),f={activationDirection:p.activationDirection,transitioning:p.transitioning,instant:u};return(0,g.useRenderElement)("div",e,{state:f,ref:t,props:[o,{children:c}],stateAttributesMapping:Y})});var Z=e.i(925395);class ee{constructor(){this.store=new Z.TooltipStore}open(e){let t=e?this.store.context.triggerElements.getById(e):void 0;if(e&&!t)throw Error((0,i.default)(81,e));this.store.setOpen(!0,(0,E.createChangeEventDetails)($.REASONS.imperativeAction,void 0,t))}close(){this.store.setOpen(!1,(0,E.createChangeEventDetails)($.REASONS.imperativeAction,void 0,void 0))}get isOpen(){return this.store.select("open")}}e.s(["Arrow",0,U,"Handle",0,ee,"Popup",0,V,"Portal",0,M,"Positioner",0,B,"Provider",0,function(e){let{delay:t,closeDelay:n,timeout:a=400}=e,i=o.useMemo(()=>({delay:t,closeDelay:n}),[t,n]),l=o.useMemo(()=>({open:t,close:n}),[t,n]);return(0,r.jsx)(h.TooltipProviderContext.Provider,{value:i,children:(0,r.jsx)(y.FloatingDelayGroup,{delay:l,timeoutMs:a,children:e.children})})},"Root",()=>a.TooltipRoot,"Trigger",0,T,"Viewport",0,J,"createHandle",0,function(){return new ee}],599643);var et=e.i(599643),et=et,en=e.i(115504);e.s(["Tooltip",0,function({...e}){return(0,r.jsx)(et.Root,{"data-slot":"tooltip",...e})},"TooltipContent",0,function({className:e,side:t="top",sideOffset:n=4,align:a="center",alignOffset:i=0,children:o,...l}){return(0,r.jsx)(et.Portal,{children:(0,r.jsx)(et.Positioner,{align:a,alignOffset:i,side:t,sideOffset:n,className:"isolate z-50",children:(0,r.jsxs)(et.Popup,{"data-slot":"tooltip-content",className:(0,en.cn)("z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",e),...l,children:[o,(0,r.jsx)(et.Arrow,{className:"z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5"})]})})})},"TooltipProvider",0,function({delay:e=0,...t}){return(0,r.jsx)(et.Provider,{"data-slot":"tooltip-provider",delay:e,...t})},"TooltipTrigger",0,function({...e}){return(0,r.jsx)(et.Trigger,{"data-slot":"tooltip-trigger",...e})}],746798)},112179,581070,e=>{"use strict";var t=e.i(843476),n=e.i(487486),r=e.i(115504),a=e.i(746798);function i({content:e,trigger:n}){return(0,t.jsx)(a.TooltipProvider,{delay:300,children:(0,t.jsxs)(a.Tooltip,{children:[(0,t.jsx)(a.TooltipTrigger,{render:n}),(0,t.jsx)(a.TooltipContent,{children:e})]})})}e.s(["CellTooltip",0,i],581070);let o={success:"border-green-200 bg-green-50 text-green-600",error:"border-red-200 bg-red-50 text-red-600",warning:"border-amber-200 bg-amber-50 text-amber-600",neutral:"border-gray-200 bg-gray-50 text-gray-600",info:"border-blue-200 bg-blue-50 text-blue-600"};e.s(["StatusBadge",0,function({tone:e,label:a,tooltip:l,dataTestId:s}){let u=(0,t.jsx)(n.Badge,{variant:"outline","data-testid":s,className:(0,r.cn)("whitespace-nowrap font-normal",o[e]),children:a});return l?(0,t.jsx)(i,{content:l,trigger:u}):u}],112179)},185793,e=>{"use strict";e.i(247167);var t=e.i(271645),n=e.i(343794),r=e.i(242064),a=e.i(529681);let i=e=>{let{prefixCls:r,className:a,style:i,size:o,shape:l}=e,s=(0,n.default)({[`${r}-lg`]:"large"===o,[`${r}-sm`]:"small"===o}),u=(0,n.default)({[`${r}-circle`]:"circle"===l,[`${r}-square`]:"square"===l,[`${r}-round`]:"round"===l}),c=t.useMemo(()=>"number"==typeof o?{width:o,height:o,lineHeight:`${o}px`}:{},[o]);return t.createElement("span",{className:(0,n.default)(r,s,u,a),style:Object.assign(Object.assign({},c),i)})};e.i(296059);var o=e.i(694758),l=e.i(915654),s=e.i(246422),u=e.i(838378);let c=new o.Keyframes("ant-skeleton-loading",{"0%":{backgroundPosition:"100% 50%"},"100%":{backgroundPosition:"0 50%"}}),d=e=>({height:e,lineHeight:(0,l.unit)(e)}),p=e=>Object.assign({width:e},d(e)),g=(e,t)=>Object.assign({width:t(e).mul(5).equal(),minWidth:t(e).mul(5).equal()},d(e)),f=e=>Object.assign({width:e},d(e)),m=(e,t,n)=>{let{skeletonButtonCls:r}=e;return{[`${n}${r}-circle`]:{width:t,minWidth:t,borderRadius:"50%"},[`${n}${r}-round`]:{borderRadius:t}}},h=(e,t)=>Object.assign({width:t(e).mul(2).equal(),minWidth:t(e).mul(2).equal()},d(e)),x=(0,s.genStyleHooks)("Skeleton",e=>{let{componentCls:t,calc:n}=e;return(e=>{let{componentCls:t,skeletonAvatarCls:n,skeletonTitleCls:r,skeletonParagraphCls:a,skeletonButtonCls:i,skeletonInputCls:o,skeletonImageCls:l,controlHeight:s,controlHeightLG:u,controlHeightSM:d,gradientFromColor:x,padding:y,marginSM:b,borderRadius:v,titleHeight:C,blockRadius:S,paragraphLiHeight:E,controlHeightXS:$,paragraphMarginTop:O}=e;return{[t]:{display:"table",width:"100%",[`${t}-header`]:{display:"table-cell",paddingInlineEnd:y,verticalAlign:"top",[n]:Object.assign({display:"inline-block",verticalAlign:"top",background:x},p(s)),[`${n}-circle`]:{borderRadius:"50%"},[`${n}-lg`]:Object.assign({},p(u)),[`${n}-sm`]:Object.assign({},p(d))},[`${t}-content`]:{display:"table-cell",width:"100%",verticalAlign:"top",[r]:{width:"100%",height:C,background:x,borderRadius:S,[`+ ${a}`]:{marginBlockStart:d}},[a]:{padding:0,"> li":{width:"100%",height:E,listStyle:"none",background:x,borderRadius:S,"+ li":{marginBlockStart:$}}},[`${a}> li:last-child:not(:first-child):not(:nth-child(2))`]:{width:"61%"}},[`&-round ${t}-content`]:{[`${r}, ${a} > li`]:{borderRadius:v}}},[`${t}-with-avatar ${t}-content`]:{[r]:{marginBlockStart:b,[`+ ${a}`]:{marginBlockStart:O}}},[`${t}${t}-element`]:Object.assign(Object.assign(Object.assign(Object.assign({display:"inline-block",width:"auto"},(e=>{let{borderRadiusSM:t,skeletonButtonCls:n,controlHeight:r,controlHeightLG:a,controlHeightSM:i,gradientFromColor:o,calc:l}=e;return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({[n]:Object.assign({display:"inline-block",verticalAlign:"top",background:o,borderRadius:t,width:l(r).mul(2).equal(),minWidth:l(r).mul(2).equal()},h(r,l))},m(e,r,n)),{[`${n}-lg`]:Object.assign({},h(a,l))}),m(e,a,`${n}-lg`)),{[`${n}-sm`]:Object.assign({},h(i,l))}),m(e,i,`${n}-sm`))})(e)),(e=>{let{skeletonAvatarCls:t,gradientFromColor:n,controlHeight:r,controlHeightLG:a,controlHeightSM:i}=e;return{[t]:Object.assign({display:"inline-block",verticalAlign:"top",background:n},p(r)),[`${t}${t}-circle`]:{borderRadius:"50%"},[`${t}${t}-lg`]:Object.assign({},p(a)),[`${t}${t}-sm`]:Object.assign({},p(i))}})(e)),(e=>{let{controlHeight:t,borderRadiusSM:n,skeletonInputCls:r,controlHeightLG:a,controlHeightSM:i,gradientFromColor:o,calc:l}=e;return{[r]:Object.assign({display:"inline-block",verticalAlign:"top",background:o,borderRadius:n},g(t,l)),[`${r}-lg`]:Object.assign({},g(a,l)),[`${r}-sm`]:Object.assign({},g(i,l))}})(e)),(e=>{let{skeletonImageCls:t,imageSizeBase:n,gradientFromColor:r,borderRadiusSM:a,calc:i}=e;return{[t]:Object.assign(Object.assign({display:"inline-flex",alignItems:"center",justifyContent:"center",verticalAlign:"middle",background:r,borderRadius:a},f(i(n).mul(2).equal())),{[`${t}-path`]:{fill:"#bfbfbf"},[`${t}-svg`]:Object.assign(Object.assign({},f(n)),{maxWidth:i(n).mul(4).equal(),maxHeight:i(n).mul(4).equal()}),[`${t}-svg${t}-svg-circle`]:{borderRadius:"50%"}}),[`${t}${t}-circle`]:{borderRadius:"50%"}}})(e)),[`${t}${t}-block`]:{width:"100%",[i]:{width:"100%"},[o]:{width:"100%"}},[`${t}${t}-active`]:{[` ${r}, ${a} > li, ${n}, ${i}, ${o}, ${l} - `]:Object.assign({},{background:e.skeletonLoadingBackground,backgroundSize:"400% 100%",animationName:c,animationDuration:e.skeletonLoadingMotionDuration,animationTimingFunction:"ease",animationIterationCount:"infinite"})}}})((0,u.mergeToken)(e,{skeletonAvatarCls:`${t}-avatar`,skeletonTitleCls:`${t}-title`,skeletonParagraphCls:`${t}-paragraph`,skeletonButtonCls:`${t}-button`,skeletonInputCls:`${t}-input`,skeletonImageCls:`${t}-image`,imageSizeBase:n(e.controlHeight).mul(1.5).equal(),borderRadius:100,skeletonLoadingBackground:`linear-gradient(90deg, ${e.gradientFromColor} 25%, ${e.gradientToColor} 37%, ${e.gradientFromColor} 63%)`,skeletonLoadingMotionDuration:"1.4s"}))},e=>{let{colorFillContent:t,colorFill:n}=e;return{color:t,colorGradientEnd:n,gradientFromColor:t,gradientToColor:n,titleHeight:e.controlHeight/2,blockRadius:e.borderRadiusSM,paragraphMarginTop:e.marginLG+e.marginXXS,paragraphLiHeight:e.controlHeight/2}},{deprecatedTokens:[["color","gradientFromColor"],["colorGradientEnd","gradientToColor"]]}),b=e=>{let{prefixCls:r,className:a,style:i,rows:o=0}=e,l=Array.from({length:o}).map((n,r)=>t.createElement("li",{key:r,style:{width:((e,t)=>{let{width:n,rows:r=2}=t;return Array.isArray(n)?n[e]:r-1===e?n:void 0})(r,e)}}));return t.createElement("ul",{className:(0,n.default)(r,a),style:i},l)},y=({prefixCls:e,className:r,width:a,style:i})=>t.createElement("h3",{className:(0,n.default)(e,r),style:Object.assign({width:a},i)});function v(e){return e&&"object"==typeof e?e:{}}let C=e=>{let{prefixCls:a,loading:o,className:l,rootClassName:s,style:u,children:c,avatar:d=!1,title:p=!0,paragraph:g=!0,active:f,round:m}=e,{getPrefixCls:h,direction:C,className:S,style:E}=(0,r.useComponentConfig)("skeleton"),$=h("skeleton",a),[O,R,j]=x($);if(o||!("loading"in e)){let e,r,a=!!d,o=!!p,c=!!g;if(a){let n=Object.assign(Object.assign({prefixCls:`${$}-avatar`},o&&!c?{size:"large",shape:"square"}:{size:"large",shape:"circle"}),v(d));e=t.createElement("div",{className:`${$}-header`},t.createElement(i,Object.assign({},n)))}if(o||c){let e,n;if(o){let n=Object.assign(Object.assign({prefixCls:`${$}-title`},!a&&c?{width:"38%"}:a&&c?{width:"50%"}:{}),v(p));e=t.createElement(y,Object.assign({},n))}if(c){let e,r=Object.assign(Object.assign({prefixCls:`${$}-paragraph`},(e={},a&&o||(e.width="61%"),!a&&o?e.rows=3:e.rows=2,e)),v(g));n=t.createElement(b,Object.assign({},r))}r=t.createElement("div",{className:`${$}-content`},e,n)}let h=(0,n.default)($,{[`${$}-with-avatar`]:a,[`${$}-active`]:f,[`${$}-rtl`]:"rtl"===C,[`${$}-round`]:m},S,l,s,R,j);return O(t.createElement("div",{className:h,style:Object.assign(Object.assign({},E),u)},e,r))}return null!=c?c:null};C.Button=e=>{let{prefixCls:o,className:l,rootClassName:s,active:u,block:c=!1,size:d="default"}=e,{getPrefixCls:p}=t.useContext(r.ConfigContext),g=p("skeleton",o),[f,m,h]=x(g),b=(0,a.default)(e,["prefixCls"]),y=(0,n.default)(g,`${g}-element`,{[`${g}-active`]:u,[`${g}-block`]:c},l,s,m,h);return f(t.createElement("div",{className:y},t.createElement(i,Object.assign({prefixCls:`${g}-button`,size:d},b))))},C.Avatar=e=>{let{prefixCls:o,className:l,rootClassName:s,active:u,shape:c="circle",size:d="default"}=e,{getPrefixCls:p}=t.useContext(r.ConfigContext),g=p("skeleton",o),[f,m,h]=x(g),b=(0,a.default)(e,["prefixCls","className"]),y=(0,n.default)(g,`${g}-element`,{[`${g}-active`]:u},l,s,m,h);return f(t.createElement("div",{className:y},t.createElement(i,Object.assign({prefixCls:`${g}-avatar`,shape:c,size:d},b))))},C.Input=e=>{let{prefixCls:o,className:l,rootClassName:s,active:u,block:c,size:d="default"}=e,{getPrefixCls:p}=t.useContext(r.ConfigContext),g=p("skeleton",o),[f,m,h]=x(g),b=(0,a.default)(e,["prefixCls"]),y=(0,n.default)(g,`${g}-element`,{[`${g}-active`]:u,[`${g}-block`]:c},l,s,m,h);return f(t.createElement("div",{className:y},t.createElement(i,Object.assign({prefixCls:`${g}-input`,size:d},b))))},C.Image=e=>{let{prefixCls:a,className:i,rootClassName:o,style:l,active:s}=e,{getPrefixCls:u}=t.useContext(r.ConfigContext),c=u("skeleton",a),[d,p,g]=x(c),f=(0,n.default)(c,`${c}-element`,{[`${c}-active`]:s},i,o,p,g);return d(t.createElement("div",{className:f},t.createElement("div",{className:(0,n.default)(`${c}-image`,i),style:l},t.createElement("svg",{viewBox:"0 0 1098 1024",xmlns:"http://www.w3.org/2000/svg",className:`${c}-image-svg`},t.createElement("title",null,"Image placeholder"),t.createElement("path",{d:"M365.714286 329.142857q0 45.714286-32.036571 77.677714t-77.677714 32.036571-77.677714-32.036571-32.036571-77.677714 32.036571-77.677714 77.677714-32.036571 77.677714 32.036571 32.036571 77.677714zM950.857143 548.571429l0 256-804.571429 0 0-109.714286 182.857143-182.857143 91.428571 91.428571 292.571429-292.571429zM1005.714286 146.285714l-914.285714 0q-7.460571 0-12.873143 5.412571t-5.412571 12.873143l0 694.857143q0 7.460571 5.412571 12.873143t12.873143 5.412571l914.285714 0q7.460571 0 12.873143-5.412571t5.412571-12.873143l0-694.857143q0-7.460571-5.412571-12.873143t-12.873143-5.412571zM1097.142857 164.571429l0 694.857143q0 37.741714-26.843429 64.585143t-64.585143 26.843429l-914.285714 0q-37.741714 0-64.585143-26.843429t-26.843429-64.585143l0-694.857143q0-37.741714 26.843429-64.585143t64.585143-26.843429l914.285714 0q37.741714 0 64.585143 26.843429t26.843429 64.585143z",className:`${c}-image-path`})))))},C.Node=e=>{let{prefixCls:a,className:i,rootClassName:o,style:l,active:s,children:u}=e,{getPrefixCls:c}=t.useContext(r.ConfigContext),d=c("skeleton",a),[p,g,f]=x(d),m=(0,n.default)(d,`${d}-element`,{[`${d}-active`]:s},g,i,o,f);return p(t.createElement("div",{className:m},t.createElement("div",{className:(0,n.default)(`${d}-image`,i),style:l},u)))},e.s(["default",0,C],185793)},922611,e=>{"use strict";var t=e.i(271645),n=e.i(175066);function r(){}let a=t.createContext({add:r,remove:r});e.s(["usePanelRef",0,function(e){let r=t.useContext(a),i=t.useRef(null);return(0,n.default)(t=>{if(t){let n=e?t.querySelector(e):t;n&&(r.add(n),i.current=n)}else r.remove(i.current)})}])},500330,e=>{"use strict";var t=e.i(727749);let n=(e,t=0,n=!1,r=!0)=>{if(null==e||!Number.isFinite(e)||0===e&&!r)return"-";let a={minimumFractionDigits:t,maximumFractionDigits:t};if(!n)return e.toLocaleString("en-US",a);let i=e<0?"-":"",o=Math.abs(e),l=o,s="";return o>=1e6?(l=o/1e6,s="M"):o>=1e3&&(l=o/1e3,s="K"),`${i}${l.toLocaleString("en-US",a)}${s}`},r=async(e,n="Copied to clipboard")=>{if(!e)return!1;if(!navigator||!navigator.clipboard||!navigator.clipboard.writeText)return a(e,n);try{return await navigator.clipboard.writeText(e),t.default.success(n),!0}catch(t){return console.error("Clipboard API failed: ",t),a(e,n)}},a=(e,n)=>{try{let r=document.createElement("textarea");r.value=e,r.style.position="fixed",r.style.left="-999999px",r.style.top="-999999px",r.setAttribute("readonly",""),document.body.appendChild(r),r.focus(),r.select();let a=document.execCommand("copy");if(document.body.removeChild(r),a)return t.default.success(n),!0;throw Error("execCommand failed")}catch(e){return t.default.fromBackend("Failed to copy to clipboard"),console.error("Failed to copy: ",e),!1}};e.s(["copyToClipboard",0,r,"formatNumberWithCommas",0,n,"getSpendString",0,(e,t=6)=>{if(null==e||!Number.isFinite(e)||0===e)return"-";let r=n(e,t,!1,!1);if(0===Number(r.replace(/,/g,""))){let e=(1/10**t).toFixed(t);return`< $${e}`}return`$${r}`},"updateExistingKeys",0,function(e,t){let n=structuredClone(e);for(let[e,r]of Object.entries(t))e in n&&(n[e]=r);return n}])},199931,e=>{"use strict";let t=(0,e.i(475254).default)("waypoints",[["circle",{cx:"12",cy:"4.5",r:"2.5",key:"r5ysbb"}],["path",{d:"m10.2 6.3-3.9 3.9",key:"1nzqf6"}],["circle",{cx:"4.5",cy:"12",r:"2.5",key:"jydg6v"}],["path",{d:"M7 12h10",key:"b7w52i"}],["circle",{cx:"19.5",cy:"12",r:"2.5",key:"1piiel"}],["path",{d:"m13.8 17.7 3.9-3.9",key:"1wyg1y"}],["circle",{cx:"12",cy:"19.5",r:"2.5",key:"13o1pw"}]]);e.s(["Waypoints",0,t],199931)},625901,e=>{"use strict";var t=e.i(266027),n=e.i(621482),r=e.i(243652),a=e.i(602869),i=e.i(135214);let o=(0,r.createQueryKeys)("models"),l=(0,r.createQueryKeys)("modelHub"),s=(0,r.createQueryKeys)("autoRouterModelGroups"),u=(0,r.createQueryKeys)("allProxyModels");(0,r.createQueryKeys)("selectedTeamModels");let c=(0,r.createQueryKeys)("infiniteModels"),d=(0,r.createQueryKeys)("userModels"),p=new Set,g=e=>!!e?.litellm_params?.model?.startsWith("auto_router/"),f=e=>new Set(e.filter(g).map(e=>e.model_name).filter(e=>!!e)),m=async(e,t,n)=>{let r=await (0,a.modelInfoCall)(e,t,n,1,1e3),i=r?.total_pages??1;return[r,...await Promise.all(Array.from({length:Math.max(0,i-1)},(r,i)=>(0,a.modelInfoCall)(e,t,n,i+2,1e3)))].flatMap(e=>e?.data??[])};e.s(["useAllProxyModels",0,()=>{let{accessToken:e,userId:n,userRole:r}=(0,i.default)();return(0,t.useQuery)({queryKey:u.list({}),queryFn:async()=>await (0,a.modelAvailableCall)(e,n,r,!0,null,!0,!1,"expand"),enabled:!!(e&&n&&r)})},"useAutoRouterModelGroups",0,()=>{let{accessToken:e,userId:n,userRole:r}=(0,i.default)(),{data:a}=(0,t.useQuery)({queryKey:s.list({filters:{...n&&{userId:n},...r&&{userRole:r}}}),queryFn:async()=>await m(e,n,r),enabled:!!(e&&n&&r),select:f});return a??p},"useInfiniteModelInfo",0,(e=50,t)=>{let{accessToken:r,userId:o,userRole:l}=(0,i.default)();return(0,n.useInfiniteQuery)({queryKey:c.list({filters:{...o&&{userId:o},...l&&{userRole:l},size:e,...t&&{search:t}}}),queryFn:async({pageParam:n})=>await (0,a.modelInfoCall)(r,o,l,n,e,t),initialPageParam:1,getNextPageParam:e=>{if(e.current_page{let{accessToken:e}=(0,i.default)();return(0,t.useQuery)({queryKey:l.list({}),queryFn:async()=>await (0,a.modelHubCall)(e),enabled:!!e})},"useModelsInfo",0,(e=1,n=50,r,l,s,u,c)=>{let{accessToken:d,userId:p,userRole:g}=(0,i.default)();return(0,t.useQuery)({queryKey:o.list({filters:{...p&&{userId:p},...g&&{userRole:g},page:e,size:n,...r&&{search:r},...l&&{modelId:l},...s&&{teamId:s},...u&&{sortBy:u},...c&&{sortOrder:c}}}),queryFn:async()=>await (0,a.modelInfoCall)(d,p,g,e,n,r,l,s,u,c),enabled:!!(d&&p&&g)})},"useUserModels",0,()=>{let{accessToken:e,userId:n,userRole:r}=(0,i.default)();return(0,t.useQuery)({queryKey:d.list({}),queryFn:async()=>(await (0,a.modelAvailableCall)(e,n,r)).data.map(e=>e.id),enabled:!!(e&&n&&r)})}])},548151,200208,399536,997422,146512,e=>{"use strict";var t=e.i(843476),n=e.i(271645),r=e.i(199931),a=e.i(625901),i=e.i(487486),o=e.i(115504);let l=new Set,s=(0,n.createContext)(l);function u(e){let t=(0,n.useContext)(s);return!!e&&t.has(e)}e.s(["AutoRouterIcon",0,function({size:e=12,className:n}){return(0,t.jsx)(r.Waypoints,{size:e,className:n,"aria-hidden":!0})},"AutoRouterModelGroupsProvider",0,function({children:e}){let n=(0,a.useAutoRouterModelGroups)();return(0,t.jsx)(s.Provider,{value:n,children:e})},"AutoRouterTag",0,function({modelGroup:e,className:n}){return u(e)?(0,t.jsxs)(i.Badge,{variant:"secondary",title:`Routed by auto-router "${e}"`,className:(0,o.cn)("gap-1.5 px-2.5 py-1 text-sm font-normal text-foreground",n),children:[(0,t.jsx)(r.Waypoints,{"aria-hidden":!0}),e]}):null},"useIsAutoRoutedModelGroup",0,u],548151);var c=e.i(581070);let d=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],p=e=>String(e).padStart(2,"0"),g=(e,t)=>"date"===t?`${d[e.getMonth()]} ${e.getDate()}, ${e.getFullYear()}`:`${d[e.getMonth()]} ${e.getDate()}, ${p(e.getHours())}:${p(e.getMinutes())}:${p(e.getSeconds())}`;e.s(["DateCell",0,function({value:e,precision:n="datetime",fallback:r="-"}){let a,i,o,l=e?new Date(e):null;return!l||Number.isNaN(l.getTime())?(0,t.jsx)("span",{className:"text-muted-foreground",children:r}):(0,t.jsx)(c.CellTooltip,{content:(a=Intl.DateTimeFormat().resolvedOptions().timeZone,i=`${d[l.getMonth()]} ${l.getDate()}, ${l.getFullYear()}`,o=`${p(l.getHours())}:${p(l.getMinutes())}:${p(l.getSeconds())}`,`${i}, ${o} (${a})`),trigger:(0,t.jsx)("span",{className:"whitespace-nowrap",children:g(l,n)})})},"formatCellDate",0,g],200208);var f=e.i(174886),m=e.i(500330);let h={pill:{base:"font-mono text-xs font-normal px-2 py-0.5 rounded-md text-left bg-blue-50 text-blue-500",clickable:"hover:bg-blue-100 cursor-pointer"},plain:{base:"font-mono text-xs text-left",clickable:"hover:text-blue-600 cursor-pointer"}};e.s(["IdCell",0,function({value:e,variant:n="pill",onClick:r,copyable:a=!1,truncate:i=!0,fallback:l="-",tooltip:s,disabled:u=!1,dataTestId:d,className:p}){if(!e)return(0,t.jsx)("span",{className:"text-muted-foreground",children:l});let g=!!r&&!u,x=(0,o.cn)(h[n].base,g&&h[n].clickable,i&&"block max-w-[15ch] truncate",u&&"opacity-50",p),b=g?(0,t.jsx)("button",{type:"button",className:x,"data-testid":d,onClick:()=>r(e),children:e}):(0,t.jsx)("span",{className:x,"data-testid":d,children:e}),y=(0,t.jsx)(c.CellTooltip,{content:s??e,trigger:b});return a?(0,t.jsxs)("span",{className:"inline-flex max-w-full items-center gap-1",children:[y,(0,t.jsx)("button",{type:"button","aria-label":"Copy ID",className:"shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",onClick:t=>{t.stopPropagation(),(0,m.copyToClipboard)(e)},children:(0,t.jsx)(f.Copy,{className:"size-3"})})]}):y}],399536);var x=e.i(463059);e.s(["IdentityCell",0,function({title:e,subtitle:n,badge:r,onClick:a,className:i,titleClassName:l}){let s=(0,t.jsxs)("div",{className:"flex min-w-0 flex-col gap-0.5",children:[(0,t.jsx)("span",{className:(0,o.cn)("truncate text-sm font-medium text-foreground",l),children:e}),(null!=n&&""!==n||null!=r)&&(0,t.jsxs)("span",{className:"flex min-w-0 items-center gap-2",children:[null!=n&&""!==n&&(0,t.jsx)("span",{className:"truncate font-mono text-xs text-muted-foreground",children:n}),r]})]});return null!=a?(0,t.jsxs)("button",{type:"button",onClick:a,className:(0,o.cn)("group -mx-2 flex w-[calc(100%+1rem)] cursor-pointer items-center gap-2 rounded-md px-2 py-1 text-left transition-colors hover:bg-muted",i),children:[s,(0,t.jsx)(x.ChevronRight,{className:"ml-auto size-4 shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100"})]}):(0,t.jsx)("div",{className:(0,o.cn)("min-w-0",i),children:s})}],997422);let b={hasModelAccess:!1,label:"Management"},y={hasModelAccess:!1,label:"Read-only"},v={hasModelAccess:!1,label:"SCIM"},C={hasModelAccess:!0,label:null},S=e=>e.startsWith("/scim"),E=(e,t)=>1===e.length&&e[0]===t;e.s(["deriveKeyModelScope",0,(e,t)=>"management"===t?b:"read_only"===t?y:Array.isArray(e)&&0!==e.length?e.every(S)?v:E(e,"management_routes")?b:E(e,"info_routes")?y:C:C],146512)},355619,e=>{"use strict";var t=e.i(602869);let n=async(e,n,r)=>{try{if(null===e||null===n)return;if(null!==r){let a=(await (0,t.modelAvailableCall)(r,e,n,!0,null,!0)).data.map(e=>e.id),i=[],o=[];return a.forEach(e=>{e.endsWith("/*")?i.push(e):o.push(e)}),[...i,...o]}}catch(e){console.error("Error fetching user models:",e)}};e.s(["excludeProxyWideSentinel",0,e=>e.filter(e=>"all-proxy-models"!==e),"fetchAvailableModelsForTeamOrKey",0,n,"getModelDisplayName",0,e=>{if("all-proxy-models"===e)return"All Proxy Models";if(e.endsWith("/*")){let t=e.replace("/*","");return`All ${t} models`}return e},"hasAllModelsSentinel",0,e=>e.includes("all-proxy-models")||e.includes("all-team-models"),"unfurlWildcardModelsInList",0,(e,t)=>{let n=[],r=[];return e.forEach(e=>{if(e.endsWith("/*")){let a=e.replace("/*",""),i=t.filter(e=>e.startsWith(a+"/"));r.push(...i),n.push(e)}else r.push(e)}),[...n,...r].filter((e,t,n)=>n.indexOf(e)===t)}])},622826,547227,964471,630500,e=>{"use strict";e.i(548151);var t=e.i(581070);e.i(200208),e.i(399536),e.i(997422);var n=e.i(843476),r=e.i(146512),a=e.i(355619),i=e.i(487486);let o="all-proxy-models",l=e=>{if(e===o)return"All Proxy Models";let t=(0,a.getModelDisplayName)(e);return t.length>30?`${t.slice(0,30)}...`:t};e.s(["ModelsCell",0,function({models:e,maxVisible:a=3,allowedRoutes:s,keyType:u}){if(!Array.isArray(e)||0===e.length){let e=(0,r.deriveKeyModelScope)(s,u);return e.hasModelAccess?(0,n.jsx)(i.Badge,{variant:"secondary",children:"All Proxy Models"}):(0,n.jsx)(t.CellTooltip,{content:`Scoped to ${e.label} routes; this key cannot call any models`,trigger:(0,n.jsx)(i.Badge,{variant:"secondary",className:"cursor-default",children:"No model access"})})}let c=e.slice(0,a),d=e.slice(a);return(0,n.jsxs)("div",{className:"flex flex-wrap items-center gap-1",children:[c.map((e,t)=>(0,n.jsx)(i.Badge,{variant:e===o?"secondary":"outline",children:l(e)},t)),d.length>0&&(0,n.jsx)(t.CellTooltip,{content:(0,n.jsx)("div",{className:"flex max-w-[280px] flex-col gap-0.5",children:d.map((e,t)=>(0,n.jsx)("span",{children:l(e)},t))}),trigger:(0,n.jsxs)(i.Badge,{variant:"outline",className:"cursor-default",children:["+",d.length," more"]})})]})}],547227);var s=e.i(500330);e.s(["MoneyCell",0,function({value:e,decimals:t=4,emptyText:r="-",showZero:a=!1}){return null==e||Number.isNaN(e)?(0,n.jsx)("span",{className:"text-muted-foreground",children:r}):0===e?a?(0,n.jsx)("span",{className:"whitespace-nowrap",children:`$${(0,s.formatNumberWithCommas)(0,t,!1,!0)}`}):(0,n.jsx)("span",{className:"text-muted-foreground",children:"-"}):(0,n.jsx)("span",{className:"whitespace-nowrap",children:(0,s.getSpendString)(e,t)})}],964471);var u=e.i(944835);e.s(["SpendBudgetCell",0,function({spend:e,maxBudget:t,teamMaxBudget:r}){let a="number"!=typeof e||Number.isNaN(e)?0:e,i=t??r??null,o=null==t&&null!=r,l="number"==typeof i&&i>0,c=l?a/i*100:0,d=a>0?(0,s.getSpendString)(a,4):"$0.00",p=null===i?"· Unlimited":`of $${(0,s.formatNumberWithCommas)(i)}${o?" (Team)":""}`;return(0,n.jsxs)("div",{className:"flex min-w-[130px] flex-col gap-1",children:[(0,n.jsxs)("div",{className:"whitespace-nowrap text-xs",children:[(0,n.jsx)("span",{className:"font-medium tabular-nums text-foreground",children:d})," ",(0,n.jsx)("span",{className:"text-muted-foreground",children:p})]}),l&&(0,n.jsx)(u.Meter,{value:a,max:i,"aria-valuetext":`${d} of $${(0,s.formatNumberWithCommas)(i)}`,children:(0,n.jsx)(u.MeterTrack,{children:(0,n.jsx)(u.MeterIndicator,{tone:c>100?"over":c>=80?"warning":"default"})})})]})}],630500),e.i(112179),e.s([],622826)}]); \ No newline at end of file + `]:Object.assign({},{background:e.skeletonLoadingBackground,backgroundSize:"400% 100%",animationName:c,animationDuration:e.skeletonLoadingMotionDuration,animationTimingFunction:"ease",animationIterationCount:"infinite"})}}})((0,u.mergeToken)(e,{skeletonAvatarCls:`${t}-avatar`,skeletonTitleCls:`${t}-title`,skeletonParagraphCls:`${t}-paragraph`,skeletonButtonCls:`${t}-button`,skeletonInputCls:`${t}-input`,skeletonImageCls:`${t}-image`,imageSizeBase:n(e.controlHeight).mul(1.5).equal(),borderRadius:100,skeletonLoadingBackground:`linear-gradient(90deg, ${e.gradientFromColor} 25%, ${e.gradientToColor} 37%, ${e.gradientFromColor} 63%)`,skeletonLoadingMotionDuration:"1.4s"}))},e=>{let{colorFillContent:t,colorFill:n}=e;return{color:t,colorGradientEnd:n,gradientFromColor:t,gradientToColor:n,titleHeight:e.controlHeight/2,blockRadius:e.borderRadiusSM,paragraphMarginTop:e.marginLG+e.marginXXS,paragraphLiHeight:e.controlHeight/2}},{deprecatedTokens:[["color","gradientFromColor"],["colorGradientEnd","gradientToColor"]]}),y=e=>{let{prefixCls:r,className:a,style:i,rows:o=0}=e,l=Array.from({length:o}).map((n,r)=>t.createElement("li",{key:r,style:{width:((e,t)=>{let{width:n,rows:r=2}=t;return Array.isArray(n)?n[e]:r-1===e?n:void 0})(r,e)}}));return t.createElement("ul",{className:(0,n.default)(r,a),style:i},l)},b=({prefixCls:e,className:r,width:a,style:i})=>t.createElement("h3",{className:(0,n.default)(e,r),style:Object.assign({width:a},i)});function v(e){return e&&"object"==typeof e?e:{}}let C=e=>{let{prefixCls:a,loading:o,className:l,rootClassName:s,style:u,children:c,avatar:d=!1,title:p=!0,paragraph:g=!0,active:f,round:m}=e,{getPrefixCls:h,direction:C,className:S,style:E}=(0,r.useComponentConfig)("skeleton"),$=h("skeleton",a),[O,R,j]=x($);if(o||!("loading"in e)){let e,r,a=!!d,o=!!p,c=!!g;if(a){let n=Object.assign(Object.assign({prefixCls:`${$}-avatar`},o&&!c?{size:"large",shape:"square"}:{size:"large",shape:"circle"}),v(d));e=t.createElement("div",{className:`${$}-header`},t.createElement(i,Object.assign({},n)))}if(o||c){let e,n;if(o){let n=Object.assign(Object.assign({prefixCls:`${$}-title`},!a&&c?{width:"38%"}:a&&c?{width:"50%"}:{}),v(p));e=t.createElement(b,Object.assign({},n))}if(c){let e,r=Object.assign(Object.assign({prefixCls:`${$}-paragraph`},(e={},a&&o||(e.width="61%"),!a&&o?e.rows=3:e.rows=2,e)),v(g));n=t.createElement(y,Object.assign({},r))}r=t.createElement("div",{className:`${$}-content`},e,n)}let h=(0,n.default)($,{[`${$}-with-avatar`]:a,[`${$}-active`]:f,[`${$}-rtl`]:"rtl"===C,[`${$}-round`]:m},S,l,s,R,j);return O(t.createElement("div",{className:h,style:Object.assign(Object.assign({},E),u)},e,r))}return null!=c?c:null};C.Button=e=>{let{prefixCls:o,className:l,rootClassName:s,active:u,block:c=!1,size:d="default"}=e,{getPrefixCls:p}=t.useContext(r.ConfigContext),g=p("skeleton",o),[f,m,h]=x(g),y=(0,a.default)(e,["prefixCls"]),b=(0,n.default)(g,`${g}-element`,{[`${g}-active`]:u,[`${g}-block`]:c},l,s,m,h);return f(t.createElement("div",{className:b},t.createElement(i,Object.assign({prefixCls:`${g}-button`,size:d},y))))},C.Avatar=e=>{let{prefixCls:o,className:l,rootClassName:s,active:u,shape:c="circle",size:d="default"}=e,{getPrefixCls:p}=t.useContext(r.ConfigContext),g=p("skeleton",o),[f,m,h]=x(g),y=(0,a.default)(e,["prefixCls","className"]),b=(0,n.default)(g,`${g}-element`,{[`${g}-active`]:u},l,s,m,h);return f(t.createElement("div",{className:b},t.createElement(i,Object.assign({prefixCls:`${g}-avatar`,shape:c,size:d},y))))},C.Input=e=>{let{prefixCls:o,className:l,rootClassName:s,active:u,block:c,size:d="default"}=e,{getPrefixCls:p}=t.useContext(r.ConfigContext),g=p("skeleton",o),[f,m,h]=x(g),y=(0,a.default)(e,["prefixCls"]),b=(0,n.default)(g,`${g}-element`,{[`${g}-active`]:u,[`${g}-block`]:c},l,s,m,h);return f(t.createElement("div",{className:b},t.createElement(i,Object.assign({prefixCls:`${g}-input`,size:d},y))))},C.Image=e=>{let{prefixCls:a,className:i,rootClassName:o,style:l,active:s}=e,{getPrefixCls:u}=t.useContext(r.ConfigContext),c=u("skeleton",a),[d,p,g]=x(c),f=(0,n.default)(c,`${c}-element`,{[`${c}-active`]:s},i,o,p,g);return d(t.createElement("div",{className:f},t.createElement("div",{className:(0,n.default)(`${c}-image`,i),style:l},t.createElement("svg",{viewBox:"0 0 1098 1024",xmlns:"http://www.w3.org/2000/svg",className:`${c}-image-svg`},t.createElement("title",null,"Image placeholder"),t.createElement("path",{d:"M365.714286 329.142857q0 45.714286-32.036571 77.677714t-77.677714 32.036571-77.677714-32.036571-32.036571-77.677714 32.036571-77.677714 77.677714-32.036571 77.677714 32.036571 32.036571 77.677714zM950.857143 548.571429l0 256-804.571429 0 0-109.714286 182.857143-182.857143 91.428571 91.428571 292.571429-292.571429zM1005.714286 146.285714l-914.285714 0q-7.460571 0-12.873143 5.412571t-5.412571 12.873143l0 694.857143q0 7.460571 5.412571 12.873143t12.873143 5.412571l914.285714 0q7.460571 0 12.873143-5.412571t5.412571-12.873143l0-694.857143q0-7.460571-5.412571-12.873143t-12.873143-5.412571zM1097.142857 164.571429l0 694.857143q0 37.741714-26.843429 64.585143t-64.585143 26.843429l-914.285714 0q-37.741714 0-64.585143-26.843429t-26.843429-64.585143l0-694.857143q0-37.741714 26.843429-64.585143t64.585143-26.843429l914.285714 0q37.741714 0 64.585143 26.843429t26.843429 64.585143z",className:`${c}-image-path`})))))},C.Node=e=>{let{prefixCls:a,className:i,rootClassName:o,style:l,active:s,children:u}=e,{getPrefixCls:c}=t.useContext(r.ConfigContext),d=c("skeleton",a),[p,g,f]=x(d),m=(0,n.default)(d,`${d}-element`,{[`${d}-active`]:s},g,i,o,f);return p(t.createElement("div",{className:m},t.createElement("div",{className:(0,n.default)(`${d}-image`,i),style:l},u)))},e.s(["default",0,C],185793)},922611,e=>{"use strict";var t=e.i(271645),n=e.i(175066);function r(){}let a=t.createContext({add:r,remove:r});e.s(["usePanelRef",0,function(e){let r=t.useContext(a),i=t.useRef(null);return(0,n.default)(t=>{if(t){let n=e?t.querySelector(e):t;n&&(r.add(n),i.current=n)}else r.remove(i.current)})}])},500330,e=>{"use strict";var t=e.i(727749);let n=(e,t=0,n=!1,r=!0)=>{if(null==e||!Number.isFinite(e)||0===e&&!r)return"-";let a={minimumFractionDigits:t,maximumFractionDigits:t};if(!n)return e.toLocaleString("en-US",a);let i=e<0?"-":"",o=Math.abs(e),l=o,s="";return o>=1e6?(l=o/1e6,s="M"):o>=1e3&&(l=o/1e3,s="K"),`${i}${l.toLocaleString("en-US",a)}${s}`},r=async(e,n="Copied to clipboard")=>{if(!e)return!1;if(!navigator||!navigator.clipboard||!navigator.clipboard.writeText)return a(e,n);try{return await navigator.clipboard.writeText(e),t.default.success(n),!0}catch(t){return console.error("Clipboard API failed: ",t),a(e,n)}},a=(e,n)=>{try{let r=document.createElement("textarea");r.value=e,r.style.position="fixed",r.style.left="-999999px",r.style.top="-999999px",r.setAttribute("readonly",""),document.body.appendChild(r),r.focus(),r.select();let a=document.execCommand("copy");if(document.body.removeChild(r),a)return t.default.success(n),!0;throw Error("execCommand failed")}catch(e){return t.default.fromBackend("Failed to copy to clipboard"),console.error("Failed to copy: ",e),!1}};e.s(["copyToClipboard",0,r,"formatNumberWithCommas",0,n,"getSpendString",0,(e,t=6)=>{if(null==e||!Number.isFinite(e)||0===e)return"-";let r=n(e,t,!1,!1);if(0===Number(r.replace(/,/g,""))){let e=(1/10**t).toFixed(t);return`< $${e}`}return`$${r}`},"updateExistingKeys",0,function(e,t){let n=structuredClone(e);for(let[e,r]of Object.entries(t))e in n&&(n[e]=r);return n}])},199931,e=>{"use strict";let t=(0,e.i(475254).default)("waypoints",[["circle",{cx:"12",cy:"4.5",r:"2.5",key:"r5ysbb"}],["path",{d:"m10.2 6.3-3.9 3.9",key:"1nzqf6"}],["circle",{cx:"4.5",cy:"12",r:"2.5",key:"jydg6v"}],["path",{d:"M7 12h10",key:"b7w52i"}],["circle",{cx:"19.5",cy:"12",r:"2.5",key:"1piiel"}],["path",{d:"m13.8 17.7 3.9-3.9",key:"1wyg1y"}],["circle",{cx:"12",cy:"19.5",r:"2.5",key:"13o1pw"}]]);e.s(["Waypoints",0,t],199931)},625901,e=>{"use strict";var t=e.i(266027),n=e.i(621482),r=e.i(912598),a=e.i(243652),i=e.i(602869),o=e.i(135214);let l=(0,a.createQueryKeys)("models"),s=(0,a.createQueryKeys)("modelHub"),u=(0,a.createQueryKeys)("allProxyModels");(0,a.createQueryKeys)("selectedTeamModels");let c=(0,a.createQueryKeys)("infiniteModels"),d=(0,a.createQueryKeys)("userModels"),p=new Set,g=e=>!!e?.litellm_params?.model?.startsWith("auto_router/"),f=e=>new Set(e.filter(g).map(e=>e.model_name).filter(e=>!!e)),m=e=>e.filter(g),h=async(e,t,n)=>{let r=await (0,i.modelInfoCall)(e,t,n,1,1e3),a=r?.total_pages??1;return[r,...await Promise.all(Array.from({length:Math.max(0,a-1)},(r,a)=>(0,i.modelInfoCall)(e,t,n,a+2,1e3)))].flatMap(e=>e?.data??[])},x=(e,t)=>l.list({filters:{scope:"autoRouters",...e&&{userId:e},...t&&{userRole:t}}});e.s(["useAllProxyModels",0,()=>{let{accessToken:e,userId:n,userRole:r}=(0,o.default)();return(0,t.useQuery)({queryKey:u.list({}),queryFn:async()=>await (0,i.modelAvailableCall)(e,n,r,!0,null,!0,!1,"expand"),enabled:!!(e&&n&&r)})},"useAutoRouterModelGroups",0,()=>{let{accessToken:e,userId:n,userRole:r}=(0,o.default)(),{data:a}=(0,t.useQuery)({queryKey:x(n,r),queryFn:async()=>await h(e,n,r),enabled:!!(e&&n&&r),select:f});return a??p},"useAutoRouters",0,()=>{let{accessToken:e,userId:n,userRole:r}=(0,o.default)();return(0,t.useQuery)({queryKey:x(n,r),queryFn:async()=>await h(e,n,r),enabled:!!(e&&n&&r),select:m})},"useInfiniteModelInfo",0,(e=50,t)=>{let{accessToken:r,userId:a,userRole:l}=(0,o.default)();return(0,n.useInfiniteQuery)({queryKey:c.list({filters:{...a&&{userId:a},...l&&{userRole:l},size:e,...t&&{search:t}}}),queryFn:async({pageParam:n})=>await (0,i.modelInfoCall)(r,a,l,n,e,t),initialPageParam:1,getNextPageParam:e=>{if(e.current_page{let e=(0,r.useQueryClient)();return async()=>{await e.invalidateQueries({queryKey:l.lists()})}},"useModelHub",0,()=>{let{accessToken:e}=(0,o.default)();return(0,t.useQuery)({queryKey:s.list({}),queryFn:async()=>await (0,i.modelHubCall)(e),enabled:!!e})},"useModelsInfo",0,(e=1,n=50,r,a,s,u,c,d=!1)=>{let{accessToken:p,userId:g,userRole:f}=(0,o.default)();return(0,t.useQuery)({queryKey:l.list({filters:{...g&&{userId:g},...f&&{userRole:f},page:e,size:n,...r&&{search:r},...a&&{modelId:a},...s&&{teamId:s},...u&&{sortBy:u},...c&&{sortOrder:c},...d&&{excludeAutoRouters:"true"}}}),queryFn:async()=>await (0,i.modelInfoCall)(p,g,f,e,n,r,a,s,u,c,d),enabled:!!(p&&g&&f)})},"useUserModels",0,()=>{let{accessToken:e,userId:n,userRole:r}=(0,o.default)();return(0,t.useQuery)({queryKey:d.list({}),queryFn:async()=>(await (0,i.modelAvailableCall)(e,n,r)).data.map(e=>e.id),enabled:!!(e&&n&&r)})}])},548151,200208,399536,997422,146512,e=>{"use strict";var t=e.i(843476),n=e.i(271645),r=e.i(199931),a=e.i(625901),i=e.i(487486),o=e.i(115504);let l=new Set,s=(0,n.createContext)(l);function u(e){let t=(0,n.useContext)(s);return!!e&&t.has(e)}e.s(["AutoRouterIcon",0,function({size:e=12,className:n}){return(0,t.jsx)(r.Waypoints,{size:e,className:n,"aria-hidden":!0})},"AutoRouterModelGroupsProvider",0,function({children:e}){let n=(0,a.useAutoRouterModelGroups)();return(0,t.jsx)(s.Provider,{value:n,children:e})},"AutoRouterTag",0,function({modelGroup:e,className:n}){return u(e)?(0,t.jsxs)(i.Badge,{variant:"secondary",title:`Routed by auto-router "${e}"`,className:(0,o.cn)("gap-1.5 px-2.5 py-1 text-sm font-normal text-foreground",n),children:[(0,t.jsx)(r.Waypoints,{"aria-hidden":!0}),e]}):null},"useIsAutoRoutedModelGroup",0,u],548151);var c=e.i(581070);let d=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],p=e=>String(e).padStart(2,"0"),g=(e,t)=>"date"===t?`${d[e.getMonth()]} ${e.getDate()}, ${e.getFullYear()}`:`${d[e.getMonth()]} ${e.getDate()}, ${p(e.getHours())}:${p(e.getMinutes())}:${p(e.getSeconds())}`;e.s(["DateCell",0,function({value:e,precision:n="datetime",fallback:r="-"}){let a,i,o,l=e?new Date(e):null;return!l||Number.isNaN(l.getTime())?(0,t.jsx)("span",{className:"text-muted-foreground",children:r}):(0,t.jsx)(c.CellTooltip,{content:(a=Intl.DateTimeFormat().resolvedOptions().timeZone,i=`${d[l.getMonth()]} ${l.getDate()}, ${l.getFullYear()}`,o=`${p(l.getHours())}:${p(l.getMinutes())}:${p(l.getSeconds())}`,`${i}, ${o} (${a})`),trigger:(0,t.jsx)("span",{className:"whitespace-nowrap",children:g(l,n)})})},"formatCellDate",0,g],200208);var f=e.i(174886),m=e.i(500330);let h={pill:{base:"font-mono text-xs font-normal px-2 py-0.5 rounded-md text-left bg-blue-50 text-blue-500",clickable:"hover:bg-blue-100 cursor-pointer"},plain:{base:"font-mono text-xs text-left",clickable:"hover:text-blue-600 cursor-pointer"}};e.s(["IdCell",0,function({value:e,variant:n="pill",onClick:r,copyable:a=!1,truncate:i=!0,fallback:l="-",tooltip:s,disabled:u=!1,dataTestId:d,className:p}){if(!e)return(0,t.jsx)("span",{className:"text-muted-foreground",children:l});let g=!!r&&!u,x=(0,o.cn)(h[n].base,g&&h[n].clickable,i&&"block max-w-[15ch] truncate",u&&"opacity-50",p),y=g?(0,t.jsx)("button",{type:"button",className:x,"data-testid":d,onClick:()=>r(e),children:e}):(0,t.jsx)("span",{className:x,"data-testid":d,children:e}),b=(0,t.jsx)(c.CellTooltip,{content:s??e,trigger:y});return a?(0,t.jsxs)("span",{className:"inline-flex max-w-full items-center gap-1",children:[b,(0,t.jsx)("button",{type:"button","aria-label":"Copy ID",className:"shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",onClick:t=>{t.stopPropagation(),(0,m.copyToClipboard)(e)},children:(0,t.jsx)(f.Copy,{className:"size-3"})})]}):b}],399536);var x=e.i(463059);e.s(["IdentityCell",0,function({title:e,subtitle:n,badge:r,onClick:a,className:i,titleClassName:l}){let s=(0,t.jsxs)("div",{className:"flex min-w-0 flex-col gap-0.5",children:[(0,t.jsx)("span",{className:(0,o.cn)("truncate text-sm font-medium text-foreground",l),children:e}),(null!=n&&""!==n||null!=r)&&(0,t.jsxs)("span",{className:"flex min-w-0 items-center gap-2",children:[null!=n&&""!==n&&(0,t.jsx)("span",{className:"truncate font-mono text-xs text-muted-foreground",children:n}),r]})]});return null!=a?(0,t.jsxs)("button",{type:"button",onClick:a,className:(0,o.cn)("group -mx-2 flex w-[calc(100%+1rem)] cursor-pointer items-center gap-2 rounded-md px-2 py-1 text-left transition-colors hover:bg-muted",i),children:[s,(0,t.jsx)(x.ChevronRight,{className:"ml-auto size-4 shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100"})]}):(0,t.jsx)("div",{className:(0,o.cn)("min-w-0",i),children:s})}],997422);let y={hasModelAccess:!1,label:"Management"},b={hasModelAccess:!1,label:"Read-only"},v={hasModelAccess:!1,label:"SCIM"},C={hasModelAccess:!0,label:null},S=e=>e.startsWith("/scim"),E=(e,t)=>1===e.length&&e[0]===t;e.s(["deriveKeyModelScope",0,(e,t)=>"management"===t?y:"read_only"===t?b:Array.isArray(e)&&0!==e.length?e.every(S)?v:E(e,"management_routes")?y:E(e,"info_routes")?b:C:C],146512)},355619,e=>{"use strict";var t=e.i(602869);let n=async(e,n,r)=>{try{if(null===e||null===n)return;if(null!==r){let a=(await (0,t.modelAvailableCall)(r,e,n,!0,null,!0)).data.map(e=>e.id),i=[],o=[];return a.forEach(e=>{e.endsWith("/*")?i.push(e):o.push(e)}),[...i,...o]}}catch(e){console.error("Error fetching user models:",e)}};e.s(["excludeProxyWideSentinel",0,e=>e.filter(e=>"all-proxy-models"!==e),"fetchAvailableModelsForTeamOrKey",0,n,"getModelDisplayName",0,e=>{if("all-proxy-models"===e)return"All Proxy Models";if(e.endsWith("/*")){let t=e.replace("/*","");return`All ${t} models`}return e},"hasAllModelsSentinel",0,e=>e.includes("all-proxy-models")||e.includes("all-team-models"),"unfurlWildcardModelsInList",0,(e,t)=>{let n=[],r=[];return e.forEach(e=>{if(e.endsWith("/*")){let a=e.replace("/*",""),i=t.filter(e=>e.startsWith(a+"/"));r.push(...i),n.push(e)}else r.push(e)}),[...n,...r].filter((e,t,n)=>n.indexOf(e)===t)}])},622826,547227,964471,630500,e=>{"use strict";e.i(548151);var t=e.i(581070);e.i(200208),e.i(399536),e.i(997422);var n=e.i(843476),r=e.i(146512),a=e.i(355619),i=e.i(487486);let o="all-proxy-models",l=e=>{if(e===o)return"All Proxy Models";let t=(0,a.getModelDisplayName)(e);return t.length>30?`${t.slice(0,30)}...`:t};e.s(["ModelsCell",0,function({models:e,maxVisible:a=3,allowedRoutes:s,keyType:u}){if(!Array.isArray(e)||0===e.length){let e=(0,r.deriveKeyModelScope)(s,u);return e.hasModelAccess?(0,n.jsx)(i.Badge,{variant:"secondary",children:"All Proxy Models"}):(0,n.jsx)(t.CellTooltip,{content:`Scoped to ${e.label} routes; this key cannot call any models`,trigger:(0,n.jsx)(i.Badge,{variant:"secondary",className:"cursor-default",children:"No model access"})})}let c=e.slice(0,a),d=e.slice(a);return(0,n.jsxs)("div",{className:"flex flex-wrap items-center gap-1",children:[c.map((e,t)=>(0,n.jsx)(i.Badge,{variant:e===o?"secondary":"outline",children:l(e)},t)),d.length>0&&(0,n.jsx)(t.CellTooltip,{content:(0,n.jsx)("div",{className:"flex max-w-[280px] flex-col gap-0.5",children:d.map((e,t)=>(0,n.jsx)("span",{children:l(e)},t))}),trigger:(0,n.jsxs)(i.Badge,{variant:"outline",className:"cursor-default",children:["+",d.length," more"]})})]})}],547227);var s=e.i(500330);e.s(["MoneyCell",0,function({value:e,decimals:t=4,emptyText:r="-",showZero:a=!1}){return null==e||Number.isNaN(e)?(0,n.jsx)("span",{className:"text-muted-foreground",children:r}):0===e?a?(0,n.jsx)("span",{className:"whitespace-nowrap",children:`$${(0,s.formatNumberWithCommas)(0,t,!1,!0)}`}):(0,n.jsx)("span",{className:"text-muted-foreground",children:"-"}):(0,n.jsx)("span",{className:"whitespace-nowrap",children:(0,s.getSpendString)(e,t)})}],964471);var u=e.i(944835);e.s(["SpendBudgetCell",0,function({spend:e,maxBudget:t,teamMaxBudget:r}){let a="number"!=typeof e||Number.isNaN(e)?0:e,i=t??r??null,o=null==t&&null!=r,l="number"==typeof i&&i>0,c=l?a/i*100:0,d=a>0?(0,s.getSpendString)(a,4):"$0.00",p=null===i?"· Unlimited":`of $${(0,s.formatNumberWithCommas)(i)}${o?" (Team)":""}`;return(0,n.jsxs)("div",{className:"flex min-w-[130px] flex-col gap-1",children:[(0,n.jsxs)("div",{className:"whitespace-nowrap text-xs",children:[(0,n.jsx)("span",{className:"font-medium tabular-nums text-foreground",children:d})," ",(0,n.jsx)("span",{className:"text-muted-foreground",children:p})]}),l&&(0,n.jsx)(u.Meter,{value:a,max:i,"aria-valuetext":`${d} of $${(0,s.formatNumberWithCommas)(i)}`,children:(0,n.jsx)(u.MeterTrack,{children:(0,n.jsx)(u.MeterIndicator,{tone:c>100?"over":c>=80?"warning":"default"})})})]})}],630500),e.i(112179),e.s([],622826)}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/02-alpsjfp5b7.js b/litellm/proxy/_experimental/out/_next/static/chunks/02-alpsjfp5b7.js new file mode 100644 index 00000000000..d4ee3ff9e92 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/02-alpsjfp5b7.js @@ -0,0 +1,10 @@ +(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,175712,e=>{"use strict";e.i(247167);var t=e.i(271645),i=e.i(343794),a=e.i(529681),l=e.i(242064),r=e.i(517455),n=e.i(185793),s=e.i(721369),o=function(e,t){var i={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(i[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var l=0,a=Object.getOwnPropertySymbols(e);lt.indexOf(a[l])&&Object.prototype.propertyIsEnumerable.call(e,a[l])&&(i[a[l]]=e[a[l]]);return i};let d=e=>{var{prefixCls:a,className:r,hoverable:n=!0}=e,s=o(e,["prefixCls","className","hoverable"]);let{getPrefixCls:d}=t.useContext(l.ConfigContext),A=d("card",a),c=(0,i.default)(`${A}-grid`,r,{[`${A}-grid-hoverable`]:n});return t.createElement("div",Object.assign({},s,{className:c}))};e.i(296059);var A=e.i(915654),c=e.i(183293),u=e.i(246422),g=e.i(838378);let h=(0,u.genStyleHooks)("Card",e=>{let t=(0,g.mergeToken)(e,{cardShadow:e.boxShadowCard,cardHeadPadding:e.padding,cardPaddingBase:e.paddingLG,cardActionsIconSize:e.fontSize});return[(e=>{let{componentCls:t,cardShadow:i,cardHeadPadding:a,colorBorderSecondary:l,boxShadowTertiary:r,bodyPadding:n,extraColor:s}=e;return{[t]:Object.assign(Object.assign({},(0,c.resetComponent)(e)),{position:"relative",background:e.colorBgContainer,borderRadius:e.borderRadiusLG,[`&:not(${t}-bordered)`]:{boxShadow:r},[`${t}-head`]:(e=>{let{antCls:t,componentCls:i,headerHeight:a,headerPadding:l,tabsMarginBottom:r}=e;return Object.assign(Object.assign({display:"flex",justifyContent:"center",flexDirection:"column",minHeight:a,marginBottom:-1,padding:`0 ${(0,A.unit)(l)}`,color:e.colorTextHeading,fontWeight:e.fontWeightStrong,fontSize:e.headerFontSize,background:e.headerBg,borderBottom:`${(0,A.unit)(e.lineWidth)} ${e.lineType} ${e.colorBorderSecondary}`,borderRadius:`${(0,A.unit)(e.borderRadiusLG)} ${(0,A.unit)(e.borderRadiusLG)} 0 0`},(0,c.clearFix)()),{"&-wrapper":{width:"100%",display:"flex",alignItems:"center"},"&-title":Object.assign(Object.assign({display:"inline-block",flex:1},c.textEllipsis),{[` + > ${i}-typography, + > ${i}-typography-edit-content + `]:{insetInlineStart:0,marginTop:0,marginBottom:0}}),[`${t}-tabs-top`]:{clear:"both",marginBottom:r,color:e.colorText,fontWeight:"normal",fontSize:e.fontSize,"&-bar":{borderBottom:`${(0,A.unit)(e.lineWidth)} ${e.lineType} ${e.colorBorderSecondary}`}}})})(e),[`${t}-extra`]:{marginInlineStart:"auto",color:s,fontWeight:"normal",fontSize:e.fontSize},[`${t}-body`]:{padding:n,borderRadius:`0 0 ${(0,A.unit)(e.borderRadiusLG)} ${(0,A.unit)(e.borderRadiusLG)}`},[`${t}-grid`]:(e=>{let{cardPaddingBase:t,colorBorderSecondary:i,cardShadow:a,lineWidth:l}=e;return{width:"33.33%",padding:t,border:0,borderRadius:0,boxShadow:` + ${(0,A.unit)(l)} 0 0 0 ${i}, + 0 ${(0,A.unit)(l)} 0 0 ${i}, + ${(0,A.unit)(l)} ${(0,A.unit)(l)} 0 0 ${i}, + ${(0,A.unit)(l)} 0 0 0 ${i} inset, + 0 ${(0,A.unit)(l)} 0 0 ${i} inset; + `,transition:`all ${e.motionDurationMid}`,"&-hoverable:hover":{position:"relative",zIndex:1,boxShadow:a}}})(e),[`${t}-cover`]:{"> *":{display:"block",width:"100%",borderRadius:`${(0,A.unit)(e.borderRadiusLG)} ${(0,A.unit)(e.borderRadiusLG)} 0 0`}},[`${t}-actions`]:(e=>{let{componentCls:t,iconCls:i,actionsLiMargin:a,cardActionsIconSize:l,colorBorderSecondary:r,actionsBg:n}=e;return Object.assign(Object.assign({margin:0,padding:0,listStyle:"none",background:n,borderTop:`${(0,A.unit)(e.lineWidth)} ${e.lineType} ${r}`,display:"flex",borderRadius:`0 0 ${(0,A.unit)(e.borderRadiusLG)} ${(0,A.unit)(e.borderRadiusLG)}`},(0,c.clearFix)()),{"& > li":{margin:a,color:e.colorTextDescription,textAlign:"center","> span":{position:"relative",display:"block",minWidth:e.calc(e.cardActionsIconSize).mul(2).equal(),fontSize:e.fontSize,lineHeight:e.lineHeight,cursor:"pointer","&:hover":{color:e.colorPrimary,transition:`color ${e.motionDurationMid}`},[`a:not(${t}-btn), > ${i}`]:{display:"inline-block",width:"100%",color:e.colorIcon,lineHeight:(0,A.unit)(e.fontHeight),transition:`color ${e.motionDurationMid}`,"&:hover":{color:e.colorPrimary}},[`> ${i}`]:{fontSize:l,lineHeight:(0,A.unit)(e.calc(l).mul(e.lineHeight).equal())}},"&:not(:last-child)":{borderInlineEnd:`${(0,A.unit)(e.lineWidth)} ${e.lineType} ${r}`}}})})(e),[`${t}-meta`]:Object.assign(Object.assign({margin:`${(0,A.unit)(e.calc(e.marginXXS).mul(-1).equal())} 0`,display:"flex"},(0,c.clearFix)()),{"&-avatar":{paddingInlineEnd:e.padding},"&-detail":{overflow:"hidden",flex:1,"> div:not(:last-child)":{marginBottom:e.marginXS}},"&-title":Object.assign({color:e.colorTextHeading,fontWeight:e.fontWeightStrong,fontSize:e.fontSizeLG},c.textEllipsis),"&-description":{color:e.colorTextDescription}})}),[`${t}-bordered`]:{border:`${(0,A.unit)(e.lineWidth)} ${e.lineType} ${l}`,[`${t}-cover`]:{marginTop:-1,marginInlineStart:-1,marginInlineEnd:-1}},[`${t}-hoverable`]:{cursor:"pointer",transition:`box-shadow ${e.motionDurationMid}, border-color ${e.motionDurationMid}`,"&:hover":{borderColor:"transparent",boxShadow:i}},[`${t}-contain-grid`]:{borderRadius:`${(0,A.unit)(e.borderRadiusLG)} ${(0,A.unit)(e.borderRadiusLG)} 0 0 `,[`${t}-body`]:{display:"flex",flexWrap:"wrap"},[`&:not(${t}-loading) ${t}-body`]:{marginBlockStart:e.calc(e.lineWidth).mul(-1).equal(),marginInlineStart:e.calc(e.lineWidth).mul(-1).equal(),padding:0}},[`${t}-contain-tabs`]:{[`> div${t}-head`]:{minHeight:0,[`${t}-head-title, ${t}-extra`]:{paddingTop:a}}},[`${t}-type-inner`]:(e=>{let{componentCls:t,colorFillAlter:i,headerPadding:a,bodyPadding:l}=e;return{[`${t}-head`]:{padding:`0 ${(0,A.unit)(a)}`,background:i,"&-title":{fontSize:e.fontSize}},[`${t}-body`]:{padding:`${(0,A.unit)(e.padding)} ${(0,A.unit)(l)}`}}})(e),[`${t}-loading`]:(e=>{let{componentCls:t}=e;return{overflow:"hidden",[`${t}-body`]:{userSelect:"none"}}})(e),[`${t}-rtl`]:{direction:"rtl"}}})(t),(e=>{let{componentCls:t,bodyPaddingSM:i,headerPaddingSM:a,headerHeightSM:l,headerFontSizeSM:r}=e;return{[`${t}-small`]:{[`> ${t}-head`]:{minHeight:l,padding:`0 ${(0,A.unit)(a)}`,fontSize:r,[`> ${t}-head-wrapper`]:{[`> ${t}-extra`]:{fontSize:e.fontSize}}},[`> ${t}-body`]:{padding:i}},[`${t}-small${t}-contain-tabs`]:{[`> ${t}-head`]:{[`${t}-head-title, ${t}-extra`]:{paddingTop:0,display:"flex",alignItems:"center"}}}}})(t)]},e=>{var t,i;return{headerBg:"transparent",headerFontSize:e.fontSizeLG,headerFontSizeSM:e.fontSize,headerHeight:e.fontSizeLG*e.lineHeightLG+2*e.padding,headerHeightSM:e.fontSize*e.lineHeight+2*e.paddingXS,actionsBg:e.colorBgContainer,actionsLiMargin:`${e.paddingSM}px 0`,tabsMarginBottom:-e.padding-e.lineWidth,extraColor:e.colorText,bodyPaddingSM:12,headerPaddingSM:12,bodyPadding:null!=(t=e.bodyPadding)?t:e.paddingLG,headerPadding:null!=(i=e.headerPadding)?i:e.paddingLG}});var m=e.i(792812),b=function(e,t){var i={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(i[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var l=0,a=Object.getOwnPropertySymbols(e);lt.indexOf(a[l])&&Object.prototype.propertyIsEnumerable.call(e,a[l])&&(i[a[l]]=e[a[l]]);return i};let f=e=>{let{actionClasses:i,actions:a=[],actionStyle:l}=e;return t.createElement("ul",{className:i,style:l},a.map((e,i)=>{let l=`action-${i}`;return t.createElement("li",{style:{width:`${100/a.length}%`},key:l},t.createElement("span",null,e))}))},p=t.forwardRef((e,o)=>{let A,{prefixCls:c,className:u,rootClassName:g,style:p,extra:O,headStyle:x={},bodyStyle:E={},title:I,loading:v,bordered:y,variant:C,size:w,type:S,cover:R,actions:L,tabList:_,children:B,activeTabKey:T,defaultActiveTabKey:k,tabBarExtraContent:$,hoverable:H,tabProps:M={},classNames:j,styles:N}=e,D=b(e,["prefixCls","className","rootClassName","style","extra","headStyle","bodyStyle","title","loading","bordered","variant","size","type","cover","actions","tabList","children","activeTabKey","defaultActiveTabKey","tabBarExtraContent","hoverable","tabProps","classNames","styles"]),{getPrefixCls:U,direction:z,card:P}=t.useContext(l.ConfigContext),[W]=(0,m.default)("card",C,y),G=e=>{var t;return(0,i.default)(null==(t=null==P?void 0:P.classNames)?void 0:t[e],null==j?void 0:j[e])},q=e=>{var t;return Object.assign(Object.assign({},null==(t=null==P?void 0:P.styles)?void 0:t[e]),null==N?void 0:N[e])},Q=t.useMemo(()=>{let e=!1;return t.Children.forEach(B,t=>{(null==t?void 0:t.type)===d&&(e=!0)}),e},[B]),F=U("card",c),[V,K,Y]=h(F),J=t.createElement(n.default,{loading:!0,active:!0,paragraph:{rows:4},title:!1},B),X=void 0!==T,Z=Object.assign(Object.assign({},M),{[X?"activeKey":"defaultActiveKey"]:X?T:k,tabBarExtraContent:$}),ee=(0,r.default)(w),et=ee&&"default"!==ee?ee:"large",ei=_?t.createElement(s.default,Object.assign({size:et},Z,{className:`${F}-head-tabs`,onChange:t=>{var i;null==(i=e.onTabChange)||i.call(e,t)},items:_.map(e=>{var{tab:t}=e;return Object.assign({label:t},b(e,["tab"]))})})):null;if(I||O||ei){let e=(0,i.default)(`${F}-head`,G("header")),a=(0,i.default)(`${F}-head-title`,G("title")),l=(0,i.default)(`${F}-extra`,G("extra")),r=Object.assign(Object.assign({},x),q("header"));A=t.createElement("div",{className:e,style:r},t.createElement("div",{className:`${F}-head-wrapper`},I&&t.createElement("div",{className:a,style:q("title")},I),O&&t.createElement("div",{className:l,style:q("extra")},O)),ei)}let ea=(0,i.default)(`${F}-cover`,G("cover")),el=R?t.createElement("div",{className:ea,style:q("cover")},R):null,er=(0,i.default)(`${F}-body`,G("body")),en=Object.assign(Object.assign({},E),q("body")),es=t.createElement("div",{className:er,style:en},v?J:B),eo=(0,i.default)(`${F}-actions`,G("actions")),ed=(null==L?void 0:L.length)?t.createElement(f,{actionClasses:eo,actionStyle:q("actions"),actions:L}):null,eA=(0,a.default)(D,["onTabChange"]),ec=(0,i.default)(F,null==P?void 0:P.className,{[`${F}-loading`]:v,[`${F}-bordered`]:"borderless"!==W,[`${F}-hoverable`]:H,[`${F}-contain-grid`]:Q,[`${F}-contain-tabs`]:null==_?void 0:_.length,[`${F}-${ee}`]:ee,[`${F}-type-${S}`]:!!S,[`${F}-rtl`]:"rtl"===z},u,g,K,Y),eu=Object.assign(Object.assign({},null==P?void 0:P.style),p);return V(t.createElement("div",Object.assign({ref:o},eA,{className:ec,style:eu}),A,el,es,ed))});var O=function(e,t){var i={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(i[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var l=0,a=Object.getOwnPropertySymbols(e);lt.indexOf(a[l])&&Object.prototype.propertyIsEnumerable.call(e,a[l])&&(i[a[l]]=e[a[l]]);return i};p.Grid=d,p.Meta=e=>{let{prefixCls:a,className:r,avatar:n,title:s,description:o}=e,d=O(e,["prefixCls","className","avatar","title","description"]),{getPrefixCls:A}=t.useContext(l.ConfigContext),c=A("card",a),u=(0,i.default)(`${c}-meta`,r),g=n?t.createElement("div",{className:`${c}-meta-avatar`},n):null,h=s?t.createElement("div",{className:`${c}-meta-title`},s):null,m=o?t.createElement("div",{className:`${c}-meta-description`},o):null,b=h||m?t.createElement("div",{className:`${c}-meta-detail`},h,m):null;return t.createElement("div",Object.assign({},d,{className:u}),g,b)},e.s(["Card",0,p],175712)},869216,e=>{"use strict";e.i(247167);var t=e.i(271645),i=e.i(343794),a=e.i(908206),l=e.i(242064),r=e.i(517455),n=e.i(150073);let s={xxl:3,xl:3,lg:3,md:3,sm:2,xs:1},o=t.default.createContext({});var d=e.i(876556),A=function(e,t){var i={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(i[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var l=0,a=Object.getOwnPropertySymbols(e);lt.indexOf(a[l])&&Object.prototype.propertyIsEnumerable.call(e,a[l])&&(i[a[l]]=e[a[l]]);return i},c=function(e,t){var i={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(i[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var l=0,a=Object.getOwnPropertySymbols(e);lt.indexOf(a[l])&&Object.prototype.propertyIsEnumerable.call(e,a[l])&&(i[a[l]]=e[a[l]]);return i};let u=e=>{let{itemPrefixCls:a,component:l,span:r,className:n,style:s,labelStyle:d,contentStyle:A,bordered:c,label:u,content:g,colon:h,type:m,styles:b}=e,{classNames:f}=t.useContext(o),p=Object.assign(Object.assign({},d),null==b?void 0:b.label),O=Object.assign(Object.assign({},A),null==b?void 0:b.content);if(c)return t.createElement(l,{colSpan:r,style:s,className:(0,i.default)(n,{[`${a}-item-${m}`]:"label"===m||"content"===m,[null==f?void 0:f.label]:(null==f?void 0:f.label)&&"label"===m,[null==f?void 0:f.content]:(null==f?void 0:f.content)&&"content"===m})},null!=u&&t.createElement("span",{style:p},u),null!=g&&t.createElement("span",{style:O},g));return t.createElement(l,{colSpan:r,style:s,className:(0,i.default)(`${a}-item`,n)},t.createElement("div",{className:`${a}-item-container`},null!=u&&t.createElement("span",{style:p,className:(0,i.default)(`${a}-item-label`,null==f?void 0:f.label,{[`${a}-item-no-colon`]:!h})},u),null!=g&&t.createElement("span",{style:O,className:(0,i.default)(`${a}-item-content`,null==f?void 0:f.content)},g)))};function g(e,{colon:i,prefixCls:a,bordered:l},{component:r,type:n,showLabel:s,showContent:o,labelStyle:d,contentStyle:A,styles:c}){return e.map(({label:e,children:g,prefixCls:h=a,className:m,style:b,labelStyle:f,contentStyle:p,span:O=1,key:x,styles:E},I)=>"string"==typeof r?t.createElement(u,{key:`${n}-${x||I}`,className:m,style:b,styles:{label:Object.assign(Object.assign(Object.assign(Object.assign({},d),null==c?void 0:c.label),f),null==E?void 0:E.label),content:Object.assign(Object.assign(Object.assign(Object.assign({},A),null==c?void 0:c.content),p),null==E?void 0:E.content)},span:O,colon:i,component:r,itemPrefixCls:h,bordered:l,label:s?e:null,content:o?g:null,type:n}):[t.createElement(u,{key:`label-${x||I}`,className:m,style:Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},d),null==c?void 0:c.label),b),f),null==E?void 0:E.label),span:1,colon:i,component:r[0],itemPrefixCls:h,bordered:l,label:e,type:"label"}),t.createElement(u,{key:`content-${x||I}`,className:m,style:Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},A),null==c?void 0:c.content),b),p),null==E?void 0:E.content),span:2*O-1,component:r[1],itemPrefixCls:h,bordered:l,content:g,type:"content"})])}let h=e=>{let i=t.useContext(o),{prefixCls:a,vertical:l,row:r,index:n,bordered:s}=e;return l?t.createElement(t.Fragment,null,t.createElement("tr",{key:`label-${n}`,className:`${a}-row`},g(r,e,Object.assign({component:"th",type:"label",showLabel:!0},i))),t.createElement("tr",{key:`content-${n}`,className:`${a}-row`},g(r,e,Object.assign({component:"td",type:"content",showContent:!0},i)))):t.createElement("tr",{key:n,className:`${a}-row`},g(r,e,Object.assign({component:s?["th","td"]:"td",type:"item",showLabel:!0,showContent:!0},i)))};e.i(296059);var m=e.i(915654),b=e.i(183293),f=e.i(246422),p=e.i(838378);let O=(0,f.genStyleHooks)("Descriptions",e=>(e=>{let{componentCls:t,extraColor:i,itemPaddingBottom:a,itemPaddingEnd:l,colonMarginRight:r,colonMarginLeft:n,titleMarginBottom:s}=e;return{[t]:Object.assign(Object.assign(Object.assign({},(0,b.resetComponent)(e)),(e=>{let{componentCls:t,labelBg:i}=e;return{[`&${t}-bordered`]:{[`> ${t}-view`]:{border:`${(0,m.unit)(e.lineWidth)} ${e.lineType} ${e.colorSplit}`,"> table":{tableLayout:"auto"},[`${t}-row`]:{borderBottom:`${(0,m.unit)(e.lineWidth)} ${e.lineType} ${e.colorSplit}`,"&:first-child":{"> th:first-child, > td:first-child":{borderStartStartRadius:e.borderRadiusLG}},"&:last-child":{borderBottom:"none","> th:first-child, > td:first-child":{borderEndStartRadius:e.borderRadiusLG}},[`> ${t}-item-label, > ${t}-item-content`]:{padding:`${(0,m.unit)(e.padding)} ${(0,m.unit)(e.paddingLG)}`,borderInlineEnd:`${(0,m.unit)(e.lineWidth)} ${e.lineType} ${e.colorSplit}`,"&:last-child":{borderInlineEnd:"none"}},[`> ${t}-item-label`]:{color:e.colorTextSecondary,backgroundColor:i,"&::after":{display:"none"}}}},[`&${t}-middle`]:{[`${t}-row`]:{[`> ${t}-item-label, > ${t}-item-content`]:{padding:`${(0,m.unit)(e.paddingSM)} ${(0,m.unit)(e.paddingLG)}`}}},[`&${t}-small`]:{[`${t}-row`]:{[`> ${t}-item-label, > ${t}-item-content`]:{padding:`${(0,m.unit)(e.paddingXS)} ${(0,m.unit)(e.padding)}`}}}}}})(e)),{"&-rtl":{direction:"rtl"},[`${t}-header`]:{display:"flex",alignItems:"center",marginBottom:s},[`${t}-title`]:Object.assign(Object.assign({},b.textEllipsis),{flex:"auto",color:e.titleColor,fontWeight:e.fontWeightStrong,fontSize:e.fontSizeLG,lineHeight:e.lineHeightLG}),[`${t}-extra`]:{marginInlineStart:"auto",color:i,fontSize:e.fontSize},[`${t}-view`]:{width:"100%",borderRadius:e.borderRadiusLG,table:{width:"100%",tableLayout:"fixed",borderCollapse:"collapse"}},[`${t}-row`]:{"> th, > td":{paddingBottom:a,paddingInlineEnd:l},"> th:last-child, > td:last-child":{paddingInlineEnd:0},"&:last-child":{borderBottom:"none","> th, > td":{paddingBottom:0}}},[`${t}-item-label`]:{color:e.labelColor,fontWeight:"normal",fontSize:e.fontSize,lineHeight:e.lineHeight,textAlign:"start","&::after":{content:'":"',position:"relative",top:-.5,marginInline:`${(0,m.unit)(n)} ${(0,m.unit)(r)}`},[`&${t}-item-no-colon::after`]:{content:'""'}},[`${t}-item-no-label`]:{"&::after":{margin:0,content:'""'}},[`${t}-item-content`]:{display:"table-cell",flex:1,color:e.contentColor,fontSize:e.fontSize,lineHeight:e.lineHeight,wordBreak:"break-word",overflowWrap:"break-word"},[`${t}-item`]:{paddingBottom:0,verticalAlign:"top","&-container":{display:"flex",[`${t}-item-label`]:{display:"inline-flex",alignItems:"baseline"},[`${t}-item-content`]:{display:"inline-flex",alignItems:"baseline",minWidth:"1em"}}},"&-middle":{[`${t}-row`]:{"> th, > td":{paddingBottom:e.paddingSM}}},"&-small":{[`${t}-row`]:{"> th, > td":{paddingBottom:e.paddingXS}}}})}})((0,p.mergeToken)(e,{})),e=>({labelBg:e.colorFillAlter,labelColor:e.colorTextTertiary,titleColor:e.colorText,titleMarginBottom:e.fontSizeSM*e.lineHeightSM,itemPaddingBottom:e.padding,itemPaddingEnd:e.padding,colonMarginRight:e.marginXS,colonMarginLeft:e.marginXXS/2,contentColor:e.colorText,extraColor:e.colorText}));var x=function(e,t){var i={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(i[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var l=0,a=Object.getOwnPropertySymbols(e);lt.indexOf(a[l])&&Object.prototype.propertyIsEnumerable.call(e,a[l])&&(i[a[l]]=e[a[l]]);return i};let E=e=>{let u,{prefixCls:g,title:m,extra:b,column:f,colon:p=!0,bordered:E,layout:I,children:v,className:y,rootClassName:C,style:w,size:S,labelStyle:R,contentStyle:L,styles:_,items:B,classNames:T}=e,k=x(e,["prefixCls","title","extra","column","colon","bordered","layout","children","className","rootClassName","style","size","labelStyle","contentStyle","styles","items","classNames"]),{getPrefixCls:$,direction:H,className:M,style:j,classNames:N,styles:D}=(0,l.useComponentConfig)("descriptions"),U=$("descriptions",g),z=(0,n.default)(),P=t.useMemo(()=>{var e;return"number"==typeof f?f:null!=(e=(0,a.matchScreen)(z,Object.assign(Object.assign({},s),f)))?e:3},[z,f]),W=(u=t.useMemo(()=>B||(0,d.default)(v).map(e=>Object.assign(Object.assign({},null==e?void 0:e.props),{key:e.key})),[B,v]),t.useMemo(()=>u.map(e=>{var{span:t}=e,i=A(e,["span"]);return"filled"===t?Object.assign(Object.assign({},i),{filled:!0}):Object.assign(Object.assign({},i),{span:"number"==typeof t?t:(0,a.matchScreen)(z,t)})}),[u,z])),G=(0,r.default)(S),q=((e,i)=>{let[a,l]=(0,t.useMemo)(()=>{let t,a,l,r;return t=[],a=[],l=!1,r=0,i.filter(e=>e).forEach(i=>{let{filled:n}=i,s=c(i,["filled"]);if(n){a.push(s),t.push(a),a=[],r=0;return}let o=e-r;(r+=i.span||1)>=e?(r>e?(l=!0,a.push(Object.assign(Object.assign({},s),{span:o}))):a.push(s),t.push(a),a=[],r=0):a.push(s)}),a.length>0&&t.push(a),[t=t.map(t=>{let i=t.reduce((e,t)=>e+(t.span||1),0);if(i({labelStyle:R,contentStyle:L,styles:{content:Object.assign(Object.assign({},D.content),null==_?void 0:_.content),label:Object.assign(Object.assign({},D.label),null==_?void 0:_.label)},classNames:{label:(0,i.default)(N.label,null==T?void 0:T.label),content:(0,i.default)(N.content,null==T?void 0:T.content)}}),[R,L,_,T,N,D]);return Q(t.createElement(o.Provider,{value:K},t.createElement("div",Object.assign({className:(0,i.default)(U,M,N.root,null==T?void 0:T.root,{[`${U}-${G}`]:G&&"default"!==G,[`${U}-bordered`]:!!E,[`${U}-rtl`]:"rtl"===H},y,C,F,V),style:Object.assign(Object.assign(Object.assign(Object.assign({},j),D.root),null==_?void 0:_.root),w)},k),(m||b)&&t.createElement("div",{className:(0,i.default)(`${U}-header`,N.header,null==T?void 0:T.header),style:Object.assign(Object.assign({},D.header),null==_?void 0:_.header)},m&&t.createElement("div",{className:(0,i.default)(`${U}-title`,N.title,null==T?void 0:T.title),style:Object.assign(Object.assign({},D.title),null==_?void 0:_.title)},m),b&&t.createElement("div",{className:(0,i.default)(`${U}-extra`,N.extra,null==T?void 0:T.extra),style:Object.assign(Object.assign({},D.extra),null==_?void 0:_.extra)},b)),t.createElement("div",{className:`${U}-view`},t.createElement("table",null,t.createElement("tbody",null,q.map((e,i)=>t.createElement(h,{key:i,index:i,colon:p,prefixCls:U,vertical:"vertical"===I,bordered:E,row:e}))))))))};E.Item=({children:e})=>e,e.s(["Descriptions",0,E],869216)},913383,e=>{e.q("/litellm-asset-prefix/_next/static/media/a2a_agent.14el5-6dflh1h.png")},238564,e=>{e.q("/litellm-asset-prefix/_next/static/media/ai21.0m_u-tih8nm0v.svg")},941533,e=>{e.q("/litellm-asset-prefix/_next/static/media/aiml_api.1dq6dbpwklhlg.svg")},994636,e=>{e.q("/litellm-asset-prefix/_next/static/media/anthropic.3s95kgy8jpc64.svg")},824770,e=>{e.q("/litellm-asset-prefix/_next/static/media/assemblyai_small.0w_dslsra91uw.png")},920076,e=>{e.q("/litellm-asset-prefix/_next/static/media/baseten.41k2lnwp3c5g9.svg")},519380,e=>{e.q("/litellm-asset-prefix/_next/static/media/bedrock.2vhamxr8q6y4o.svg")},96889,e=>{e.q("/litellm-asset-prefix/_next/static/media/cerebras.1ur1xyfqk9ncz.svg")},348026,e=>{e.q("/litellm-asset-prefix/_next/static/media/cloudflare.2n1spdq7u5yki.svg")},727148,e=>{e.q("/litellm-asset-prefix/_next/static/media/cohere.22i3i2e449j5i.svg")},411703,e=>{e.q("/litellm-asset-prefix/_next/static/media/cometapi.3w0xficbg3dkk.svg")},922480,e=>{e.q("/litellm-asset-prefix/_next/static/media/cursor.1q1ev-_5l7exg.svg")},769490,e=>{e.q("/litellm-asset-prefix/_next/static/media/databricks.2hiet9qlqzn-g.svg")},829789,e=>{e.q("/litellm-asset-prefix/_next/static/media/deepgram.3-krp20p_xed3.png")},41730,e=>{e.q("/litellm-asset-prefix/_next/static/media/deepinfra.3lr_lsr7mhui6.png")},662576,e=>{e.q("/litellm-asset-prefix/_next/static/media/deepseek.3n4cu0x32i_7w.svg")},987202,e=>{e.q("/litellm-asset-prefix/_next/static/media/elevenlabs.2982m_dk2-h_y.png")},953265,e=>{e.q("/litellm-asset-prefix/_next/static/media/fal_ai.3rahrirki8sby.jpg")},346512,e=>{e.q("/litellm-asset-prefix/_next/static/media/featherless.3hmlef3fhc0h5.svg")},84416,e=>{e.q("/litellm-asset-prefix/_next/static/media/fireworks.3t1b6p8edeqyo.svg")},104602,e=>{e.q("/litellm-asset-prefix/_next/static/media/friendli.0ymiswh6l35bq.svg")},888193,e=>{e.q("/litellm-asset-prefix/_next/static/media/github_copilot.3k-8jyadoaq2u.svg")},946013,e=>{e.q("/litellm-asset-prefix/_next/static/media/google.3y8ypywwtqob_.svg")},447500,e=>{e.q("/litellm-asset-prefix/_next/static/media/groq.20csmqzsusp1k.svg")},270039,e=>{e.q("/litellm-asset-prefix/_next/static/media/huggingface.1-07ypt9ii_-p.svg")},227733,e=>{e.q("/litellm-asset-prefix/_next/static/media/hyperbolic.3le20v59sebn8.svg")},823929,e=>{e.q("/litellm-asset-prefix/_next/static/media/infinity.0s9s12bl4lccx.png")},333845,e=>{e.q("/litellm-asset-prefix/_next/static/media/jina.0ukab8o-3-5m_.png")},813878,e=>{e.q("/litellm-asset-prefix/_next/static/media/lambda.26_vz7cmyuodo.svg")},767788,e=>{e.q("/litellm-asset-prefix/_next/static/media/lmstudio.35s3-83mlhcms.svg")},808630,e=>{e.q("/litellm-asset-prefix/_next/static/media/meta_llama.1kxk24vwsem49.svg")},641266,e=>{e.q("/litellm-asset-prefix/_next/static/media/microsoft_azure.3626-7zx7wf09.svg")},468732,e=>{e.q("/litellm-asset-prefix/_next/static/media/minimax.2mfkkqc-lnsen.svg")},610503,e=>{e.q("/litellm-asset-prefix/_next/static/media/mistral.0n8jv_67hgq4h.svg")},438969,e=>{e.q("/litellm-asset-prefix/_next/static/media/moonshot.3__i9wvf37ksm.svg")},852459,e=>{e.q("/litellm-asset-prefix/_next/static/media/morph.2av06eo-t0-ve.svg")},494696,e=>{e.q("/litellm-asset-prefix/_next/static/media/nebius.2ipf7rmjccira.svg")},148973,e=>{e.q("/litellm-asset-prefix/_next/static/media/novita.0_nzmm_rl4lrf.svg")},570036,e=>{e.q("/litellm-asset-prefix/_next/static/media/nvidia_nim.1fz-5ugf_um0v.svg")},151170,e=>{e.q("/litellm-asset-prefix/_next/static/media/nvidia_triton.1aotoxig_m2w1.png")},862330,e=>{e.q("/litellm-asset-prefix/_next/static/media/ollama.144cif369atc5.svg")},956529,e=>{e.q("/litellm-asset-prefix/_next/static/media/openai_small.3rj8nedgjpevh.svg")},788158,e=>{e.q("/litellm-asset-prefix/_next/static/media/openrouter.1xk7748-_jixf.svg")},350567,e=>{e.q("/litellm-asset-prefix/_next/static/media/oracle.43kws59xxr8ig.svg")},822797,e=>{e.q("/litellm-asset-prefix/_next/static/media/perplexity-ai.2do8hoc8tw__0.svg")},840911,e=>{e.q("/litellm-asset-prefix/_next/static/media/qwen.0a49x9i08_0gz.png")},445735,e=>{e.q("/litellm-asset-prefix/_next/static/media/recraft.2f4cv-c9ad-mo.svg")},635202,e=>{e.q("/litellm-asset-prefix/_next/static/media/replicate.445bidyix2tyh.svg")},669161,e=>{e.q("/litellm-asset-prefix/_next/static/media/runway.2zzwye1fddnnn.png")},159508,e=>{e.q("/litellm-asset-prefix/_next/static/media/sambanova.1vcyu0faw1x8h.svg")},544025,e=>{e.q("/litellm-asset-prefix/_next/static/media/sap.1367hgge0s0xl.png")},100588,e=>{e.q("/litellm-asset-prefix/_next/static/media/snowflake.2_p8qqpi0r3lr.svg")},183804,e=>{e.q("/litellm-asset-prefix/_next/static/media/soniox.3nhjmssy7ybh7.svg")},865502,e=>{e.q("/litellm-asset-prefix/_next/static/media/togetherai.1wk-mouzgw5ho.svg")},96912,e=>{e.q("/litellm-asset-prefix/_next/static/media/topaz.3rjp3zvx4eags.svg")},395073,e=>{e.q("/litellm-asset-prefix/_next/static/media/v0.15trd3tb1ulop.svg")},407829,e=>{e.q("/litellm-asset-prefix/_next/static/media/vercel.1mvnwxofolt8y.svg")},846077,e=>{e.q("/litellm-asset-prefix/_next/static/media/vllm.3_gqby46r7s3x.png")},914861,e=>{e.q("/litellm-asset-prefix/_next/static/media/volcengine.23ly9ik_qc138.png")},285328,e=>{e.q("/litellm-asset-prefix/_next/static/media/voyage.0krq6ew-yr8dk.webp")},779278,e=>{e.q("/litellm-asset-prefix/_next/static/media/watsonx.19rrg39yvhpk8.svg")},325532,e=>{e.q("/litellm-asset-prefix/_next/static/media/xai.2kc3gjiopn9om.svg")},288330,e=>{e.q("/litellm-asset-prefix/_next/static/media/xinference.063cy_ievvy5u.svg")},555987,938137,301035,470524,901539,434339,857152,e=>{"use strict";var t=e.i(221688),i=e.i(950643);let a=/^(https?:|data:|blob:|\/\/)/i;e.s(["resolveLogoSrc",0,(e,l=t.serverRootPath)=>{let r;if(!e)return;if(a.test(e)||e.includes("/_next/static/"))return e;let n=(0,i.normalizeRootPath)(l);return n&&(e===n||e.startsWith(`${n}/`))?e:(r=(0,i.normalizeRootPath)(l),`${r}${e.startsWith("/")?e:`/${e}`}`)}],555987);let l={src:e.i(913383).default,width:661,height:768,blurWidth:7,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAICAYAAAA1BOUGAAAAwUlEQVR42h2OvQpGcByFf5dK7oBJFoooJDaDZFA+BoVkNBikGJSUJINyGeft/Q+nznDO00O6rsM0Tfi+z6JpGizLgqqqIEVR0HUdqqqCbdus930PWZZBy7IgCAKkaYpxHJEkCaIowjAMoGmaEMcx8jxHURRo2xZ1XaMsS9B5nrjvG+u6Yp5nfN+H53nwJ9JxHMiyDO/7Mtx1Xey1bRvIcRzwPI993xGGIZqmgSAIMAwDJEkSPM9jphzHsYHruhBFET+hn6S8pf6D7AAAAABJRU5ErkJggg=="};e.s(["default",0,l],938137);let r={src:e.i(238564).default,width:103,height:24,blurWidth:0,blurHeight:0};e.s(["default",0,r],301035);let n={src:e.i(941533).default,width:115,height:24,blurWidth:0,blurHeight:0};e.s(["default",0,n],470524);let s={src:e.i(994636).default,width:46,height:46,blurWidth:0,blurHeight:0};e.s(["default",0,s],901539);let o={src:e.i(824770).default,width:28,height:28,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAlElEQVR42m3OoQqDUBQGYF9mwzLbFiZsMNgwuGa4YFgYLC6PgcXuA5gFzdcgFkExGr33ogZRNIr6CAoKKgo/nAMfh/NTu3u4GWoY+0fIAsIC3C8LuIiRH9S2W5wBXsBPKaHTGGb+lZMJmCe2vPYjZe9/bMDswKMRbq9Yg9VRICcBq3p6FckINIf6o6ECwyOam/1YpwPFUFu+Lt2F2QAAAABJRU5ErkJggg=="};e.s(["default",0,o],434339);let d={src:e.i(920076).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,d],857152)},922158,e=>{"use strict";let t={src:e.i(519380).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,t])},896614,9774,503119,272896,144923,562171,533881,837957,227247,708889,859320,586455,921117,21296,579967,e=>{"use strict";let t={src:e.i(96889).default,width:800,height:600,blurWidth:0,blurHeight:0};e.s(["default",0,t],896614);let i={src:e.i(348026).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,i],9774);let a={src:e.i(727148).default,width:75,height:75,blurWidth:0,blurHeight:0};e.s(["default",0,a],503119);let l={src:e.i(411703).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,l],272896);let r={src:e.i(922480).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,r],144923);let n={src:e.i(769490).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,n],562171);let s={src:e.i(829789).default,width:32,height:32,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAzklEQVR42k3PPWuDcBDHccUiiikYSwqhg+IkuKUpOLqK4OYrcBZcFCcH352mgy/Ch9mxEPg2+s+Q4W65zx33kyRJQpZlFOUNTdM4HN45Hi1Op0/O56+7tAHbtqmqiqZpKIqCy+Uby/rYkABhGLKuK+M4siwLt9sv1+sPpmkKEAQBwzCQpilxHDNNE3VdYxiGALqu47oujuMQRdEOyrJEVVUBFEUhSRK6rmOeZ/q+x/f97XkBHqfIsoy2bcnzHM/z9mSP2Q62dt/0c+O1/v4BYRxu3LgaR7UAAAAASUVORK5CYII="};e.s(["default",0,s],533881);let o={src:e.i(41730).default,width:225,height:225,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAfklEQVR42nVOvQqDMBjM+7+RpbQ0Ji2NQhwdzGIwCMZFiCAa9STqIPhxw/3A3UeW41w/AKck87w0tqvbngkFgEDCJOPoKc+fovxKzaUGiXkOk0zei1TRzDxYEbHiIw0kzD2Aji5BqHolW9Uv0+9U01AVxivr4r8CjHX7+N27K5WIrP56XsFTAAAAAElFTkSuQmCC"};e.s(["default",0,o],837957);let d={src:e.i(662576).default,width:293,height:215,blurWidth:0,blurHeight:0};e.s(["default",0,d],227247);let A={src:e.i(987202).default,width:1563,height:1563,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA5ElEQVR42h2PPwtBURjGj3wCk93sC1iUCYXFIlGSMjC4QiiTBSE3Ge5M3e0uPoJFBhOTP8NNukXduFL3uJ1Xzz11znCe93me38uitQSL11Pe8rAanKpycbSa5Ar9UiAmJT3QXHGwHGfvj/vJcRzOfz/7fLvsW4tuJFZLehicEIUQpOs6GYZBOIfrcZPp5f0MsXDatk2yLJOqqu6A9f2YjXk7zNCJWM45KYpCmqa5A6/P+ynNGiEGIHSiwjRNsiyLBAmxPezW6U7Gx0ALIHQiFk6I5WEl6G6BB7QAQqc0a4bgxD/uH4FYoVec8m3dAAAAAElFTkSuQmCC"};e.s(["default",0,A],708889);let c={src:e.i(953265).default,width:400,height:400,blurWidth:8,blurHeight:8,blurDataURL:"data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/wAARCAAIAAgDAREAAhEBAxEB/9sAQwAKBwcIBwYKCAgICwoKCw4YEA4NDQ4dFRYRGCMfJSQiHyIhJis3LyYpNCkhIjBBMTQ5Oz4+PiUuRElDPEg3PT47/9sAQwEKCwsODQ4cEBAcOygiKDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDtP9N/tL+PO/8A7ZeV/wDFUj6L917L+r3/AMj/2Q=="};e.s(["default",0,c],859320);let u={src:e.i(346512).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,u],586455);let g={src:e.i(84416).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,g],921117);let h={src:e.i(104602).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,h],21296);let m={src:e.i(888193).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,m],579967)},336712,e=>{"use strict";let t={src:e.i(946013).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,t])},770752,383963,862493,902860,901372,206258,176228,728685,e=>{"use strict";let t={src:e.i(447500).default,width:26,height:26,blurWidth:0,blurHeight:0};e.s(["default",0,t],770752);let i={src:e.i(270039).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,i],383963);let a={src:e.i(227733).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,a],862493);let l={src:e.i(823929).default,width:128,height:129,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAApklEQVR42oWPsQrCMBRFY51KaUuwNktBQbcgSenQ32hCTBAHXfSfulUK4g/0/7yvdO+FB5dzXhLC2FoiJE3TfZ7nYrskyzIBVpBjTdPcu677GGPGtm1fmDd1YnVd31gIYSrLUnLOjxC9tfaLfhBCXLz307xQVZUCODvnfljoceAEpskxKeUV1w0Qo1LqobV+Lk8McI5tkDiOeZIkO9SIBr0gRm71l38QASaqNz7VMwAAAABJRU5ErkJggg=="};e.s(["default",0,l],902860);let r={src:e.i(333845).default,width:200,height:200,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAXUlEQVR42p2MoRHAMAwDQwJzCcgCwQEZIDjA03gED2BzewSPWRe2qFcB3Z+kU0o/lXNea4U/0t77OYeIACDq4FLKnSKimYmIqjKzu885U6117z3GCGitxTbg/flJF7DAEmcR3WAqAAAAAElFTkSuQmCC"};e.s(["default",0,r],901372);let n={src:e.i(813878).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,n],206258);let s={src:e.i(767788).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,s],176228);let o={src:e.i(808630).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,o],728685)},39182,e=>{"use strict";let t={src:e.i(641266).default,width:200,height:58,blurWidth:0,blurHeight:0};e.s(["default",0,t])},272967,551726,399495,740876,709103,277207,836473,768493,297720,e=>{"use strict";let t={src:e.i(468732).default,width:490,height:412,blurWidth:0,blurHeight:0};e.s(["default",0,t],272967);let i={src:e.i(610503).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,i],551726);let a={src:e.i(438969).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,a],399495);let l={src:e.i(852459).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,l],740876);let r={src:e.i(494696).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,r],709103);let n={src:e.i(148973).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,n],277207);let s={src:e.i(570036).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,s],836473);let o={src:e.i(151170).default,width:95,height:81,blurWidth:8,blurHeight:7,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAHCAYAAAA1WQxeAAAA0UlEQVR42kXMvWrCUBjG8VxIL6KX0IvobXQSOrQd2kLSdMzQdMmaEErPUqoIIYtKOC5iFD9BEBwiaAY/AgpH/2oUfOGFh4cfj8blNmrNKK0zmFUZzmust2nea7u9IlkNkBMPv1Xgs3JHoXRDe1o+g8Vmyl/vnf++wU/7EdF54jW8JU6K14WTPgE3fjiCZ77kPa2kdAZZlrFcLpikXYLeN6Lxxm/zhfEsRimF5roupmli2zbGh4Gu61iWlX8URWhSSsIwxPM8hBA4jpNn3/cJgoADqC23HJzwo6wAAAAASUVORK5CYII="};e.s(["default",0,o],768493);let d={src:e.i(862330).default,width:646,height:854,blurWidth:0,blurHeight:0};e.s(["default",0,d],297720)},980385,e=>{"use strict";let t={src:e.i(956529).default,width:28,height:28,blurWidth:0,blurHeight:0};e.s(["default",0,t])},916925,247044,e=>{"use strict";var t,i=e.i(555987),a=e.i(938137),l=e.i(301035),r=e.i(470524),n=e.i(901539),s=e.i(434339),o=e.i(857152),d=e.i(922158),A=e.i(896614),c=e.i(9774),u=e.i(503119),g=e.i(272896),h=e.i(144923),m=e.i(562171),b=e.i(533881),f=e.i(837957),p=e.i(227247),O=e.i(708889),x=e.i(859320),E=e.i(586455),I=e.i(921117),v=e.i(21296),y=e.i(579967),C=e.i(336712),w=e.i(770752),S=e.i(383963),R=e.i(862493),L=e.i(902860),_=e.i(901372),B=e.i(206258),T=e.i(176228),k=e.i(728685),$=e.i(39182),H=e.i(272967),M=e.i(551726),j=e.i(399495),N=e.i(740876),D=e.i(709103),U=e.i(277207),z=e.i(836473),P=e.i(768493),W=e.i(297720),G=e.i(980385);let q={src:e.i(788158).default,width:300,height:300,blurWidth:0,blurHeight:0},Q={src:e.i(350567).default,width:231,height:30,blurWidth:0,blurHeight:0},F={src:e.i(822797).default,width:48,height:48,blurWidth:0,blurHeight:0},V={src:e.i(840911).default,width:1024,height:1024,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAABE0lEQVR42gEIAff+AAAAAAAAAAAAEQwmIT8ukIAgGUpBBwYQDQQECggAAAAAAAIBBQQJBhUTFw80LUIxloZbR8+5UUS3oUA4kH0HBxEOACMUUkolFlVNCAYTERwUPzgkHFJIIhxNRCsmYVQODSAbADYffnNFKaCSDgkgHD0tjX4+MI5+CQgVEhQSLCULChgUABAKJiJTM7+vJhlYTzEkb2MxJm9iIh1NQywnZFYhH0o/AA8JIh9RMrurNiR9cQkHFhMQDSUgUkW5o1hOx60pJlxPAAcEDw4qGmJYFw80LhENKCMyKHFjUEK0nhEOJSADAwYFAAAAAAADAgcGBQMLCRUPMClCNJeEIRtLQgAAAQAAAAAAfjQxsiCQipMAAAAASUVORK5CYII="},K={src:e.i(445735).default,width:16,height:16,blurWidth:0,blurHeight:0},Y={src:e.i(635202).default,width:16,height:16,blurWidth:0,blurHeight:0},J={src:e.i(669161).default,width:320,height:320,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA7UlEQVR42lVQvc7BYBR+B/kM3w+fFm2p9m3fRhFRP7UQlTAQCVfAgE3EHZAQMTBIzBIrYbEgFhaza3ABrsF7bIYnOTl5/s5BVrvIY5MMvYTbUuy8hN8CcFIdW62CgHCKjFwie/So3PqH+b0Hc+GebkW6Lj9zUtJaHwEbFvGqWf7zOG5GJVmjcwUEAUOevQmhfKSTqJol+7f94RT+L7Yv25OV3PtsI699EByc85qpW5qcUCaMyB7orH9EgJLaTnPNgk/QfUsxKi0QtKWFzjRzA7a08IEPCivYKWkyQHAKtJUMPIVMQCAmz3FKHcELXnxEPy9E1lYCAAAAAElFTkSuQmCC"},X={src:e.i(159508).default,width:52,height:46,blurWidth:0,blurHeight:0},Z={src:e.i(544025).default,width:3300,height:2550,blurWidth:8,blurHeight:6,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAYAAAD+Bd/7AAAAZklEQVR42oWNTQ5AMBhE3f8mEhux4BgiIRzAwoKWplrt146fBbVhkpnNvORF+El0jvMAV4RR2qtsJZD3DyA2h6xhiMsRSTUhrRm0DYBFE4qOI285yl5CGvdW0OEYhEE/G6jgvIGv7A4KupBwIVaNAAAAAElFTkSuQmCC"},ee={src:e.i(100588).default,width:146,height:139,blurWidth:0,blurHeight:0};e.s(["default",0,ee],247044);let et={src:e.i(183804).default,width:100,height:18,blurWidth:0,blurHeight:0},ei={src:e.i(865502).default,width:32,height:32,blurWidth:0,blurHeight:0},ea={src:e.i(96912).default,width:16,height:16,blurWidth:0,blurHeight:0},el={src:e.i(395073).default,width:16,height:16,blurWidth:0,blurHeight:0},er={src:e.i(407829).default,width:16,height:16,blurWidth:0,blurHeight:0},en={src:e.i(846077).default,width:64,height:64,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAo0lEQVR42lWOvQrCQBCE7718oUA6wVcQBF/AXhu1CIgcpgoi/qCNguBPwCaChYHDv9zt3e6ZeEFxq9n5GGaY/T8iq3QhWKFRkZGWzEPaaE/RDoiI5e7z0BILP7tMB2vyO3p2BJew8syvvJLGQTPEaheSVJeAQIi5F2+CWt80uHopLMEnNBpPuNc2vaXK+38A4RauTvUhbBNwDvsOlYD3DA2Se99kLK7hC5QFVQAAAABJRU5ErkJggg=="},es={src:e.i(914861).default,width:1024,height:1024,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA7ElEQVR42l2LMUsCAQBG39GFFQ02ad1oq0QIRR2hER4EGdQqdBDdQZAFEudU0NZSQ0O1VUOjiuJvEBxc3BQFEVQQdFAU9OROFwf9lg/e48Hiti8vWJekBSoIODacLK04CP79IAX8815cW2X3IYL3JIhaKuKLGdNGmMUCTo+Hq1yO72yB6HDEeTrFjm8fUVwGt3uL00eD5+6AfMvixbS5bTR5/43jcm2CooT4KNf56ltUGzafA5uoZZGpdJDlABhPr7z1TJLtMbXamMT0702T//aQu0gM5GOFvRuNkKoTDuucqRpeXePoWuPg0D8BGkpJjYhKXN4AAAAASUVORK5CYII="},eo={src:e.i(285328).default,width:400,height:399,blurWidth:8,blurHeight:8,blurDataURL:"data:image/webp;base64,UklGRgwBAABXRUJQVlA4TAABAAAvB8ABEM1VICICHggIDgIAAIDzDAACD0FoAAARAAwAAAAAAAAAAAAAAACAAABAAcBCAoAWgGyoXwUAAA8E7QQCAADg/N+nrsYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4CClZXkg5AQAAAAA538LAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL0HIgEAAAAAcP4ZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMsHA39Qutr2AtkROu8CASWZ/nMlNyzz5AdgWmkK65oUI6AlgIkY/M3zxIW3iABq15KT/Uz7BXidCOONHieAz1v6HqAC"},ed={src:e.i(779278).default,width:16,height:16,blurWidth:0,blurHeight:0},eA={src:e.i(325532).default,width:1e3,height:1e3,blurWidth:0,blurHeight:0},ec={src:e.i(288330).default,width:16,height:16,blurWidth:0,blurHeight:0};var eu=((t={}).A2A_Agent="A2A Agent",t.AI21="Ai21",t.AI21_CHAT="Ai21 Chat",t.AIML="AI/ML API",t.AIOHTTP_OPENAI="Aiohttp Openai",t.Anthropic="Anthropic",t.ANTHROPIC_TEXT="Anthropic Text",t.AssemblyAI="AssemblyAI",t.AUTO_ROUTER="Auto Router",t.Bedrock="Amazon Bedrock",t.BedrockMantle="Amazon Bedrock Mantle",t.SageMaker="AWS SageMaker",t.Azure="Azure",t.Azure_AI_Studio="Azure AI Foundry (Studio)",t.AZURE_TEXT="Azure Text",t.BASETEN="Baseten",t.BYTEZ="Bytez",t.Cerebras="Cerebras",t.CLARIFAI="Clarifai",t.CLOUDFLARE="Cloudflare",t.CODESTRAL="Codestral",t.Cohere="Cohere",t.COHERE_CHAT="Cohere Chat",t.COMETAPI="Cometapi",t.COMPACTIFAI="Compactifai",t.Cursor="Cursor",t.Dashscope="Dashscope",t.Databricks="Databricks (Qwen API)",t.DATAROBOT="Datarobot",t.DeepInfra="DeepInfra",t.Deepgram="Deepgram",t.Deepseek="Deepseek",t.DOCKER_MODEL_RUNNER="Docker Model Runner",t.DOTPROMPT="Dotprompt",t.ElevenLabs="ElevenLabs",t.EMPOWER="Empower",t.FalAI="Fal AI",t.FEATHERLESS_AI="Featherless Ai",t.FireworksAI="Fireworks AI",t.FRIENDLIAI="Friendliai",t.GALADRIEL="Galadriel",t.GITHUB_COPILOT="Github Copilot",t.Google_AI_Studio="Google AI Studio",t.GradientAI="GradientAI",t.Groq="Groq",t.HEROKU="Heroku",t.Hosted_Vllm="vllm",t.HUGGINGFACE="Huggingface",t.HYPERBOLIC="Hyperbolic",t.Infinity="Infinity",t.JinaAI="Jina AI",t.LAMBDA_AI="Lambda Ai",t.LEMONADE="Lemonade",t.LLAMAFILE="Llamafile",t.LM_STUDIO="Lm Studio",t.LLAMA="Meta Llama",t.MARITALK="Maritalk",t.MiniMax="MiniMax",t.MistralAI="Mistral AI",t.MOONSHOT="Moonshot",t.MORPH="Morph",t.NEBIUS="Nebius",t.NLP_CLOUD="Nlp Cloud",t.NOVITA="Novita",t.NSCALE="Nscale",t.NVIDIA_NIM="Nvidia Nim",t.Ollama="Ollama",t.OLLAMA_CHAT="Ollama Chat",t.OOBABOOGA="Oobabooga",t.OpenAI="OpenAI",t.OPENAI_LIKE="Openai Like",t.OpenAI_Compatible="OpenAI-Compatible Chat Completions (Together AI, vLLM, etc.)",t.OpenAI_Text="OpenAI Text Completion",t.OpenAI_Text_Compatible="OpenAI-Compatible Completions (legacy /v1/completions)",t.Openrouter="Openrouter",t.Oracle="Oracle Cloud Infrastructure (OCI)",t.OVHCLOUD="Ovhcloud",t.Perplexity="Perplexity",t.PETALS="Petals",t.PG_VECTOR="Pg Vector",t.PREDIBASE="Predibase",t.RECRAFT="Recraft",t.REPLICATE="Replicate",t.RunwayML="RunwayML",t.SAGEMAKER_LEGACY="Sagemaker",t.Sambanova="Sambanova",t.SAP="SAP Generative AI Hub",t.Snowflake="Snowflake",t.Soniox="Soniox",t.TEXT_COMPLETION_CODESTRAL="Text-Completion-Codestral",t.TogetherAI="TogetherAI",t.TOPAZ="Topaz",t.Triton="Triton",t.V0="V0",t.VERCEL_AI_GATEWAY="Vercel Ai Gateway",t.Vertex_AI="Vertex AI (Anthropic, Gemini, etc.)",t.VERTEX_AI_BETA="Vertex Ai Beta",t.VLLM="Vllm",t.VolcEngine="VolcEngine",t.Voyage="Voyage AI",t.WANDB="Wandb",t.WATSONX="Watsonx",t.WATSONX_TEXT="Watsonx Text",t.xAI="xAI",t.XINFERENCE="Xinference",t.ZAI="Z.AI (Zhipu AI)",t);let eg={A2A_Agent:"a2a_agent",AI21:"ai21",AI21_CHAT:"ai21_chat",AIML:"aiml",AIOHTTP_OPENAI:"aiohttp_openai",Anthropic:"anthropic",ANTHROPIC_TEXT:"anthropic_text",AssemblyAI:"assemblyai",AUTO_ROUTER:"auto_router",Azure:"azure",Azure_AI_Studio:"azure_ai",AZURE_TEXT:"azure_text",BASETEN:"baseten",Bedrock:"bedrock",BedrockMantle:"bedrock_mantle",BYTEZ:"bytez",Cerebras:"cerebras",CLARIFAI:"clarifai",CLOUDFLARE:"cloudflare",CODESTRAL:"codestral",Cohere:"cohere",COHERE_CHAT:"cohere_chat",COMETAPI:"cometapi",COMPACTIFAI:"compactifai",Cursor:"cursor",Dashscope:"dashscope",Databricks:"databricks",DATAROBOT:"datarobot",DeepInfra:"deepinfra",Deepgram:"deepgram",Deepseek:"deepseek",DOCKER_MODEL_RUNNER:"docker_model_runner",DOTPROMPT:"dotprompt",ElevenLabs:"elevenlabs",EMPOWER:"empower",FalAI:"fal_ai",FEATHERLESS_AI:"featherless_ai",FireworksAI:"fireworks_ai",FRIENDLIAI:"friendliai",GALADRIEL:"galadriel",GITHUB_COPILOT:"github_copilot",Google_AI_Studio:"gemini",GradientAI:"gradient_ai",Groq:"groq",HEROKU:"heroku",Hosted_Vllm:"hosted_vllm",HUGGINGFACE:"huggingface",HYPERBOLIC:"hyperbolic",Infinity:"infinity",JinaAI:"jina_ai",LAMBDA_AI:"lambda_ai",LEMONADE:"lemonade",LLAMAFILE:"llamafile",LLAMA:"meta_llama",LM_STUDIO:"lm_studio",MARITALK:"maritalk",MiniMax:"minimax",MistralAI:"mistral",MOONSHOT:"moonshot",MORPH:"morph",NEBIUS:"nebius",NLP_CLOUD:"nlp_cloud",NOVITA:"novita",NSCALE:"nscale",NVIDIA_NIM:"nvidia_nim",Ollama:"ollama",OLLAMA_CHAT:"ollama_chat",OOBABOOGA:"oobabooga",OpenAI:"openai",OPENAI_LIKE:"openai_like",OpenAI_Compatible:"openai",OpenAI_Text:"text-completion-openai",OpenAI_Text_Compatible:"text-completion-openai",Openrouter:"openrouter",Oracle:"oci",OVHCLOUD:"ovhcloud",Perplexity:"perplexity",PETALS:"petals",PG_VECTOR:"pg_vector",PREDIBASE:"predibase",RECRAFT:"recraft",REPLICATE:"replicate",RunwayML:"runwayml",SAGEMAKER_LEGACY:"sagemaker",SageMaker:"sagemaker_chat",Sambanova:"sambanova",SAP:"sap",Snowflake:"snowflake",Soniox:"soniox",TEXT_COMPLETION_CODESTRAL:"text-completion-codestral",TogetherAI:"together_ai",TOPAZ:"topaz",Triton:"triton",V0:"v0",VERCEL_AI_GATEWAY:"vercel_ai_gateway",Vertex_AI:"vertex_ai",VERTEX_AI_BETA:"vertex_ai_beta",VLLM:"vllm",VolcEngine:"volcengine",Voyage:"voyage",WANDB:"wandb",WATSONX:"watsonx",WATSONX_TEXT:"watsonx_text",xAI:"xai",XINFERENCE:"xinference",ZAI:"zai"},eh=new Set(["bedrock_mantle"]),em={"A2A Agent":a.default.src,Ai21:l.default.src,"Ai21 Chat":l.default.src,"AI/ML API":r.default.src,"Aiohttp Openai":G.default.src,Anthropic:n.default.src,"Anthropic Text":n.default.src,AssemblyAI:s.default.src,Azure:$.default.src,"Azure AI Foundry (Studio)":$.default.src,"Azure Text":$.default.src,Baseten:o.default.src,"Amazon Bedrock":d.default.src,"Amazon Bedrock Mantle":d.default.src,"AWS SageMaker":d.default.src,Cerebras:A.default.src,Cloudflare:c.default.src,Codestral:M.default.src,Cohere:u.default.src,"Cohere Chat":u.default.src,Cometapi:g.default.src,Cursor:h.default.src,"Databricks (Qwen API)":m.default.src,Dashscope:V.src,Deepseek:p.default.src,Deepgram:b.default.src,DeepInfra:f.default.src,ElevenLabs:O.default.src,"Fal AI":x.default.src,"Featherless Ai":E.default.src,"Fireworks AI":I.default.src,Friendliai:v.default.src,"Github Copilot":y.default.src,"Google AI Studio":C.default.src,Groq:w.default.src,vllm:en.src,Huggingface:S.default.src,Hyperbolic:R.default.src,Infinity:L.default.src,"Jina AI":_.default.src,"Lambda Ai":B.default.src,"Lm Studio":T.default.src,"Meta Llama":k.default.src,MiniMax:H.default.src,"Mistral AI":M.default.src,Moonshot:j.default.src,Morph:N.default.src,Nebius:D.default.src,Novita:U.default.src,"Nvidia Nim":z.default.src,Ollama:W.default.src,"Ollama Chat":W.default.src,Oobabooga:G.default.src,OpenAI:G.default.src,"Openai Like":G.default.src,"OpenAI Text Completion":G.default.src,"OpenAI-Compatible Completions (legacy /v1/completions)":G.default.src,"OpenAI-Compatible Chat Completions (Together AI, vLLM, etc.)":G.default.src,Openrouter:q.src,"Oracle Cloud Infrastructure (OCI)":Q.src,Perplexity:F.src,Recraft:K.src,Replicate:Y.src,RunwayML:J.src,Sagemaker:d.default.src,Sambanova:X.src,"SAP Generative AI Hub":Z.src,Snowflake:ee.src,Soniox:et.src,"Text-Completion-Codestral":M.default.src,TogetherAI:ei.src,Topaz:ea.src,Triton:P.default.src,V0:el.src,"Vercel Ai Gateway":er.src,"Vertex AI (Anthropic, Gemini, etc.)":C.default.src,"Vertex Ai Beta":C.default.src,Vllm:en.src,VolcEngine:es.src,"Voyage AI":eo.src,Watsonx:ed.src,"Watsonx Text":ed.src,xAI:eA.src,Xinference:ec.src};e.s(["Providers",()=>eu,"getPlaceholder",0,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)return"claude-3-opus";if("Amazon Bedrock"==e)return"claude-3-opus";if("AWS SageMaker"==e)return"sagemaker/jumpstart-dft-meta-textgeneration-llama-2-7b";else if("Google AI Studio"==e)return"gemini-pro";else if("Azure AI Foundry (Studio)"==e)return"azure_ai/command-r-plus";else if("Azure"==e)return"my-deployment";else if("Oracle Cloud Infrastructure (OCI)"==e)return"oci/xai.grok-4";else if("Snowflake"==e)return"snowflake/mistral-7b";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 if("Fal AI"==e)return"fal_ai/fal-ai/flux-pro/v1.1-ultra";else if("RunwayML"==e)return"runwayml/gen4_turbo";else if("Watsonx"===e)return"watsonx/ibm/granite-3-3-8b-instruct";else if("Cursor"===e)return"cursor/claude-4-sonnet";else if("Z.AI (Zhipu AI)"===e)return"zai/glm-4.5";else return"gpt-3.5-turbo"},"getProviderLogoAndName",0,e=>{if(!e)return{logo:"",displayName:"-"};if("gemini"===e.toLowerCase()){let e="Google AI Studio";return{logo:(0,i.resolveLogoSrc)(em[e])??"",displayName:e}}let t=Object.keys(eg).find(t=>eg[t].toLowerCase()===e.toLowerCase())??Object.keys(eg).find(t=>t.toLowerCase()===e.toLowerCase());if(!t)return{logo:"",displayName:e};let a=eu[t];return{logo:(0,i.resolveLogoSrc)(em[a])??"",displayName:a}},"getProviderModels",0,(e,t)=>{let i=eg[e],a=[];return e&&"object"==typeof t&&(Object.entries(t).forEach(([e,t])=>{if(null!==t&&"object"==typeof t&&"litellm_provider"in t){let l=t.litellm_provider,r="string"==typeof l&&(l.startsWith(`${i}_`)||l.startsWith(`${i}-`));(l===i||r&&!eh.has(l))&&a.push(e)}}),"Cohere"==e&&Object.entries(t).forEach(([e,t])=>{null!==t&&"object"==typeof t&&"litellm_provider"in t&&"cohere_chat"===t.litellm_provider&&a.push(e)}),"AWS SageMaker"==e&&Object.entries(t).forEach(([e,t])=>{null!==t&&"object"==typeof t&&"litellm_provider"in t&&"sagemaker_chat"===t.litellm_provider&&a.push(e)})),a},"providerLogoMap",0,em,"provider_map",0,eg],916925)},653496,e=>{"use strict";var t=e.i(721369);e.s(["Tabs",()=>t.default])}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/025ocjcb8e481.js b/litellm/proxy/_experimental/out/_next/static/chunks/025ocjcb8e481.js deleted file mode 100644 index d45da443d18..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/025ocjcb8e481.js +++ /dev/null @@ -1,7 +0,0 @@ -(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,244451,e=>{"use strict";let t;e.i(247167);var r=e.i(271645),a=e.i(343794),i=e.i(242064),o=e.i(763731),l=e.i(174428);let n=80*Math.PI,s=e=>{let{dotClassName:t,style:i,hasCircleCls:o}=e;return r.createElement("circle",{className:(0,a.default)(`${t}-circle`,{[`${t}-circle-bg`]:o}),r:40,cx:50,cy:50,strokeWidth:20,style:i})},c=({percent:e,prefixCls:t})=>{let i=`${t}-dot`,o=`${i}-holder`,c=`${o}-hidden`,[d,u]=r.useState(!1);(0,l.default)(()=>{0!==e&&u(!0)},[0!==e]);let f=Math.max(Math.min(e,100),0);if(!d)return null;let m={strokeDashoffset:`${n/4}`,strokeDasharray:`${n*f/100} ${n*(100-f)/100}`};return r.createElement("span",{className:(0,a.default)(o,`${i}-progress`,f<=0&&c)},r.createElement("svg",{viewBox:"0 0 100 100",role:"progressbar","aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":f},r.createElement(s,{dotClassName:i,hasCircleCls:!0}),r.createElement(s,{dotClassName:i,style:m})))};function d(e){let{prefixCls:t,percent:i=0}=e,o=`${t}-dot`,l=`${o}-holder`,n=`${l}-hidden`;return r.createElement(r.Fragment,null,r.createElement("span",{className:(0,a.default)(l,i>0&&n)},r.createElement("span",{className:(0,a.default)(o,`${t}-dot-spin`)},[1,2,3,4].map(e=>r.createElement("i",{className:`${t}-dot-item`,key:e})))),r.createElement(c,{prefixCls:t,percent:i}))}function u(e){var t;let{prefixCls:i,indicator:l,percent:n}=e,s=`${i}-dot`;return l&&r.isValidElement(l)?(0,o.cloneElement)(l,{className:(0,a.default)(null==(t=l.props)?void 0:t.className,s),percent:n}):r.createElement(d,{prefixCls:i,percent:n})}e.i(296059);var f=e.i(694758),m=e.i(183293),p=e.i(246422),v=e.i(838378);let h=new f.Keyframes("antSpinMove",{to:{opacity:1}}),g=new f.Keyframes("antRotate",{to:{transform:"rotate(405deg)"}}),b=(0,p.genStyleHooks)("Spin",e=>(e=>{let{componentCls:t,calc:r}=e;return{[t]:Object.assign(Object.assign({},(0,m.resetComponent)(e)),{position:"absolute",display:"none",color:e.colorPrimary,fontSize:0,textAlign:"center",verticalAlign:"middle",opacity:0,transition:`transform ${e.motionDurationSlow} ${e.motionEaseInOutCirc}`,"&-spinning":{position:"relative",display:"inline-block",opacity:1},[`${t}-text`]:{fontSize:e.fontSize,paddingTop:r(r(e.dotSize).sub(e.fontSize)).div(2).add(2).equal()},"&-fullscreen":{position:"fixed",width:"100vw",height:"100vh",backgroundColor:e.colorBgMask,zIndex:e.zIndexPopupBase,inset:0,display:"flex",alignItems:"center",flexDirection:"column",justifyContent:"center",opacity:0,visibility:"hidden",transition:`all ${e.motionDurationMid}`,"&-show":{opacity:1,visibility:"visible"},[t]:{[`${t}-dot-holder`]:{color:e.colorWhite},[`${t}-text`]:{color:e.colorTextLightSolid}}},"&-nested-loading":{position:"relative",[`> div > ${t}`]:{position:"absolute",top:0,insetInlineStart:0,zIndex:4,display:"block",width:"100%",height:"100%",maxHeight:e.contentHeight,[`${t}-dot`]:{position:"absolute",top:"50%",insetInlineStart:"50%",margin:r(e.dotSize).mul(-1).div(2).equal()},[`${t}-text`]:{position:"absolute",top:"50%",width:"100%",textShadow:`0 1px 2px ${e.colorBgContainer}`},[`&${t}-show-text ${t}-dot`]:{marginTop:r(e.dotSize).div(2).mul(-1).sub(10).equal()},"&-sm":{[`${t}-dot`]:{margin:r(e.dotSizeSM).mul(-1).div(2).equal()},[`${t}-text`]:{paddingTop:r(r(e.dotSizeSM).sub(e.fontSize)).div(2).add(2).equal()},[`&${t}-show-text ${t}-dot`]:{marginTop:r(e.dotSizeSM).div(2).mul(-1).sub(10).equal()}},"&-lg":{[`${t}-dot`]:{margin:r(e.dotSizeLG).mul(-1).div(2).equal()},[`${t}-text`]:{paddingTop:r(r(e.dotSizeLG).sub(e.fontSize)).div(2).add(2).equal()},[`&${t}-show-text ${t}-dot`]:{marginTop:r(e.dotSizeLG).div(2).mul(-1).sub(10).equal()}}},[`${t}-container`]:{position:"relative",transition:`opacity ${e.motionDurationSlow}`,"&::after":{position:"absolute",top:0,insetInlineEnd:0,bottom:0,insetInlineStart:0,zIndex:10,width:"100%",height:"100%",background:e.colorBgContainer,opacity:0,transition:`all ${e.motionDurationSlow}`,content:'""',pointerEvents:"none"}},[`${t}-blur`]:{clear:"both",opacity:.5,userSelect:"none",pointerEvents:"none","&::after":{opacity:.4,pointerEvents:"auto"}}},"&-tip":{color:e.spinDotDefault},[`${t}-dot-holder`]:{width:"1em",height:"1em",fontSize:e.dotSize,display:"inline-block",transition:`transform ${e.motionDurationSlow} ease, opacity ${e.motionDurationSlow} ease`,transformOrigin:"50% 50%",lineHeight:1,color:e.colorPrimary,"&-hidden":{transform:"scale(0.3)",opacity:0}},[`${t}-dot-progress`]:{position:"absolute",inset:0},[`${t}-dot`]:{position:"relative",display:"inline-block",fontSize:e.dotSize,width:"1em",height:"1em","&-item":{position:"absolute",display:"block",width:r(e.dotSize).sub(r(e.marginXXS).div(2)).div(2).equal(),height:r(e.dotSize).sub(r(e.marginXXS).div(2)).div(2).equal(),background:"currentColor",borderRadius:"100%",transform:"scale(0.75)",transformOrigin:"50% 50%",opacity:.3,animationName:h,animationDuration:"1s",animationIterationCount:"infinite",animationTimingFunction:"linear",animationDirection:"alternate","&:nth-child(1)":{top:0,insetInlineStart:0,animationDelay:"0s"},"&:nth-child(2)":{top:0,insetInlineEnd:0,animationDelay:"0.4s"},"&:nth-child(3)":{insetInlineEnd:0,bottom:0,animationDelay:"0.8s"},"&:nth-child(4)":{bottom:0,insetInlineStart:0,animationDelay:"1.2s"}},"&-spin":{transform:"rotate(45deg)",animationName:g,animationDuration:"1.2s",animationIterationCount:"infinite",animationTimingFunction:"linear"},"&-circle":{strokeLinecap:"round",transition:["stroke-dashoffset","stroke-dasharray","stroke","stroke-width","opacity"].map(t=>`${t} ${e.motionDurationSlow} ease`).join(","),fillOpacity:0,stroke:"currentcolor"},"&-circle-bg":{stroke:e.colorFillSecondary}},[`&-sm ${t}-dot`]:{"&, &-holder":{fontSize:e.dotSizeSM}},[`&-sm ${t}-dot-holder`]:{i:{width:r(r(e.dotSizeSM).sub(r(e.marginXXS).div(2))).div(2).equal(),height:r(r(e.dotSizeSM).sub(r(e.marginXXS).div(2))).div(2).equal()}},[`&-lg ${t}-dot`]:{"&, &-holder":{fontSize:e.dotSizeLG}},[`&-lg ${t}-dot-holder`]:{i:{width:r(r(e.dotSizeLG).sub(e.marginXXS)).div(2).equal(),height:r(r(e.dotSizeLG).sub(e.marginXXS)).div(2).equal()}},[`&${t}-show-text ${t}-text`]:{display:"block"}})}})((0,v.mergeToken)(e,{spinDotDefault:e.colorTextDescription})),e=>{let{controlHeightLG:t,controlHeight:r}=e;return{contentHeight:400,dotSize:t/2,dotSizeSM:.35*t,dotSizeLG:r}}),y=[[30,.05],[70,.03],[96,.01]];var $=function(e,t){var r={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(r[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var i=0,a=Object.getOwnPropertySymbols(e);it.indexOf(a[i])&&Object.prototype.propertyIsEnumerable.call(e,a[i])&&(r[a[i]]=e[a[i]]);return r};let S=e=>{var o;let{prefixCls:l,spinning:n=!0,delay:s=0,className:c,rootClassName:d,size:f="default",tip:m,wrapperClassName:p,style:v,children:h,fullscreen:g=!1,indicator:S,percent:C}=e,x=$(e,["prefixCls","spinning","delay","className","rootClassName","size","tip","wrapperClassName","style","children","fullscreen","indicator","percent"]),{getPrefixCls:w,direction:k,className:E,style:z,indicator:O}=(0,i.useComponentConfig)("spin"),N=w("spin",l),[M,D,j]=b(N),[P,I]=r.useState(()=>n&&(!n||!s||!!Number.isNaN(Number(s)))),T=function(e,t){let[a,i]=r.useState(0),o=r.useRef(null),l="auto"===t;return r.useEffect(()=>(l&&e&&(i(0),o.current=setInterval(()=>{i(e=>{let t=100-e;for(let r=0;r{o.current&&(clearInterval(o.current),o.current=null)}),[l,e]),l?a:t}(P,C);r.useEffect(()=>{if(n){let e=function(e,t,r){var a,i=r||{},o=i.noTrailing,l=void 0!==o&&o,n=i.noLeading,s=void 0!==n&&n,c=i.debounceMode,d=void 0===c?void 0:c,u=!1,f=0;function m(){a&&clearTimeout(a)}function p(){for(var r=arguments.length,i=Array(r),o=0;oe?s?(f=Date.now(),l||(a=setTimeout(d?v:p,e))):p():!0!==l&&(a=setTimeout(d?v:p,void 0===d?e-c:e)))}return p.cancel=function(e){var t=(e||{}).upcomingOnly;m(),u=!(void 0!==t&&t)},p}(s,()=>{I(!0)},{debounceMode:false});return e(),()=>{var t;null==(t=null==e?void 0:e.cancel)||t.call(e)}}I(!1)},[s,n]);let _=r.useMemo(()=>void 0!==h&&!g,[h,g]),H=(0,a.default)(N,E,{[`${N}-sm`]:"small"===f,[`${N}-lg`]:"large"===f,[`${N}-spinning`]:P,[`${N}-show-text`]:!!m,[`${N}-rtl`]:"rtl"===k},c,!g&&d,D,j),R=(0,a.default)(`${N}-container`,{[`${N}-blur`]:P}),B=null!=(o=null!=S?S:O)?o:t,L=Object.assign(Object.assign({},z),v),q=r.createElement("div",Object.assign({},x,{style:L,className:H,"aria-live":"polite","aria-busy":P}),r.createElement(u,{prefixCls:N,indicator:B,percent:T}),m&&(_||g)?r.createElement("div",{className:`${N}-text`},m):null);return M(_?r.createElement("div",Object.assign({},x,{className:(0,a.default)(`${N}-nested-loading`,p,D,j)}),P&&r.createElement("div",{key:"loading"},q),r.createElement("div",{className:R,key:"container"},h)):g?r.createElement("div",{className:(0,a.default)(`${N}-fullscreen`,{[`${N}-fullscreen-show`]:P},d,D,j)},q):q)};S.setDefaultIndicator=e=>{t=e},e.s(["default",0,S],244451)},482725,e=>{"use strict";var t=e.i(244451);e.s(["Spin",()=>t.default])},184163,e=>{"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={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"};var i=e.i(9583),o=r.forwardRef(function(e,o){return r.createElement(i.default,(0,t.default)({},e,{ref:o,icon:a}))});e.s(["default",0,o],184163)},737434,e=>{"use strict";var t=e.i(184163);e.s(["DownloadOutlined",()=>t.default])},107233,603908,e=>{"use strict";let t=(0,e.i(475254).default)("plus",[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]]);e.s(["default",0,t],603908),e.s(["Plus",0,t],107233)},695411,e=>{"use strict";var t=e.i(602869);let r=async e=>{try{let r=await (0,t.modelHubCall)(e);if(r?.data.length>0){let e=r.data.map(e=>({model_group:e.model_group,mode:e?.mode}));return e.sort((e,t)=>e.model_group.localeCompare(t.model_group)),e}return[]}catch(e){throw console.error("Error fetching model info:",e),e}};e.s(["fetchAvailableModels",0,r])},343488,e=>{"use strict";var t=e.i(540626),r=e.i(271645);e.s(["useDebouncedCallback",0,function(e,a){let i=(0,t.useDebouncer)(e,a).maybeExecute;return(0,r.useCallback)((...e)=>i(...e),[i])}])},983561,e=>{"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={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"};var i=e.i(9583),o=r.forwardRef(function(e,o){return r.createElement(i.default,(0,t.default)({},e,{ref:o,icon:a}))});e.s(["RobotOutlined",0,o],983561)},916940,e=>{"use strict";var t=e.i(843476),r=e.i(271645),a=e.i(199133),i=e.i(602869);e.s(["default",0,({onChange:e,value:o,className:l,accessToken:n,placeholder:s="Select vector stores",disabled:c=!1})=>{let[d,u]=(0,r.useState)([]),[f,m]=(0,r.useState)(!1);return(0,r.useEffect)(()=>{(async()=>{if(n){m(!0);try{let e=await (0,i.vectorStoreListCall)(n);e.data&&u(e.data)}catch(e){console.error("Error fetching vector stores:",e)}finally{m(!1)}}})()},[n]),(0,t.jsx)("div",{children:(0,t.jsx)(a.Select,{mode:"multiple",placeholder:s,onChange:e,value:o,loading:f,className:l,allowClear:!0,options:d.map(e=>({label:`${e.vector_store_name||e.vector_store_id} (${e.vector_store_id})`,value:e.vector_store_id,title:e.vector_store_description||e.vector_store_id})),optionFilterProp:"label",showSearch:!0,style:{width:"100%"},disabled:c})})}])},245704,e=>{"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={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"};var i=e.i(9583),o=r.forwardRef(function(e,o){return r.createElement(i.default,(0,t.default)({},e,{ref:o,icon:a}))});e.s(["CheckCircleOutlined",0,o],245704)},637235,e=>{"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={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"};var i=e.i(9583),o=r.forwardRef(function(e,o){return r.createElement(i.default,(0,t.default)({},e,{ref:o,icon:a}))});e.s(["ClockCircleOutlined",0,o],637235)},646563,e=>{"use strict";var t=e.i(959013);e.s(["PlusOutlined",()=>t.default])},597440,e=>{"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={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"};var i=e.i(9583),o=r.forwardRef(function(e,o){return r.createElement(i.default,(0,t.default)({},e,{ref:o,icon:a}))});e.s(["default",0,o],597440)},955135,e=>{"use strict";var t=e.i(597440);e.s(["DeleteOutlined",()=>t.default])},166406,e=>{"use strict";var t=e.i(190144);e.s(["CopyOutlined",()=>t.default])},447566,e=>{"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M872 474H286.9l350.2-304c5.6-4.9 2.2-14-5.2-14h-88.5c-3.9 0-7.6 1.4-10.5 3.9L155 487.8a31.96 31.96 0 000 48.3L535.1 866c1.5 1.3 3.3 2 5.2 2h91.5c7.4 0 10.8-9.2 5.2-14L286.9 550H872c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"}}]},name:"arrow-left",theme:"outlined"};var i=e.i(9583),o=r.forwardRef(function(e,o){return r.createElement(i.default,(0,t.default)({},e,{ref:o,icon:a}))});e.s(["ArrowLeftOutlined",0,o],447566)},653496,e=>{"use strict";var t=e.i(721369);e.s(["Tabs",()=>t.default])},629569,e=>{"use strict";var t=e.i(290571),r=e.i(95779),a=e.i(444755),i=e.i(673706),o=e.i(271645);let l=o.default.forwardRef((e,l)=>{let{color:n,children:s,className:c}=e,d=(0,t.__rest)(e,["color","children","className"]);return o.default.createElement("p",Object.assign({ref:l,className:(0,a.tremorTwMerge)("font-medium text-tremor-title",n?(0,i.getColorClassNames)(n,r.colorPalette.darkText).textColor:"text-tremor-content-strong dark:text-dark-tremor-content-strong",c)},d),s)});l.displayName="Title",e.s(["Title",0,l],629569)},304967,e=>{"use strict";var t=e.i(290571),r=e.i(271645),a=e.i(480731),i=e.i(95779),o=e.i(444755),l=e.i(673706);let n=(0,l.makeClassName)("Card"),s=r.default.forwardRef((e,s)=>{let{decoration:c="",decorationColor:d,children:u,className:f}=e,m=(0,t.__rest)(e,["decoration","decorationColor","children","className"]);return r.default.createElement("div",Object.assign({ref:s,className:(0,o.tremorTwMerge)(n("root"),"relative w-full text-left ring-1 rounded-tremor-default p-6","bg-tremor-background ring-tremor-ring shadow-tremor-card","dark:bg-dark-tremor-background dark:ring-dark-tremor-ring dark:shadow-dark-tremor-card",d?(0,l.getColorClassNames)(d,i.colorPalette.border).borderColor:"border-tremor-brand dark:border-dark-tremor-brand",(e=>{if(!e)return"";switch(e){case a.HorizontalPositions.Left:return"border-l-4";case a.VerticalPositions.Top:return"border-t-4";case a.HorizontalPositions.Right:return"border-r-4";case a.VerticalPositions.Bottom:return"border-b-4";default:return""}})(c),f)},m),u)});s.displayName="Card",e.s(["Card",0,s],304967)},91874,681216,e=>{"use strict";var t=e.i(931067),r=e.i(209428),a=e.i(211577),i=e.i(392221),o=e.i(703923),l=e.i(343794),n=e.i(914949),s=e.i(271645),c=["prefixCls","className","style","checked","disabled","defaultChecked","type","title","onChange"],d=(0,s.forwardRef)(function(e,d){var u=e.prefixCls,f=void 0===u?"rc-checkbox":u,m=e.className,p=e.style,v=e.checked,h=e.disabled,g=e.defaultChecked,b=e.type,y=void 0===b?"checkbox":b,$=e.title,S=e.onChange,C=(0,o.default)(e,c),x=(0,s.useRef)(null),w=(0,s.useRef)(null),k=(0,n.default)(void 0!==g&&g,{value:v}),E=(0,i.default)(k,2),z=E[0],O=E[1];(0,s.useImperativeHandle)(d,function(){return{focus:function(e){var t;null==(t=x.current)||t.focus(e)},blur:function(){var e;null==(e=x.current)||e.blur()},input:x.current,nativeElement:w.current}});var N=(0,l.default)(f,m,(0,a.default)((0,a.default)({},"".concat(f,"-checked"),z),"".concat(f,"-disabled"),h));return s.createElement("span",{className:N,title:$,style:p,ref:w},s.createElement("input",(0,t.default)({},C,{className:"".concat(f,"-input"),ref:x,onChange:function(t){h||("checked"in e||O(t.target.checked),null==S||S({target:(0,r.default)((0,r.default)({},e),{},{type:y,checked:t.target.checked}),stopPropagation:function(){t.stopPropagation()},preventDefault:function(){t.preventDefault()},nativeEvent:t.nativeEvent}))},disabled:h,checked:!!z,type:y})),s.createElement("span",{className:"".concat(f,"-inner")}))});e.s(["default",0,d],91874);var u=e.i(963188);e.s(["default",0,function(e){let t=s.default.useRef(null),r=()=>{u.default.cancel(t.current),t.current=null};return[()=>{r(),t.current=(0,u.default)(()=>{t.current=null})},a=>{t.current&&(a.stopPropagation(),r()),null==e||e(a)}]}],681216)},374276,236836,e=>{"use strict";e.i(247167);var t=e.i(271645),r=e.i(343794),a=e.i(91874),i=e.i(611935),o=e.i(121872),l=e.i(26905),n=e.i(242064),s=e.i(937328),c=e.i(321883),d=e.i(62139);let u=t.default.createContext(null);e.i(296059);var f=e.i(915654),m=e.i(183293),p=e.i(246422),v=e.i(838378);function h(e,t){return(e=>{let{checkboxCls:t}=e,r=`${t}-wrapper`;return[{[`${t}-group`]:Object.assign(Object.assign({},(0,m.resetComponent)(e)),{display:"inline-flex",flexWrap:"wrap",columnGap:e.marginXS,[`> ${e.antCls}-row`]:{flex:1}}),[r]:Object.assign(Object.assign({},(0,m.resetComponent)(e)),{display:"inline-flex",alignItems:"baseline",cursor:"pointer","&:after":{display:"inline-block",width:0,overflow:"hidden",content:"'\\a0'"},[`& + ${r}`]:{marginInlineStart:0},[`&${r}-in-form-item`]:{'input[type="checkbox"]':{width:14,height:14}}}),[t]:Object.assign(Object.assign({},(0,m.resetComponent)(e)),{position:"relative",whiteSpace:"nowrap",lineHeight:1,cursor:"pointer",borderRadius:e.borderRadiusSM,alignSelf:"center",[`${t}-input`]:{position:"absolute",inset:0,zIndex:1,cursor:"pointer",opacity:0,margin:0,[`&:focus-visible + ${t}-inner`]:(0,m.genFocusOutline)(e)},[`${t}-inner`]:{boxSizing:"border-box",display:"block",width:e.checkboxSize,height:e.checkboxSize,direction:"ltr",backgroundColor:e.colorBgContainer,border:`${(0,f.unit)(e.lineWidth)} ${e.lineType} ${e.colorBorder}`,borderRadius:e.borderRadiusSM,borderCollapse:"separate",transition:`all ${e.motionDurationSlow}`,"&:after":{boxSizing:"border-box",position:"absolute",top:"50%",insetInlineStart:"25%",display:"table",width:e.calc(e.checkboxSize).div(14).mul(5).equal(),height:e.calc(e.checkboxSize).div(14).mul(8).equal(),border:`${(0,f.unit)(e.lineWidthBold)} solid ${e.colorWhite}`,borderTop:0,borderInlineStart:0,transform:"rotate(45deg) scale(0) translate(-50%,-50%)",opacity:0,content:'""',transition:`all ${e.motionDurationFast} ${e.motionEaseInBack}, opacity ${e.motionDurationFast}`}},"& + span":{paddingInlineStart:e.paddingXS,paddingInlineEnd:e.paddingXS}})},{[` - ${r}:not(${r}-disabled), - ${t}:not(${t}-disabled) - `]:{[`&:hover ${t}-inner`]:{borderColor:e.colorPrimary}},[`${r}:not(${r}-disabled)`]:{[`&:hover ${t}-checked:not(${t}-disabled) ${t}-inner`]:{backgroundColor:e.colorPrimaryHover,borderColor:"transparent"},[`&:hover ${t}-checked:not(${t}-disabled):after`]:{borderColor:e.colorPrimaryHover}}},{[`${t}-checked`]:{[`${t}-inner`]:{backgroundColor:e.colorPrimary,borderColor:e.colorPrimary,"&:after":{opacity:1,transform:"rotate(45deg) scale(1) translate(-50%,-50%)",transition:`all ${e.motionDurationMid} ${e.motionEaseOutBack} ${e.motionDurationFast}`}}},[` - ${r}-checked:not(${r}-disabled), - ${t}-checked:not(${t}-disabled) - `]:{[`&:hover ${t}-inner`]:{backgroundColor:e.colorPrimaryHover,borderColor:"transparent"}}},{[t]:{"&-indeterminate":{"&":{[`${t}-inner`]:{backgroundColor:`${e.colorBgContainer}`,borderColor:`${e.colorBorder}`,"&:after":{top:"50%",insetInlineStart:"50%",width:e.calc(e.fontSizeLG).div(2).equal(),height:e.calc(e.fontSizeLG).div(2).equal(),backgroundColor:e.colorPrimary,border:0,transform:"translate(-50%, -50%) scale(1)",opacity:1,content:'""'}},[`&:hover ${t}-inner`]:{backgroundColor:`${e.colorBgContainer}`,borderColor:`${e.colorPrimary}`}}}}},{[`${r}-disabled`]:{cursor:"not-allowed"},[`${t}-disabled`]:{[`&, ${t}-input`]:{cursor:"not-allowed",pointerEvents:"none"},[`${t}-inner`]:{background:e.colorBgContainerDisabled,borderColor:e.colorBorder,"&:after":{borderColor:e.colorTextDisabled}},"&:after":{display:"none"},"& + span":{color:e.colorTextDisabled},[`&${t}-indeterminate ${t}-inner::after`]:{background:e.colorTextDisabled}}}]})((0,v.mergeToken)(t,{checkboxCls:`.${e}`,checkboxSize:t.controlInteractiveSize}))}let g=(0,p.genStyleHooks)("Checkbox",(e,{prefixCls:t})=>[h(t,e)]);e.s(["default",0,g,"getStyle",0,h],236836);var b=e.i(681216),y=function(e,t){var r={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(r[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var i=0,a=Object.getOwnPropertySymbols(e);it.indexOf(a[i])&&Object.prototype.propertyIsEnumerable.call(e,a[i])&&(r[a[i]]=e[a[i]]);return r};let $=t.forwardRef((e,f)=>{var m;let{prefixCls:p,className:v,rootClassName:h,children:$,indeterminate:S=!1,style:C,onMouseEnter:x,onMouseLeave:w,skipGroup:k=!1,disabled:E}=e,z=y(e,["prefixCls","className","rootClassName","children","indeterminate","style","onMouseEnter","onMouseLeave","skipGroup","disabled"]),{getPrefixCls:O,direction:N,checkbox:M}=t.useContext(n.ConfigContext),D=t.useContext(u),{isFormItemInput:j}=t.useContext(d.FormItemInputContext),P=t.useContext(s.default),I=null!=(m=(null==D?void 0:D.disabled)||E)?m:P,T=t.useRef(z.value),_=t.useRef(null),H=(0,i.composeRef)(f,_);t.useEffect(()=>{null==D||D.registerValue(z.value)},[]),t.useEffect(()=>{if(!k)return z.value!==T.current&&(null==D||D.cancelValue(T.current),null==D||D.registerValue(z.value),T.current=z.value),()=>null==D?void 0:D.cancelValue(z.value)},[z.value]),t.useEffect(()=>{var e;(null==(e=_.current)?void 0:e.input)&&(_.current.input.indeterminate=S)},[S]);let R=O("checkbox",p),B=(0,c.default)(R),[L,q,X]=g(R,B),G=Object.assign({},z);D&&!k&&(G.onChange=(...e)=>{z.onChange&&z.onChange.apply(z,e),D.toggleOption&&D.toggleOption({label:$,value:z.value})},G.name=D.name,G.checked=D.value.includes(z.value));let V=(0,r.default)(`${R}-wrapper`,{[`${R}-rtl`]:"rtl"===N,[`${R}-wrapper-checked`]:G.checked,[`${R}-wrapper-disabled`]:I,[`${R}-wrapper-in-form-item`]:j},null==M?void 0:M.className,v,h,X,B,q),F=(0,r.default)({[`${R}-indeterminate`]:S},l.TARGET_CLS,q),[A,W]=(0,b.default)(G.onClick);return L(t.createElement(o.default,{component:"Checkbox",disabled:I},t.createElement("label",{className:V,style:Object.assign(Object.assign({},null==M?void 0:M.style),C),onMouseEnter:x,onMouseLeave:w,onClick:A},t.createElement(a.default,Object.assign({},G,{onClick:W,prefixCls:R,className:F,disabled:I,ref:H})),null!=$&&t.createElement("span",{className:`${R}-label`},$))))});var S=e.i(8211),C=e.i(529681),x=function(e,t){var r={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(r[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var i=0,a=Object.getOwnPropertySymbols(e);it.indexOf(a[i])&&Object.prototype.propertyIsEnumerable.call(e,a[i])&&(r[a[i]]=e[a[i]]);return r};let w=t.forwardRef((e,a)=>{let{defaultValue:i,children:o,options:l=[],prefixCls:s,className:d,rootClassName:f,style:m,onChange:p}=e,v=x(e,["defaultValue","children","options","prefixCls","className","rootClassName","style","onChange"]),{getPrefixCls:h,direction:b}=t.useContext(n.ConfigContext),[y,w]=t.useState(v.value||i||[]),[k,E]=t.useState([]);t.useEffect(()=>{"value"in v&&w(v.value||[])},[v.value]);let z=t.useMemo(()=>l.map(e=>"string"==typeof e||"number"==typeof e?{label:e,value:e}:e),[l]),O=e=>{E(t=>t.filter(t=>t!==e))},N=e=>{E(t=>[].concat((0,S.default)(t),[e]))},M=e=>{let t=y.indexOf(e.value),r=(0,S.default)(y);-1===t?r.push(e.value):r.splice(t,1),"value"in v||w(r),null==p||p(r.filter(e=>k.includes(e)).sort((e,t)=>z.findIndex(t=>t.value===e)-z.findIndex(e=>e.value===t)))},D=h("checkbox",s),j=`${D}-group`,P=(0,c.default)(D),[I,T,_]=g(D,P),H=(0,C.default)(v,["value","disabled"]),R=l.length?z.map(e=>t.createElement($,{prefixCls:D,key:e.value.toString(),disabled:"disabled"in e?e.disabled:v.disabled,value:e.value,checked:y.includes(e.value),onChange:e.onChange,className:(0,r.default)(`${j}-item`,e.className),style:e.style,title:e.title,id:e.id,required:e.required},e.label)):o,B=t.useMemo(()=>({toggleOption:M,value:y,disabled:v.disabled,name:v.name,registerValue:N,cancelValue:O}),[M,y,v.disabled,v.name,N,O]),L=(0,r.default)(j,{[`${j}-rtl`]:"rtl"===b},d,f,_,P,T);return I(t.createElement("div",Object.assign({className:L,style:m},H,{ref:a}),t.createElement(u.Provider,{value:B},R)))});$.Group=w,$.__ANT_CHECKBOX=!0,e.s(["default",0,$],374276)},536916,e=>{"use strict";var t=e.i(374276);e.s(["Checkbox",()=>t.default])},921511,e=>{"use strict";var t=e.i(843476),r=e.i(271645),a=e.i(199133),i=e.i(602869);function o(e){return e.filter(e=>(e.version_status??"draft")!=="draft").map(e=>{var t;let r=e.version_number??1,a=e.version_status??"draft";return{label:`${e.policy_name} — v${r} (${a})${e.description?` — ${e.description}`:""}`,value:"production"===a?e.policy_name:e.policy_id?(t=e.policy_id,`policy_${t}`):e.policy_name}})}e.s(["default",0,({onChange:e,value:l,className:n,accessToken:s,disabled:c,onPoliciesLoaded:d})=>{let[u,f]=(0,r.useState)([]),[m,p]=(0,r.useState)(!1);return(0,r.useEffect)(()=>{(async()=>{if(s){p(!0);try{let e=await (0,i.getPoliciesList)(s);e.policies&&(f(e.policies),d?.(e.policies))}catch(e){console.error("Error fetching policies:",e)}finally{p(!1)}}})()},[s,d]),(0,t.jsx)("div",{children:(0,t.jsx)(a.Select,{mode:"multiple",disabled:c,placeholder:c?"Setting policies is a premium feature.":"Select policies (production or published versions)",onChange:t=>{e(t)},value:l,loading:m,className:n,allowClear:!0,options:o(u),optionFilterProp:"label",showSearch:!0,style:{width:"100%"}})})},"getPolicyOptionEntries",0,o])},891547,e=>{"use strict";var t=e.i(843476),r=e.i(271645),a=e.i(199133),i=e.i(602869);e.s(["default",0,({onChange:e,value:o,className:l,accessToken:n,disabled:s})=>{let[c,d]=(0,r.useState)([]),[u,f]=(0,r.useState)(!1);return(0,r.useEffect)(()=>{(async()=>{if(n){f(!0);try{let e=await (0,i.getGuardrailsList)(n);e.guardrails&&d(e.guardrails)}catch(e){console.error("Error fetching guardrails:",e)}finally{f(!1)}}})()},[n]),(0,t.jsx)("div",{children:(0,t.jsx)(a.Select,{mode:"multiple",disabled:s,placeholder:s?"Setting guardrails is a premium feature.":"Select guardrails",onChange:t=>{e(t)},value:o,loading:u,className:l,allowClear:!0,options:c.map(e=>({label:`${e.guardrail_name}`,value:e.guardrail_name})),optionFilterProp:"label",showSearch:!0,style:{width:"100%"}})})}])}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/03c3h-nx-fb3y.js b/litellm/proxy/_experimental/out/_next/static/chunks/03c3h-nx-fb3y.js new file mode 100644 index 00000000000..a44a9973265 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/03c3h-nx-fb3y.js @@ -0,0 +1 @@ +(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,95779,e=>{"use strict";var t=e.i(480731);t.BaseColors.Blue,t.BaseColors.Cyan,t.BaseColors.Sky,t.BaseColors.Indigo,t.BaseColors.Violet,t.BaseColors.Purple,t.BaseColors.Fuchsia,t.BaseColors.Slate,t.BaseColors.Gray,t.BaseColors.Zinc,t.BaseColors.Neutral,t.BaseColors.Stone,t.BaseColors.Red,t.BaseColors.Orange,t.BaseColors.Amber,t.BaseColors.Yellow,t.BaseColors.Lime,t.BaseColors.Green,t.BaseColors.Emerald,t.BaseColors.Teal,t.BaseColors.Pink,t.BaseColors.Rose,e.s(["colorPalette",0,{canvasBackground:50,lightBackground:100,background:500,darkBackground:600,darkestBackground:800,lightBorder:200,border:500,darkBorder:700,lightRing:200,ring:300,iconRing:500,lightText:400,text:500,iconText:600,darkText:700,darkestText:900,icon:500}])},599724,936325,e=>{"use strict";var t=e.i(95779),r=e.i(444755),o=e.i(673706),a=e.i(271645);let n=a.default.forwardRef((e,n)=>{let{color:s,className:l,children:i}=e;return a.default.createElement("p",{ref:n,className:(0,r.tremorTwMerge)("text-tremor-default",s?(0,o.getColorClassNames)(s,t.colorPalette.text).textColor:(0,r.tremorTwMerge)("text-tremor-content","dark:text-dark-tremor-content"),l)},i)});n.displayName="Text",e.s(["default",0,n],936325),e.s(["Text",0,n],599724)},994388,e=>{"use strict";var t=e.i(290571),r=e.i(829087),o=e.i(271645);let a=["preEnter","entering","entered","preExit","exiting","exited","unmounted"],n=e=>({_s:e,status:a[e],isEnter:e<3,isMounted:6!==e,isResolved:2===e||e>4}),s=e=>e?6:5,l=(e,t,r,o,a)=>{clearTimeout(o.current);let s=n(e);t(s),r.current=s,a&&a({current:s})};var i=e.i(480731),c=e.i(444755),d=e.i(673706);let u=e=>{var r=(0,t.__rest)(e,[]);return o.default.createElement("svg",Object.assign({},r,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"}),o.default.createElement("path",{fill:"none",d:"M0 0h24v24H0z"}),o.default.createElement("path",{d:"M18.364 5.636L16.95 7.05A7 7 0 1 0 19 12h2a9 9 0 1 1-2.636-6.364z"}))};var m=e.i(95779);let f={xs:{height:"h-4",width:"w-4"},sm:{height:"h-5",width:"w-5"},md:{height:"h-5",width:"w-5"},lg:{height:"h-6",width:"w-6"},xl:{height:"h-6",width:"w-6"}},p=(e,t)=>{switch(e){case"primary":return{textColor:t?(0,d.getColorClassNames)("white").textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",hoverTextColor:t?(0,d.getColorClassNames)("white").textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",bgColor:t?(0,d.getColorClassNames)(t,m.colorPalette.background).bgColor:"bg-tremor-brand dark:bg-dark-tremor-brand",hoverBgColor:t?(0,d.getColorClassNames)(t,m.colorPalette.darkBackground).hoverBgColor:"hover:bg-tremor-brand-emphasis dark:hover:bg-dark-tremor-brand-emphasis",borderColor:t?(0,d.getColorClassNames)(t,m.colorPalette.border).borderColor:"border-tremor-brand dark:border-dark-tremor-brand",hoverBorderColor:t?(0,d.getColorClassNames)(t,m.colorPalette.darkBorder).hoverBorderColor:"hover:border-tremor-brand-emphasis dark:hover:border-dark-tremor-brand-emphasis"};case"secondary":return{textColor:t?(0,d.getColorClassNames)(t,m.colorPalette.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",hoverTextColor:t?(0,d.getColorClassNames)(t,m.colorPalette.text).textColor:"hover:text-tremor-brand-emphasis dark:hover:text-dark-tremor-brand-emphasis",bgColor:(0,d.getColorClassNames)("transparent").bgColor,hoverBgColor:t?(0,c.tremorTwMerge)((0,d.getColorClassNames)(t,m.colorPalette.background).hoverBgColor,"hover:bg-opacity-20 dark:hover:bg-opacity-20"):"hover:bg-tremor-brand-faint dark:hover:bg-dark-tremor-brand-faint",borderColor:t?(0,d.getColorClassNames)(t,m.colorPalette.border).borderColor:"border-tremor-brand dark:border-dark-tremor-brand"};case"light":return{textColor:t?(0,d.getColorClassNames)(t,m.colorPalette.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",hoverTextColor:t?(0,d.getColorClassNames)(t,m.colorPalette.darkText).hoverTextColor:"hover:text-tremor-brand-emphasis dark:hover:text-dark-tremor-brand-emphasis",bgColor:(0,d.getColorClassNames)("transparent").bgColor,borderColor:"",hoverBorderColor:""}}},g=(0,d.makeClassName)("Button"),b=({loading:e,iconSize:t,iconPosition:r,Icon:a,needMargin:n,transitionStatus:s})=>{let l=n?r===i.HorizontalPositions.Left?(0,c.tremorTwMerge)("-ml-1","mr-1.5"):(0,c.tremorTwMerge)("-mr-1","ml-1.5"):"",d=(0,c.tremorTwMerge)("w-0 h-0"),m={default:d,entering:d,entered:t,exiting:t,exited:d};return e?o.default.createElement(u,{className:(0,c.tremorTwMerge)(g("icon"),"animate-spin shrink-0",l,m.default,m[s]),style:{transition:"width 150ms"}}):o.default.createElement(a,{className:(0,c.tremorTwMerge)(g("icon"),"shrink-0",t,l)})},h=o.default.forwardRef((e,a)=>{let{icon:u,iconPosition:m=i.HorizontalPositions.Left,size:h=i.Sizes.SM,color:x,variant:v="primary",disabled:C,loading:y=!1,loadingText:k,children:w,tooltip:E,className:T}=e,N=(0,t.__rest)(e,["icon","iconPosition","size","color","variant","disabled","loading","loadingText","children","tooltip","className"]),S=y||C,$=void 0!==u||y,P=y&&k,I=!(!w&&!P),M=(0,c.tremorTwMerge)(f[h].height,f[h].width),F="light"!==v?(0,c.tremorTwMerge)("rounded-tremor-default border","shadow-tremor-input","dark:shadow-dark-tremor-input"):"",R=p(v,x),O=("light"!==v?{xs:{paddingX:"px-2.5",paddingY:"py-1.5",fontSize:"text-xs"},sm:{paddingX:"px-4",paddingY:"py-2",fontSize:"text-sm"},md:{paddingX:"px-4",paddingY:"py-2",fontSize:"text-md"},lg:{paddingX:"px-4",paddingY:"py-2.5",fontSize:"text-lg"},xl:{paddingX:"px-4",paddingY:"py-3",fontSize:"text-xl"}}:{xs:{paddingX:"",paddingY:"",fontSize:"text-xs"},sm:{paddingX:"",paddingY:"",fontSize:"text-sm"},md:{paddingX:"",paddingY:"",fontSize:"text-md"},lg:{paddingX:"",paddingY:"",fontSize:"text-lg"},xl:{paddingX:"",paddingY:"",fontSize:"text-xl"}})[h],{tooltipProps:A,getReferenceProps:B}=(0,r.useTooltip)(300),[j,D]=(({enter:e=!0,exit:t=!0,preEnter:r,preExit:a,timeout:i,initialEntered:c,mountOnEnter:d,unmountOnExit:u,onStateChange:m}={})=>{let[f,p]=(0,o.useState)(()=>n(c?2:s(d))),g=(0,o.useRef)(f),b=(0,o.useRef)(0),[h,x]="object"==typeof i?[i.enter,i.exit]:[i,i],v=(0,o.useCallback)(()=>{let e=((e,t)=>{switch(e){case 1:case 0:return 2;case 4:case 3:return s(t)}})(g.current._s,u);e&&l(e,p,g,b,m)},[m,u]);return[f,(0,o.useCallback)(o=>{let n=e=>{switch(l(e,p,g,b,m),e){case 1:h>=0&&(b.current=((...e)=>setTimeout(...e))(v,h));break;case 4:x>=0&&(b.current=((...e)=>setTimeout(...e))(v,x));break;case 0:case 3:b.current=((...e)=>setTimeout(...e))(()=>{isNaN(document.body.offsetTop)||n(e+1)},0)}},i=g.current.isEnter;"boolean"!=typeof o&&(o=!i),o?i||n(e?+!r:2):i&&n(t?a?3:4:s(u))},[v,m,e,t,r,a,h,x,u]),v]})({timeout:50});return(0,o.useEffect)(()=>{D(y)},[y]),o.default.createElement("button",Object.assign({ref:(0,d.mergeRefs)([a,A.refs.setReference]),className:(0,c.tremorTwMerge)(g("root"),"shrink-0 inline-flex justify-center items-center group font-medium outline-none",F,O.paddingX,O.paddingY,O.fontSize,R.textColor,R.bgColor,R.borderColor,R.hoverBorderColor,S?"opacity-50 cursor-not-allowed":(0,c.tremorTwMerge)(p(v,x).hoverTextColor,p(v,x).hoverBgColor,p(v,x).hoverBorderColor),T),disabled:S},B,N),o.default.createElement(r.default,Object.assign({text:E},A)),$&&m!==i.HorizontalPositions.Right?o.default.createElement(b,{loading:y,iconSize:M,iconPosition:m,Icon:u,transitionStatus:j.status,needMargin:I}):null,P||w?o.default.createElement("span",{className:(0,c.tremorTwMerge)(g("text"),"text-tremor-default whitespace-nowrap")},P?k:w):null,$&&m===i.HorizontalPositions.Right?o.default.createElement(b,{loading:y,iconSize:M,iconPosition:m,Icon:u,transitionStatus:j.status,needMargin:I}):null)});h.displayName="Button",e.s(["Button",0,h],994388)},2788,e=>{"use strict";let t;var r=e.i(700020),o=((t=o||{})[t.None=1]="None",t[t.Focusable=2]="Focusable",t[t.Hidden=4]="Hidden",t);let a=(0,r.forwardRefWithAs)(function(e,t){var o;let{features:a=1,...n}=e,s={ref:t,"aria-hidden":(2&a)==2||(null!=(o=n["aria-hidden"])?o:void 0),hidden:(4&a)==4||void 0,style:{position:"fixed",top:1,left:1,width:1,height:0,padding:0,margin:-1,overflow:"hidden",clip:"rect(0, 0, 0, 0)",whiteSpace:"nowrap",borderWidth:"0",...(4&a)==4&&(2&a)!=2&&{display:"none"}}};return(0,r.useRender)()({ourProps:s,theirProps:n,slot:{},defaultTag:"span",name:"Hidden"})});e.s(["Hidden",0,a,"HiddenFeatures",0,o])},553521,e=>{"use strict";var t=e.i(271645),r=e.i(835696);e.s(["useIsMounted",0,function(){let e=(0,t.useRef)(!1);return(0,r.useIsoMorphicEffect)(()=>(e.current=!0,()=>{e.current=!1}),[]),e}])},652265,e=>{"use strict";let t,r,o,a,n;e.i(544508);var s=e.i(397701),l=e.i(402155);let i=["[contentEditable=true]","[tabindex]","a[href]","area[href]","button:not([disabled])","iframe","input:not([disabled])","select:not([disabled])","textarea:not([disabled])"].map(e=>`${e}:not([tabindex='-1'])`).join(","),c=["[data-autofocus]"].map(e=>`${e}:not([tabindex='-1'])`).join(",");var d=((t=d||{})[t.First=1]="First",t[t.Previous=2]="Previous",t[t.Next=4]="Next",t[t.Last=8]="Last",t[t.WrapAround=16]="WrapAround",t[t.NoScroll=32]="NoScroll",t[t.AutoFocus=64]="AutoFocus",t),u=((r=u||{})[r.Error=0]="Error",r[r.Overflow=1]="Overflow",r[r.Success=2]="Success",r[r.Underflow=3]="Underflow",r),m=((o=m||{})[o.Previous=-1]="Previous",o[o.Next=1]="Next",o);function f(e=document.body){return null==e?[]:Array.from(e.querySelectorAll(i)).sort((e,t)=>Math.sign((e.tabIndex||Number.MAX_SAFE_INTEGER)-(t.tabIndex||Number.MAX_SAFE_INTEGER)))}var p=((a=p||{})[a.Strict=0]="Strict",a[a.Loose=1]="Loose",a),g=((n=g||{})[n.Keyboard=0]="Keyboard",n[n.Mouse=1]="Mouse",n);function b(e,t=e=>e){return e.slice().sort((e,r)=>{let o=t(e),a=t(r);if(null===o||null===a)return 0;let n=o.compareDocumentPosition(a);return n&Node.DOCUMENT_POSITION_FOLLOWING?-1:n&Node.DOCUMENT_POSITION_PRECEDING?1:0})}function h(e,t,{sorted:r=!0,relativeTo:o=null,skipElements:a=[]}={}){var n,s,l;let i=Array.isArray(e)?e.length>0?e[0].ownerDocument:document:e.ownerDocument,d=Array.isArray(e)?r?b(e):e:64&t?function(e=document.body){return null==e?[]:Array.from(e.querySelectorAll(c)).sort((e,t)=>Math.sign((e.tabIndex||Number.MAX_SAFE_INTEGER)-(t.tabIndex||Number.MAX_SAFE_INTEGER)))}(e):f(e);a.length>0&&d.length>1&&(d=d.filter(e=>!a.some(t=>null!=t&&"current"in t?(null==t?void 0:t.current)===e:t===e))),o=null!=o?o:i.activeElement;let u=(()=>{if(5&t)return 1;if(10&t)return -1;throw Error("Missing Focus.First, Focus.Previous, Focus.Next or Focus.Last")})(),m=(()=>{if(1&t)return 0;if(2&t)return Math.max(0,d.indexOf(o))-1;if(4&t)return Math.max(0,d.indexOf(o))+1;if(8&t)return d.length-1;throw Error("Missing Focus.First, Focus.Previous, Focus.Next or Focus.Last")})(),p=32&t?{preventScroll:!0}:{},g=0,x=d.length,v;do{if(g>=x||g+x<=0)return 0;let e=m+g;if(16&t)e=(e+x)%x;else{if(e<0)return 3;if(e>=x)return 1}null==(v=d[e])||v.focus(p),g+=u}while(v!==i.activeElement)return 6&t&&null!=(l=null==(s=null==(n=v)?void 0:n.matches)?void 0:s.call(n,"textarea,input"))&&l&&v.select(),2}"u">typeof window&&"u">typeof document&&(document.addEventListener("keydown",e=>{e.metaKey||e.altKey||e.ctrlKey||(document.documentElement.dataset.headlessuiFocusVisible="")},!0),document.addEventListener("click",e=>{1===e.detail?delete document.documentElement.dataset.headlessuiFocusVisible:0===e.detail&&(document.documentElement.dataset.headlessuiFocusVisible="")},!0)),e.s(["Focus",0,d,"FocusResult",0,u,"FocusableMode",0,p,"focusFrom",0,function(e,t){return h(f(),t,{relativeTo:e})},"focusIn",0,h,"getFocusableElements",0,f,"isFocusableElement",0,function(e,t=0){var r;return e!==(null==(r=(0,l.getOwnerDocument)(e))?void 0:r.body)&&(0,s.match)(t,{0:()=>e.matches(i),1(){let t=e;for(;null!==t;){if(t.matches(i))return!0;t=t.parentElement}return!1}})},"sortByDomNode",0,b])},970554,e=>{"use strict";let t,r,o;var a=e.i(783222),n=e.i(433336),s=e.i(271645),l=e.i(394487),i=e.i(914189),c=e.i(835696),d=e.i(941444),u=e.i(144279),m=e.i(294316),f=e.i(553521),p=e.i(2788);function g({onFocus:e}){let[t,r]=(0,s.useState)(!0),o=(0,f.useIsMounted)();return t?s.default.createElement(p.Hidden,{as:"button",type:"button",features:p.HiddenFeatures.Focusable,onFocus:t=>{t.preventDefault();let a,n=50;a=requestAnimationFrame(function t(){if(n--<=0){a&&cancelAnimationFrame(a);return}if(e()){if(cancelAnimationFrame(a),!o.current)return;r(!1);return}a=requestAnimationFrame(t)})}}):null}var b=e.i(652265),h=e.i(397701),x=e.i(368578),v=e.i(402155),C=e.i(700020);let y=s.createContext(null);function k({children:e}){let t=s.useRef({groups:new Map,get(e,t){var r;let o=this.groups.get(e);o||(o=new Map,this.groups.set(e,o));let a=null!=(r=o.get(t))?r:0;return o.set(t,a+1),[Array.from(o.keys()).indexOf(t),function(){let e=o.get(t);e>1?o.set(t,e-1):o.delete(t)}]}});return s.createElement(y.Provider,{value:t},e)}function w(e){let t=s.useContext(y);if(!t)throw Error("You must wrap your component in a ");let r=s.useId(),[o,a]=t.current.get(e,r);return s.useEffect(()=>a,[]),o}var E=e.i(998348),T=((t=T||{})[t.Forwards=0]="Forwards",t[t.Backwards=1]="Backwards",t),N=((r=N||{})[r.Less=-1]="Less",r[r.Equal=0]="Equal",r[r.Greater=1]="Greater",r),S=((o=S||{})[o.SetSelectedIndex=0]="SetSelectedIndex",o[o.RegisterTab=1]="RegisterTab",o[o.UnregisterTab=2]="UnregisterTab",o[o.RegisterPanel=3]="RegisterPanel",o[o.UnregisterPanel=4]="UnregisterPanel",o);let $={0(e,t){var r;let o=(0,b.sortByDomNode)(e.tabs,e=>e.current),a=(0,b.sortByDomNode)(e.panels,e=>e.current),n=o.filter(e=>{var t;return!(null!=(t=e.current)&&t.hasAttribute("disabled"))}),s={...e,tabs:o,panels:a};if(t.index<0||t.index>o.length-1){let r=(0,h.match)(Math.sign(t.index-e.selectedIndex),{[-1]:()=>1,0:()=>(0,h.match)(Math.sign(t.index),{[-1]:()=>0,0:()=>0,1:()=>1}),1:()=>0});if(0===n.length)return s;let a=(0,h.match)(r,{0:()=>o.indexOf(n[0]),1:()=>o.indexOf(n[n.length-1])});return{...s,selectedIndex:-1===a?e.selectedIndex:a}}let l=o.slice(0,t.index),i=[...o.slice(t.index),...l].find(e=>n.includes(e));if(!i)return s;let c=null!=(r=o.indexOf(i))?r:e.selectedIndex;return -1===c&&(c=e.selectedIndex),{...s,selectedIndex:c}},1(e,t){if(e.tabs.includes(t.tab))return e;let r=e.tabs[e.selectedIndex],o=(0,b.sortByDomNode)([...e.tabs,t.tab],e=>e.current),a=e.selectedIndex;return e.info.current.isControlled||-1===(a=o.indexOf(r))&&(a=e.selectedIndex),{...e,tabs:o,selectedIndex:a}},2:(e,t)=>({...e,tabs:e.tabs.filter(e=>e!==t.tab)}),3:(e,t)=>e.panels.includes(t.panel)?e:{...e,panels:(0,b.sortByDomNode)([...e.panels,t.panel],e=>e.current)},4:(e,t)=>({...e,panels:e.panels.filter(e=>e!==t.panel)})},P=(0,s.createContext)(null);function I(e){let t=(0,s.useContext)(P);if(null===t){let t=Error(`<${e} /> is missing a parent component.`);throw Error.captureStackTrace&&Error.captureStackTrace(t,I),t}return t}P.displayName="TabsDataContext";let M=(0,s.createContext)(null);function F(e){let t=(0,s.useContext)(M);if(null===t){let t=Error(`<${e} /> is missing a parent component.`);throw Error.captureStackTrace&&Error.captureStackTrace(t,F),t}return t}function R(e,t){return(0,h.match)(t.type,$,e,t)}M.displayName="TabsActionsContext";let O=C.RenderFeatures.RenderStrategy|C.RenderFeatures.Static,A=Object.assign((0,C.forwardRefWithAs)(function(e,t){var r,o;let d=(0,s.useId)(),{id:f=`headlessui-tabs-tab-${d}`,disabled:p=!1,autoFocus:g=!1,...y}=e,{orientation:k,activation:T,selectedIndex:N,tabs:S,panels:$}=I("Tab"),P=F("Tab"),M=I("Tab"),[R,O]=(0,s.useState)(null),A=(0,s.useRef)(null),B=(0,m.useSyncRefs)(A,t,O);(0,c.useIsoMorphicEffect)(()=>P.registerTab(A),[P,A]);let j=w("tabs"),D=S.indexOf(A);-1===D&&(D=j);let z=D===N,L=(0,i.useEvent)(e=>{var t;let r=e();if(r===b.FocusResult.Success&&"auto"===T){let e=null==(t=(0,v.getOwnerDocument)(A))?void 0:t.activeElement,r=M.tabs.findIndex(t=>t.current===e);-1!==r&&P.change(r)}return r}),W=(0,i.useEvent)(e=>{let t=S.map(e=>e.current).filter(Boolean);if(e.key===E.Keys.Space||e.key===E.Keys.Enter){e.preventDefault(),e.stopPropagation(),P.change(D);return}switch(e.key){case E.Keys.Home:case E.Keys.PageUp:return e.preventDefault(),e.stopPropagation(),L(()=>(0,b.focusIn)(t,b.Focus.First));case E.Keys.End:case E.Keys.PageDown:return e.preventDefault(),e.stopPropagation(),L(()=>(0,b.focusIn)(t,b.Focus.Last))}if(L(()=>(0,h.match)(k,{vertical:()=>e.key===E.Keys.ArrowUp?(0,b.focusIn)(t,b.Focus.Previous|b.Focus.WrapAround):e.key===E.Keys.ArrowDown?(0,b.focusIn)(t,b.Focus.Next|b.Focus.WrapAround):b.FocusResult.Error,horizontal:()=>e.key===E.Keys.ArrowLeft?(0,b.focusIn)(t,b.Focus.Previous|b.Focus.WrapAround):e.key===E.Keys.ArrowRight?(0,b.focusIn)(t,b.Focus.Next|b.Focus.WrapAround):b.FocusResult.Error}))===b.FocusResult.Success)return e.preventDefault()}),_=(0,s.useRef)(!1),X=(0,i.useEvent)(()=>{var e;_.current||(_.current=!0,null==(e=A.current)||e.focus({preventScroll:!0}),P.change(D),(0,x.microTask)(()=>{_.current=!1}))}),H=(0,i.useEvent)(e=>{e.preventDefault()}),{isFocusVisible:K,focusProps:G}=(0,a.useFocusRing)({autoFocus:g}),{isHovered:V,hoverProps:Y}=(0,n.useHover)({isDisabled:p}),{pressed:U,pressProps:q}=(0,l.useActivePress)({disabled:p}),Q=(0,s.useMemo)(()=>({selected:z,hover:V,active:U,focus:K,autofocus:g,disabled:p}),[z,V,K,U,g,p]),Z=(0,C.mergeProps)({ref:B,onKeyDown:W,onMouseDown:H,onClick:X,id:f,role:"tab",type:(0,u.useResolveButtonType)(e,R),"aria-controls":null==(o=null==(r=$[D])?void 0:r.current)?void 0:o.id,"aria-selected":z,tabIndex:z?0:-1,disabled:p||void 0,autoFocus:g},G,Y,q);return(0,C.useRender)()({ourProps:Z,theirProps:y,slot:Q,defaultTag:"button",name:"Tabs.Tab"})}),{Group:(0,C.forwardRefWithAs)(function(e,t){let{defaultIndex:r=0,vertical:o=!1,manual:a=!1,onChange:n,selectedIndex:l=null,...u}=e,f=o?"vertical":"horizontal",p=a?"manual":"auto",h=null!==l,x=(0,d.useLatestValue)({isControlled:h}),v=(0,m.useSyncRefs)(t),[y,w]=(0,s.useReducer)(R,{info:x,selectedIndex:null!=l?l:r,tabs:[],panels:[]}),E=(0,s.useMemo)(()=>({selectedIndex:y.selectedIndex}),[y.selectedIndex]),T=(0,d.useLatestValue)(n||(()=>{})),N=(0,d.useLatestValue)(y.tabs),S=(0,s.useMemo)(()=>({orientation:f,activation:p,...y}),[f,p,y]),$=(0,i.useEvent)(e=>(w({type:1,tab:e}),()=>w({type:2,tab:e}))),I=(0,i.useEvent)(e=>(w({type:3,panel:e}),()=>w({type:4,panel:e}))),F=(0,i.useEvent)(e=>{O.current!==e&&T.current(e),h||w({type:0,index:e})}),O=(0,d.useLatestValue)(h?e.selectedIndex:y.selectedIndex),A=(0,s.useMemo)(()=>({registerTab:$,registerPanel:I,change:F}),[]);(0,c.useIsoMorphicEffect)(()=>{w({type:0,index:null!=l?l:r})},[l]),(0,c.useIsoMorphicEffect)(()=>{if(void 0===O.current||y.tabs.length<=0)return;let e=(0,b.sortByDomNode)(y.tabs,e=>e.current);e.some((e,t)=>y.tabs[t]!==e)&&F(e.indexOf(y.tabs[O.current]))});let B=(0,C.useRender)();return s.default.createElement(k,null,s.default.createElement(M.Provider,{value:A},s.default.createElement(P.Provider,{value:S},S.tabs.length<=0&&s.default.createElement(g,{onFocus:()=>{var e,t;for(let r of N.current)if((null==(e=r.current)?void 0:e.tabIndex)===0)return null==(t=r.current)||t.focus(),!0;return!1}}),B({ourProps:{ref:v},theirProps:u,slot:E,defaultTag:"div",name:"Tabs"}))))}),List:(0,C.forwardRefWithAs)(function(e,t){let{orientation:r,selectedIndex:o}=I("Tab.List"),a=(0,m.useSyncRefs)(t),n=(0,s.useMemo)(()=>({selectedIndex:o}),[o]);return(0,C.useRender)()({ourProps:{ref:a,role:"tablist","aria-orientation":r},theirProps:e,slot:n,defaultTag:"div",name:"Tabs.List"})}),Panels:(0,C.forwardRefWithAs)(function(e,t){let{selectedIndex:r}=I("Tab.Panels"),o=(0,m.useSyncRefs)(t),a=(0,s.useMemo)(()=>({selectedIndex:r}),[r]);return(0,C.useRender)()({ourProps:{ref:o},theirProps:e,slot:a,defaultTag:"div",name:"Tabs.Panels"})}),Panel:(0,C.forwardRefWithAs)(function(e,t){var r,o,n,l;let i=(0,s.useId)(),{id:d=`headlessui-tabs-panel-${i}`,tabIndex:u=0,...f}=e,{selectedIndex:g,tabs:b,panels:h}=I("Tab.Panel"),x=F("Tab.Panel"),v=(0,s.useRef)(null),y=(0,m.useSyncRefs)(v,t);(0,c.useIsoMorphicEffect)(()=>x.registerPanel(v),[x,v]);let k=w("panels"),E=h.indexOf(v);-1===E&&(E=k);let T=E===g,{isFocusVisible:N,focusProps:S}=(0,a.useFocusRing)(),$=(0,s.useMemo)(()=>({selected:T,focus:N}),[T,N]),P=(0,C.mergeProps)({ref:y,id:d,role:"tabpanel","aria-labelledby":null==(o=null==(r=b[E])?void 0:r.current)?void 0:o.id,tabIndex:T?u:-1},S),M=(0,C.useRender)();return T||null!=(n=f.unmount)&&!n||null!=(l=f.static)&&l?M({ourProps:P,theirProps:f,slot:$,defaultTag:"div",features:O,visible:T,name:"Tabs.Panel"}):s.default.createElement(p.Hidden,{"aria-hidden":"true",...P})})});e.s(["Tab",0,A],970554)},405371,910342,e=>{"use strict";var t=e.i(290571),r=e.i(271645),o=e.i(480731);let a=(0,r.createContext)(o.BaseColors.Blue);e.s(["default",0,a],910342);var n=e.i(970554),s=e.i(444755);let l=(0,e.i(673706).makeClassName)("TabList"),i=(0,r.createContext)("line"),c={line:(0,s.tremorTwMerge)("flex border-b space-x-4","border-tremor-border","dark:border-dark-tremor-border"),solid:(0,s.tremorTwMerge)("inline-flex p-0.5 rounded-tremor-default space-x-1.5","bg-tremor-background-subtle","dark:bg-dark-tremor-background-subtle")},d=r.default.forwardRef((e,o)=>{let{color:d,variant:u="line",children:m,className:f}=e,p=(0,t.__rest)(e,["color","variant","children","className"]);return r.default.createElement(n.Tab.List,Object.assign({ref:o,className:(0,s.tremorTwMerge)(l("root"),"justify-start overflow-x-clip",c[u],f)},p),r.default.createElement(i.Provider,{value:u},r.default.createElement(a.Provider,{value:d},m)))});d.displayName="TabList",e.s(["TabVariantContext",0,i,"default",0,d],405371)},197647,e=>{"use strict";var t=e.i(290571),r=e.i(970554),o=e.i(95779),a=e.i(444755),n=e.i(673706),s=e.i(271645),l=e.i(405371),i=e.i(910342);let c=(0,n.makeClassName)("Tab"),d=s.default.forwardRef((e,d)=>{let{icon:u,className:m,children:f}=e,p=(0,t.__rest)(e,["icon","className","children"]),g=(0,s.useContext)(l.TabVariantContext),b=(0,s.useContext)(i.default);return s.default.createElement(r.Tab,Object.assign({ref:d,className:(0,a.tremorTwMerge)(c("root"),"flex whitespace-nowrap truncate max-w-xs outline-none data-focus-visible:ring text-tremor-default transition duration-100",function(e,t){switch(e){case"line":return(0,a.tremorTwMerge)("data-[selected]:border-b-2 hover:border-b-2 border-transparent transition duration-100 -mb-px px-2 py-2","hover:border-tremor-content hover:text-tremor-content-emphasis text-tremor-content","[&:not([data-selected])]:dark:hover:border-dark-tremor-content-emphasis [&:not([data-selected])]:dark:hover:text-dark-tremor-content-emphasis [&:not([data-selected])]:dark:text-dark-tremor-content",t?(0,n.getColorClassNames)(t,o.colorPalette.border).selectBorderColor:["data-[selected]:border-tremor-brand data-[selected]:text-tremor-brand","data-[selected]:dark:border-dark-tremor-brand data-[selected]:dark:text-dark-tremor-brand"]);case"solid":return(0,a.tremorTwMerge)("border-transparent border rounded-tremor-small px-2.5 py-1","data-[selected]:border-tremor-border data-[selected]:bg-tremor-background data-[selected]:shadow-tremor-input [&:not([data-selected])]:hover:text-tremor-content-emphasis data-[selected]:text-tremor-brand [&:not([data-selected])]:text-tremor-content","dark:data-[selected]:border-dark-tremor-border dark:data-[selected]:bg-dark-tremor-background dark:data-[selected]:shadow-dark-tremor-input dark:[&:not([data-selected])]:hover:text-dark-tremor-content-emphasis dark:data-[selected]:text-dark-tremor-brand dark:[&:not([data-selected])]:text-dark-tremor-content",t?(0,n.getColorClassNames)(t,o.colorPalette.text).selectTextColor:"text-tremor-content dark:text-dark-tremor-content")}}(g,b),m,b&&(0,n.getColorClassNames)(b,o.colorPalette.text).selectTextColor)},p),u?s.default.createElement(u,{className:(0,a.tremorTwMerge)(c("icon"),"flex-none h-5 w-5",f?"mr-2":"")}):null,f?s.default.createElement("span",null,f):null)});d.displayName="Tab",e.s(["Tab",0,d],197647)},653824,e=>{"use strict";var t=e.i(290571),r=e.i(970554),o=e.i(444755),a=e.i(673706),n=e.i(271645);let s=(0,a.makeClassName)("TabGroup"),l=n.default.forwardRef((e,a)=>{let{defaultIndex:l,index:i,onIndexChange:c,children:d,className:u}=e,m=(0,t.__rest)(e,["defaultIndex","index","onIndexChange","children","className"]);return n.default.createElement(r.Tab.Group,Object.assign({as:"div",ref:a,defaultIndex:l,selectedIndex:i,onChange:c,className:(0,o.tremorTwMerge)(s("root"),"w-full",u)},m),d)});l.displayName="TabGroup",e.s(["TabGroup",0,l],653824)},881073,e=>{"use strict";var t=e.i(405371);e.s(["TabList",()=>t.default])},751734,144582,e=>{"use strict";var t=e.i(271645);let r=(0,t.createContext)(0);e.s(["default",0,r],751734);let o=(0,t.createContext)({selectedValue:void 0,handleValueChange:void 0});e.s(["default",0,o],144582)},404206,e=>{"use strict";var t=e.i(290571),r=e.i(751734),o=e.i(144582),a=e.i(444755),n=e.i(673706),s=e.i(271645);let l=(0,n.makeClassName)("TabPanel"),i=s.default.forwardRef((e,n)=>{let{children:i,className:c}=e,d=(0,t.__rest)(e,["children","className"]),{selectedValue:u}=(0,s.useContext)(o.default),m=u===(0,s.useContext)(r.default);return s.default.createElement("div",Object.assign({ref:n,className:(0,a.tremorTwMerge)(l("root"),"w-full mt-2",m?"":"hidden",c),"aria-selected":m?"true":"false"},d),i)});i.displayName="TabPanel",e.s(["TabPanel",0,i],404206)},723731,e=>{"use strict";var t=e.i(290571),r=e.i(970554),o=e.i(751734),a=e.i(144582),n=e.i(444755),s=e.i(673706),l=e.i(271645);let i=(0,s.makeClassName)("TabPanels"),c=l.default.forwardRef((e,s)=>{let{children:c,className:d}=e,u=(0,t.__rest)(e,["children","className"]);return l.default.createElement(r.Tab.Panels,Object.assign({as:"div",ref:s,className:(0,n.tremorTwMerge)(i("root"),"w-full",d)},u),({selectedIndex:e})=>l.default.createElement(a.default.Provider,{value:{selectedValue:e}},l.default.Children.map(c,(e,t)=>l.default.createElement(o.default.Provider,{value:t},e))))});c.displayName="TabPanels",e.s(["TabPanels",0,c],723731)},309821,e=>{"use strict";e.i(247167);var t=e.i(271645),r=e.i(135551),o=e.i(201072),a=e.i(121229),n=e.i(726289),s=e.i(864517),l=e.i(343794),i=e.i(529681),c=e.i(242064),d=e.i(931067),u=e.i(209428),m=e.i(703923),f={percent:0,prefixCls:"rc-progress",strokeColor:"#2db7f5",strokeLinecap:"round",strokeWidth:1,trailColor:"#D9D9D9",trailWidth:1,gapPosition:"bottom"},p=function(){var e=(0,t.useRef)([]),r=(0,t.useRef)(null);return(0,t.useEffect)(function(){var t=Date.now(),o=!1;e.current.forEach(function(e){if(e){o=!0;var a=e.style;a.transitionDuration=".3s, .3s, .3s, .06s",r.current&&t-r.current<100&&(a.transitionDuration="0s, 0s")}}),o&&(r.current=Date.now())}),e.current},g=e.i(410160),b=e.i(392221),h=e.i(654310),x=0,v=(0,h.default)();let C=function(e){var r=t.useState(),o=(0,b.default)(r,2),a=o[0],n=o[1];return t.useEffect(function(){var e;n("rc_progress_".concat((v?(e=x,x+=1):e="TEST_OR_SSR",e)))},[]),e||a};var y=function(e){var r=e.bg,o=e.children;return t.createElement("div",{style:{width:"100%",height:"100%",background:r}},o)};function k(e,t){return Object.keys(e).map(function(r){var o=parseFloat(r),a="".concat(Math.floor(o*t),"%");return"".concat(e[r]," ").concat(a)})}var w=t.forwardRef(function(e,r){var o=e.prefixCls,a=e.color,n=e.gradientId,s=e.radius,l=e.style,i=e.ptg,c=e.strokeLinecap,d=e.strokeWidth,u=e.size,m=e.gapDegree,f=a&&"object"===(0,g.default)(a),p=u/2,b=t.createElement("circle",{className:"".concat(o,"-circle-path"),r:s,cx:p,cy:p,stroke:f?"#FFF":void 0,strokeLinecap:c,strokeWidth:d,opacity:+(0!==i),style:l,ref:r});if(!f)return b;var h="".concat(n,"-conic"),x=k(a,(360-m)/360),v=k(a,1),C="conic-gradient(from ".concat(m?"".concat(180+m/2,"deg"):"0deg",", ").concat(x.join(", "),")"),w="linear-gradient(to ".concat(m?"bottom":"top",", ").concat(v.join(", "),")");return t.createElement(t.Fragment,null,t.createElement("mask",{id:h},b),t.createElement("foreignObject",{x:0,y:0,width:u,height:u,mask:"url(#".concat(h,")")},t.createElement(y,{bg:w},t.createElement(y,{bg:C}))))}),E=function(e,t,r,o,a,n,s,l,i,c){var d=arguments.length>10&&void 0!==arguments[10]?arguments[10]:0,u=(100-o)/100*t;return"round"===i&&100!==o&&(u+=c/2)>=t&&(u=t-.01),{stroke:"string"==typeof l?l:void 0,strokeDasharray:"".concat(t,"px ").concat(e),strokeDashoffset:u+d,transform:"rotate(".concat(a+r/100*360*((360-n)/360)+(0===n?0:({bottom:0,top:180,left:90,right:-90})[s]),"deg)"),transformOrigin:"".concat(50,"px ").concat(50,"px"),transition:"stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s, opacity .3s ease 0s",fillOpacity:0}},T=["id","prefixCls","steps","strokeWidth","trailWidth","gapDegree","gapPosition","trailColor","strokeLinecap","style","className","strokeColor","percent"];function N(e){var t=null!=e?e:[];return Array.isArray(t)?t:[t]}let S=function(e){var r,o,a,n,s=(0,u.default)((0,u.default)({},f),e),i=s.id,c=s.prefixCls,b=s.steps,h=s.strokeWidth,x=s.trailWidth,v=s.gapDegree,y=void 0===v?0:v,k=s.gapPosition,S=s.trailColor,$=s.strokeLinecap,P=s.style,I=s.className,M=s.strokeColor,F=s.percent,R=(0,m.default)(s,T),O=C(i),A="".concat(O,"-gradient"),B=50-h/2,j=2*Math.PI*B,D=y>0?90+y/2:-90,z=(360-y)/360*j,L="object"===(0,g.default)(b)?b:{count:b,gap:2},W=L.count,_=L.gap,X=N(F),H=N(M),K=H.find(function(e){return e&&"object"===(0,g.default)(e)}),G=K&&"object"===(0,g.default)(K)?"butt":$,V=E(j,z,0,100,D,y,k,S,G,h),Y=p();return t.createElement("svg",(0,d.default)({className:(0,l.default)("".concat(c,"-circle"),I),viewBox:"0 0 ".concat(100," ").concat(100),style:P,id:i,role:"presentation"},R),!W&&t.createElement("circle",{className:"".concat(c,"-circle-trail"),r:B,cx:50,cy:50,stroke:S,strokeLinecap:G,strokeWidth:x||h,style:V}),W?(r=Math.round(W*(X[0]/100)),o=100/W,a=0,Array(W).fill(null).map(function(e,n){var s=n<=r-1?H[0]:S,l=s&&"object"===(0,g.default)(s)?"url(#".concat(A,")"):void 0,i=E(j,z,a,o,D,y,k,s,"butt",h,_);return a+=(z-i.strokeDashoffset+_)*100/z,t.createElement("circle",{key:n,className:"".concat(c,"-circle-path"),r:B,cx:50,cy:50,stroke:l,strokeWidth:h,opacity:1,style:i,ref:function(e){Y[n]=e}})})):(n=0,X.map(function(e,r){var o=H[r]||H[H.length-1],a=E(j,z,n,e,D,y,k,o,G,h);return n+=e,t.createElement(w,{key:r,color:o,ptg:e,radius:B,prefixCls:c,gradientId:A,style:a,strokeLinecap:G,strokeWidth:h,gapDegree:y,ref:function(e){Y[r]=e},size:100})}).reverse()))};var $=e.i(491816);e.i(765846);var P=e.i(896091);function I(e){return!e||e<0?0:e>100?100:e}function M({success:e,successPercent:t}){let r=t;return e&&"progress"in e&&(r=e.progress),e&&"percent"in e&&(r=e.percent),r}let F=(e,t,r)=>{var o,a,n,s;let l=-1,i=-1;if("step"===t){let t=r.steps,o=r.strokeWidth;"string"==typeof e||void 0===e?(l="small"===e?2:14,i=null!=o?o:8):"number"==typeof e?[l,i]=[e,e]:[l=14,i=8]=Array.isArray(e)?e:[e.width,e.height],l*=t}else if("line"===t){let t=null==r?void 0:r.strokeWidth;"string"==typeof e||void 0===e?i=t||("small"===e?6:8):"number"==typeof e?[l,i]=[e,e]:[l=-1,i=8]=Array.isArray(e)?e:[e.width,e.height]}else("circle"===t||"dashboard"===t)&&("string"==typeof e||void 0===e?[l,i]="small"===e?[60,60]:[120,120]:"number"==typeof e?[l,i]=[e,e]:Array.isArray(e)&&(l=null!=(a=null!=(o=e[0])?o:e[1])?a:120,i=null!=(s=null!=(n=e[0])?n:e[1])?s:120));return[l,i]},R=e=>{let{prefixCls:r,trailColor:o=null,strokeLinecap:a="round",gapPosition:n,gapDegree:s,width:i=120,type:c,children:d,success:u,size:m=i,steps:f}=e,[p,g]=F(m,"circle"),{strokeWidth:b}=e;void 0===b&&(b=Math.max(3/p*100,6));let h=t.useMemo(()=>s||0===s?s:"dashboard"===c?75:void 0,[s,c]),x=(({percent:e,success:t,successPercent:r})=>{let o=I(M({success:t,successPercent:r}));return[o,I(I(e)-o)]})(e),v="[object Object]"===Object.prototype.toString.call(e.strokeColor),C=(({success:e={},strokeColor:t})=>{let{strokeColor:r}=e;return[r||P.presetPrimaryColors.green,t||null]})({success:u,strokeColor:e.strokeColor}),y=(0,l.default)(`${r}-inner`,{[`${r}-circle-gradient`]:v}),k=t.createElement(S,{steps:f,percent:f?x[1]:x,strokeWidth:b,trailWidth:b,strokeColor:f?C[1]:C,strokeLinecap:a,trailColor:o,prefixCls:r,gapDegree:h,gapPosition:n||"dashboard"===c&&"bottom"||void 0}),w=p<=20,E=t.createElement("div",{className:y,style:{width:p,height:g,fontSize:.15*p+6}},k,!w&&d);return w?t.createElement($.default,{title:d},E):E};e.i(296059);var O=e.i(694758),A=e.i(915654),B=e.i(183293),j=e.i(246422),D=e.i(838378);let z="--progress-line-stroke-color",L="--progress-percent",W=e=>{let t=e?"100%":"-100%";return new O.Keyframes(`antProgress${e?"RTL":"LTR"}Active`,{"0%":{transform:`translateX(${t}) scaleX(0)`,opacity:.1},"20%":{transform:`translateX(${t}) scaleX(0)`,opacity:.5},to:{transform:"translateX(0) scaleX(1)",opacity:0}})},_=(0,j.genStyleHooks)("Progress",e=>{let t=e.calc(e.marginXXS).div(2).equal(),r=(0,D.mergeToken)(e,{progressStepMarginInlineEnd:t,progressStepMinWidth:t,progressActiveMotionDuration:"2.4s"});return[(e=>{let{componentCls:t,iconCls:r}=e;return{[t]:Object.assign(Object.assign({},(0,B.resetComponent)(e)),{display:"inline-block","&-rtl":{direction:"rtl"},"&-line":{position:"relative",width:"100%",fontSize:e.fontSize},[`${t}-outer`]:{display:"inline-flex",alignItems:"center",width:"100%"},[`${t}-inner`]:{position:"relative",display:"inline-block",width:"100%",flex:1,overflow:"hidden",verticalAlign:"middle",backgroundColor:e.remainingColor,borderRadius:e.lineBorderRadius},[`${t}-inner:not(${t}-circle-gradient)`]:{[`${t}-circle-path`]:{stroke:e.defaultColor}},[`${t}-success-bg, ${t}-bg`]:{position:"relative",background:e.defaultColor,borderRadius:e.lineBorderRadius,transition:`all ${e.motionDurationSlow} ${e.motionEaseInOutCirc}`},[`${t}-layout-bottom`]:{display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",[`${t}-text`]:{width:"max-content",marginInlineStart:0,marginTop:e.marginXXS}},[`${t}-bg`]:{overflow:"hidden","&::after":{content:'""',background:{_multi_value_:!0,value:["inherit",`var(${z})`]},height:"100%",width:`calc(1 / var(${L}) * 100%)`,display:"block"},[`&${t}-bg-inner`]:{minWidth:"max-content","&::after":{content:"none"},[`${t}-text-inner`]:{color:e.colorWhite,[`&${t}-text-bright`]:{color:"rgba(0, 0, 0, 0.45)"}}}},[`${t}-success-bg`]:{position:"absolute",insetBlockStart:0,insetInlineStart:0,backgroundColor:e.colorSuccess},[`${t}-text`]:{display:"inline-block",marginInlineStart:e.marginXS,color:e.colorText,lineHeight:1,width:"2em",whiteSpace:"nowrap",textAlign:"start",verticalAlign:"middle",wordBreak:"normal",[r]:{fontSize:e.fontSize},[`&${t}-text-outer`]:{width:"max-content"},[`&${t}-text-outer${t}-text-start`]:{width:"max-content",marginInlineStart:0,marginInlineEnd:e.marginXS}},[`${t}-text-inner`]:{display:"flex",justifyContent:"center",alignItems:"center",width:"100%",height:"100%",marginInlineStart:0,padding:`0 ${(0,A.unit)(e.paddingXXS)}`,[`&${t}-text-start`]:{justifyContent:"start"},[`&${t}-text-end`]:{justifyContent:"end"}},[`&${t}-status-active`]:{[`${t}-bg::before`]:{position:"absolute",inset:0,backgroundColor:e.colorBgContainer,borderRadius:e.lineBorderRadius,opacity:0,animationName:W(),animationDuration:e.progressActiveMotionDuration,animationTimingFunction:e.motionEaseOutQuint,animationIterationCount:"infinite",content:'""'}},[`&${t}-rtl${t}-status-active`]:{[`${t}-bg::before`]:{animationName:W(!0)}},[`&${t}-status-exception`]:{[`${t}-bg`]:{backgroundColor:e.colorError},[`${t}-text`]:{color:e.colorError}},[`&${t}-status-exception ${t}-inner:not(${t}-circle-gradient)`]:{[`${t}-circle-path`]:{stroke:e.colorError}},[`&${t}-status-success`]:{[`${t}-bg`]:{backgroundColor:e.colorSuccess},[`${t}-text`]:{color:e.colorSuccess}},[`&${t}-status-success ${t}-inner:not(${t}-circle-gradient)`]:{[`${t}-circle-path`]:{stroke:e.colorSuccess}}})}})(r),(e=>{let{componentCls:t,iconCls:r}=e;return{[t]:{[`${t}-circle-trail`]:{stroke:e.remainingColor},[`&${t}-circle ${t}-inner`]:{position:"relative",lineHeight:1,backgroundColor:"transparent"},[`&${t}-circle ${t}-text`]:{position:"absolute",insetBlockStart:"50%",insetInlineStart:0,width:"100%",margin:0,padding:0,color:e.circleTextColor,fontSize:e.circleTextFontSize,lineHeight:1,whiteSpace:"normal",textAlign:"center",transform:"translateY(-50%)",[r]:{fontSize:e.circleIconFontSize}},[`${t}-circle&-status-exception`]:{[`${t}-text`]:{color:e.colorError}},[`${t}-circle&-status-success`]:{[`${t}-text`]:{color:e.colorSuccess}}},[`${t}-inline-circle`]:{lineHeight:1,[`${t}-inner`]:{verticalAlign:"bottom"}}}})(r),(e=>{let{componentCls:t}=e;return{[t]:{[`${t}-steps`]:{display:"inline-block","&-outer":{display:"flex",flexDirection:"row",alignItems:"center"},"&-item":{flexShrink:0,minWidth:e.progressStepMinWidth,marginInlineEnd:e.progressStepMarginInlineEnd,backgroundColor:e.remainingColor,transition:`all ${e.motionDurationSlow}`,"&-active":{backgroundColor:e.defaultColor}}}}}})(r),(e=>{let{componentCls:t,iconCls:r}=e;return{[t]:{[`${t}-small&-line, ${t}-small&-line ${t}-text ${r}`]:{fontSize:e.fontSizeSM}}}})(r)]},e=>({circleTextColor:e.colorText,defaultColor:e.colorInfo,remainingColor:e.colorFillSecondary,lineBorderRadius:100,circleTextFontSize:"1em",circleIconFontSize:`${e.fontSize/e.fontSizeSM}em`}));var X=function(e,t){var r={};for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&0>t.indexOf(o)&&(r[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var a=0,o=Object.getOwnPropertySymbols(e);at.indexOf(o[a])&&Object.prototype.propertyIsEnumerable.call(e,o[a])&&(r[o[a]]=e[o[a]]);return r};let H=e=>{let{prefixCls:r,direction:o,percent:a,size:n,strokeWidth:s,strokeColor:i,strokeLinecap:c="round",children:d,trailColor:u=null,percentPosition:m,success:f}=e,{align:p,type:g}=m,b=i&&"string"!=typeof i?((e,t)=>{let{from:r=P.presetPrimaryColors.blue,to:o=P.presetPrimaryColors.blue,direction:a="rtl"===t?"to left":"to right"}=e,n=X(e,["from","to","direction"]);if(0!==Object.keys(n).length){let e,t=(e=[],Object.keys(n).forEach(t=>{let r=Number.parseFloat(t.replace(/%/g,""));Number.isNaN(r)||e.push({key:r,value:n[t]})}),(e=e.sort((e,t)=>e.key-t.key)).map(({key:e,value:t})=>`${t} ${e}%`).join(", ")),r=`linear-gradient(${a}, ${t})`;return{background:r,[z]:r}}let s=`linear-gradient(${a}, ${r}, ${o})`;return{background:s,[z]:s}})(i,o):{[z]:i,background:i},h="square"===c||"butt"===c?0:void 0,[x,v]=F(null!=n?n:[-1,s||("small"===n?6:8)],"line",{strokeWidth:s}),C=Object.assign(Object.assign({width:`${I(a)}%`,height:v,borderRadius:h},b),{[L]:I(a)/100}),y=M(e),k={width:`${I(y)}%`,height:v,borderRadius:h,backgroundColor:null==f?void 0:f.strokeColor},w=t.createElement("div",{className:`${r}-inner`,style:{backgroundColor:u||void 0,borderRadius:h}},t.createElement("div",{className:(0,l.default)(`${r}-bg`,`${r}-bg-${g}`),style:C},"inner"===g&&d),void 0!==y&&t.createElement("div",{className:`${r}-success-bg`,style:k})),E="outer"===g&&"start"===p,T="outer"===g&&"end"===p;return"outer"===g&&"center"===p?t.createElement("div",{className:`${r}-layout-bottom`},w,d):t.createElement("div",{className:`${r}-outer`,style:{width:x<0?"100%":x}},E&&d,w,T&&d)},K=e=>{let{size:r,steps:o,rounding:a=Math.round,percent:n=0,strokeWidth:s=8,strokeColor:i,trailColor:c=null,prefixCls:d,children:u}=e,m=a(n/100*o),[f,p]=F(null!=r?r:["small"===r?2:14,s],"step",{steps:o,strokeWidth:s}),g=f/o,b=Array.from({length:o});for(let e=0;et.indexOf(o)&&(r[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var a=0,o=Object.getOwnPropertySymbols(e);at.indexOf(o[a])&&Object.prototype.propertyIsEnumerable.call(e,o[a])&&(r[o[a]]=e[o[a]]);return r};let V=["normal","exception","active","success"],Y=t.forwardRef((e,d)=>{let u,{prefixCls:m,className:f,rootClassName:p,steps:g,strokeColor:b,percent:h=0,size:x="default",showInfo:v=!0,type:C="line",status:y,format:k,style:w,percentPosition:E={}}=e,T=G(e,["prefixCls","className","rootClassName","steps","strokeColor","percent","size","showInfo","type","status","format","style","percentPosition"]),{align:N="end",type:S="outer"}=E,$=Array.isArray(b)?b[0]:b,P="string"==typeof b||Array.isArray(b)?b:void 0,O=t.useMemo(()=>{if($){let e="string"==typeof $?$:Object.values($)[0];return new r.FastColor(e).isLight()}return!1},[b]),A=t.useMemo(()=>{var t,r;let o=M(e);return Number.parseInt(void 0!==o?null==(t=null!=o?o:0)?void 0:t.toString():null==(r=null!=h?h:0)?void 0:r.toString(),10)},[h,e.success,e.successPercent]),B=t.useMemo(()=>!V.includes(y)&&A>=100?"success":y||"normal",[y,A]),{getPrefixCls:j,direction:D,progress:z}=t.useContext(c.ConfigContext),L=j("progress",m),[W,X,Y]=_(L),U="line"===C,q=U&&!g,Q=t.useMemo(()=>{let r;if(!v)return null;let i=M(e),c=k||(e=>`${e}%`),d=U&&O&&"inner"===S;return"inner"===S||k||"exception"!==B&&"success"!==B?r=c(I(h),I(i)):"exception"===B?r=U?t.createElement(n.default,null):t.createElement(s.default,null):"success"===B&&(r=U?t.createElement(o.default,null):t.createElement(a.default,null)),t.createElement("span",{className:(0,l.default)(`${L}-text`,{[`${L}-text-bright`]:d,[`${L}-text-${N}`]:q,[`${L}-text-${S}`]:q}),title:"string"==typeof r?r:void 0},r)},[v,h,A,B,C,L,k]);"line"===C?u=g?t.createElement(K,Object.assign({},e,{strokeColor:P,prefixCls:L,steps:"object"==typeof g?g.count:g}),Q):t.createElement(H,Object.assign({},e,{strokeColor:$,prefixCls:L,direction:D,percentPosition:{align:N,type:S}}),Q):("circle"===C||"dashboard"===C)&&(u=t.createElement(R,Object.assign({},e,{strokeColor:$,prefixCls:L,progressStatus:B}),Q));let Z=(0,l.default)(L,`${L}-status-${B}`,{[`${L}-${"dashboard"===C&&"circle"||C}`]:"line"!==C,[`${L}-inline-circle`]:"circle"===C&&F(x,"circle")[0]<=20,[`${L}-line`]:q,[`${L}-line-align-${N}`]:q,[`${L}-line-position-${S}`]:q,[`${L}-steps`]:g,[`${L}-show-info`]:v,[`${L}-${x}`]:"string"==typeof x,[`${L}-rtl`]:"rtl"===D},null==z?void 0:z.className,f,p,X,Y);return W(t.createElement("div",Object.assign({ref:d,style:Object.assign(Object.assign({},null==z?void 0:z.style),w),className:Z,role:"progressbar","aria-valuenow":A,"aria-valuemin":0,"aria-valuemax":100},(0,i.default)(T,["trailColor","strokeWidth","width","gapDegree","gapPosition","strokeLinecap","success","successPercent"])),u))});e.s(["default",0,Y],309821)}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/03kaz3d0v3z45.js b/litellm/proxy/_experimental/out/_next/static/chunks/03kaz3d0v3z45.js new file mode 100644 index 00000000000..ea09603b861 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/03kaz3d0v3z45.js @@ -0,0 +1,10 @@ +(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,270377,e=>{"use strict";e.i(247167);var t=e.i(931067),i=e.i(271645);let a={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"};var l=e.i(9583),r=i.forwardRef(function(e,r){return i.createElement(l.default,(0,t.default)({},e,{ref:r,icon:a}))});e.s(["ExclamationCircleOutlined",0,r],270377)},175712,e=>{"use strict";e.i(247167);var t=e.i(271645),i=e.i(343794),a=e.i(529681),l=e.i(242064),r=e.i(517455),s=e.i(185793),n=e.i(721369),o=function(e,t){var i={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(i[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var l=0,a=Object.getOwnPropertySymbols(e);lt.indexOf(a[l])&&Object.prototype.propertyIsEnumerable.call(e,a[l])&&(i[a[l]]=e[a[l]]);return i};let d=e=>{var{prefixCls:a,className:r,hoverable:s=!0}=e,n=o(e,["prefixCls","className","hoverable"]);let{getPrefixCls:d}=t.useContext(l.ConfigContext),c=d("card",a),u=(0,i.default)(`${c}-grid`,r,{[`${c}-grid-hoverable`]:s});return t.createElement("div",Object.assign({},n,{className:u}))};e.i(296059);var c=e.i(915654),u=e.i(183293),A=e.i(246422),g=e.i(838378);let h=(0,A.genStyleHooks)("Card",e=>{let t=(0,g.mergeToken)(e,{cardShadow:e.boxShadowCard,cardHeadPadding:e.padding,cardPaddingBase:e.paddingLG,cardActionsIconSize:e.fontSize});return[(e=>{let{componentCls:t,cardShadow:i,cardHeadPadding:a,colorBorderSecondary:l,boxShadowTertiary:r,bodyPadding:s,extraColor:n}=e;return{[t]:Object.assign(Object.assign({},(0,u.resetComponent)(e)),{position:"relative",background:e.colorBgContainer,borderRadius:e.borderRadiusLG,[`&:not(${t}-bordered)`]:{boxShadow:r},[`${t}-head`]:(e=>{let{antCls:t,componentCls:i,headerHeight:a,headerPadding:l,tabsMarginBottom:r}=e;return Object.assign(Object.assign({display:"flex",justifyContent:"center",flexDirection:"column",minHeight:a,marginBottom:-1,padding:`0 ${(0,c.unit)(l)}`,color:e.colorTextHeading,fontWeight:e.fontWeightStrong,fontSize:e.headerFontSize,background:e.headerBg,borderBottom:`${(0,c.unit)(e.lineWidth)} ${e.lineType} ${e.colorBorderSecondary}`,borderRadius:`${(0,c.unit)(e.borderRadiusLG)} ${(0,c.unit)(e.borderRadiusLG)} 0 0`},(0,u.clearFix)()),{"&-wrapper":{width:"100%",display:"flex",alignItems:"center"},"&-title":Object.assign(Object.assign({display:"inline-block",flex:1},u.textEllipsis),{[` + > ${i}-typography, + > ${i}-typography-edit-content + `]:{insetInlineStart:0,marginTop:0,marginBottom:0}}),[`${t}-tabs-top`]:{clear:"both",marginBottom:r,color:e.colorText,fontWeight:"normal",fontSize:e.fontSize,"&-bar":{borderBottom:`${(0,c.unit)(e.lineWidth)} ${e.lineType} ${e.colorBorderSecondary}`}}})})(e),[`${t}-extra`]:{marginInlineStart:"auto",color:n,fontWeight:"normal",fontSize:e.fontSize},[`${t}-body`]:{padding:s,borderRadius:`0 0 ${(0,c.unit)(e.borderRadiusLG)} ${(0,c.unit)(e.borderRadiusLG)}`},[`${t}-grid`]:(e=>{let{cardPaddingBase:t,colorBorderSecondary:i,cardShadow:a,lineWidth:l}=e;return{width:"33.33%",padding:t,border:0,borderRadius:0,boxShadow:` + ${(0,c.unit)(l)} 0 0 0 ${i}, + 0 ${(0,c.unit)(l)} 0 0 ${i}, + ${(0,c.unit)(l)} ${(0,c.unit)(l)} 0 0 ${i}, + ${(0,c.unit)(l)} 0 0 0 ${i} inset, + 0 ${(0,c.unit)(l)} 0 0 ${i} inset; + `,transition:`all ${e.motionDurationMid}`,"&-hoverable:hover":{position:"relative",zIndex:1,boxShadow:a}}})(e),[`${t}-cover`]:{"> *":{display:"block",width:"100%",borderRadius:`${(0,c.unit)(e.borderRadiusLG)} ${(0,c.unit)(e.borderRadiusLG)} 0 0`}},[`${t}-actions`]:(e=>{let{componentCls:t,iconCls:i,actionsLiMargin:a,cardActionsIconSize:l,colorBorderSecondary:r,actionsBg:s}=e;return Object.assign(Object.assign({margin:0,padding:0,listStyle:"none",background:s,borderTop:`${(0,c.unit)(e.lineWidth)} ${e.lineType} ${r}`,display:"flex",borderRadius:`0 0 ${(0,c.unit)(e.borderRadiusLG)} ${(0,c.unit)(e.borderRadiusLG)}`},(0,u.clearFix)()),{"& > li":{margin:a,color:e.colorTextDescription,textAlign:"center","> span":{position:"relative",display:"block",minWidth:e.calc(e.cardActionsIconSize).mul(2).equal(),fontSize:e.fontSize,lineHeight:e.lineHeight,cursor:"pointer","&:hover":{color:e.colorPrimary,transition:`color ${e.motionDurationMid}`},[`a:not(${t}-btn), > ${i}`]:{display:"inline-block",width:"100%",color:e.colorIcon,lineHeight:(0,c.unit)(e.fontHeight),transition:`color ${e.motionDurationMid}`,"&:hover":{color:e.colorPrimary}},[`> ${i}`]:{fontSize:l,lineHeight:(0,c.unit)(e.calc(l).mul(e.lineHeight).equal())}},"&:not(:last-child)":{borderInlineEnd:`${(0,c.unit)(e.lineWidth)} ${e.lineType} ${r}`}}})})(e),[`${t}-meta`]:Object.assign(Object.assign({margin:`${(0,c.unit)(e.calc(e.marginXXS).mul(-1).equal())} 0`,display:"flex"},(0,u.clearFix)()),{"&-avatar":{paddingInlineEnd:e.padding},"&-detail":{overflow:"hidden",flex:1,"> div:not(:last-child)":{marginBottom:e.marginXS}},"&-title":Object.assign({color:e.colorTextHeading,fontWeight:e.fontWeightStrong,fontSize:e.fontSizeLG},u.textEllipsis),"&-description":{color:e.colorTextDescription}})}),[`${t}-bordered`]:{border:`${(0,c.unit)(e.lineWidth)} ${e.lineType} ${l}`,[`${t}-cover`]:{marginTop:-1,marginInlineStart:-1,marginInlineEnd:-1}},[`${t}-hoverable`]:{cursor:"pointer",transition:`box-shadow ${e.motionDurationMid}, border-color ${e.motionDurationMid}`,"&:hover":{borderColor:"transparent",boxShadow:i}},[`${t}-contain-grid`]:{borderRadius:`${(0,c.unit)(e.borderRadiusLG)} ${(0,c.unit)(e.borderRadiusLG)} 0 0 `,[`${t}-body`]:{display:"flex",flexWrap:"wrap"},[`&:not(${t}-loading) ${t}-body`]:{marginBlockStart:e.calc(e.lineWidth).mul(-1).equal(),marginInlineStart:e.calc(e.lineWidth).mul(-1).equal(),padding:0}},[`${t}-contain-tabs`]:{[`> div${t}-head`]:{minHeight:0,[`${t}-head-title, ${t}-extra`]:{paddingTop:a}}},[`${t}-type-inner`]:(e=>{let{componentCls:t,colorFillAlter:i,headerPadding:a,bodyPadding:l}=e;return{[`${t}-head`]:{padding:`0 ${(0,c.unit)(a)}`,background:i,"&-title":{fontSize:e.fontSize}},[`${t}-body`]:{padding:`${(0,c.unit)(e.padding)} ${(0,c.unit)(l)}`}}})(e),[`${t}-loading`]:(e=>{let{componentCls:t}=e;return{overflow:"hidden",[`${t}-body`]:{userSelect:"none"}}})(e),[`${t}-rtl`]:{direction:"rtl"}}})(t),(e=>{let{componentCls:t,bodyPaddingSM:i,headerPaddingSM:a,headerHeightSM:l,headerFontSizeSM:r}=e;return{[`${t}-small`]:{[`> ${t}-head`]:{minHeight:l,padding:`0 ${(0,c.unit)(a)}`,fontSize:r,[`> ${t}-head-wrapper`]:{[`> ${t}-extra`]:{fontSize:e.fontSize}}},[`> ${t}-body`]:{padding:i}},[`${t}-small${t}-contain-tabs`]:{[`> ${t}-head`]:{[`${t}-head-title, ${t}-extra`]:{paddingTop:0,display:"flex",alignItems:"center"}}}}})(t)]},e=>{var t,i;return{headerBg:"transparent",headerFontSize:e.fontSizeLG,headerFontSizeSM:e.fontSize,headerHeight:e.fontSizeLG*e.lineHeightLG+2*e.padding,headerHeightSM:e.fontSize*e.lineHeight+2*e.paddingXS,actionsBg:e.colorBgContainer,actionsLiMargin:`${e.paddingSM}px 0`,tabsMarginBottom:-e.padding-e.lineWidth,extraColor:e.colorText,bodyPaddingSM:12,headerPaddingSM:12,bodyPadding:null!=(t=e.bodyPadding)?t:e.paddingLG,headerPadding:null!=(i=e.headerPadding)?i:e.paddingLG}});var m=e.i(792812),f=function(e,t){var i={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(i[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var l=0,a=Object.getOwnPropertySymbols(e);lt.indexOf(a[l])&&Object.prototype.propertyIsEnumerable.call(e,a[l])&&(i[a[l]]=e[a[l]]);return i};let p=e=>{let{actionClasses:i,actions:a=[],actionStyle:l}=e;return t.createElement("ul",{className:i,style:l},a.map((e,i)=>{let l=`action-${i}`;return t.createElement("li",{style:{width:`${100/a.length}%`},key:l},t.createElement("span",null,e))}))},b=t.forwardRef((e,o)=>{let c,{prefixCls:u,className:A,rootClassName:g,style:b,extra:x,headStyle:O={},bodyStyle:y={},title:v,loading:E,bordered:C,variant:I,size:w,type:S,cover:R,actions:L,tabList:B,children:k,activeTabKey:_,defaultActiveTabKey:T,tabBarExtraContent:j,hoverable:M,tabProps:$={},classNames:H,styles:N}=e,P=f(e,["prefixCls","className","rootClassName","style","extra","headStyle","bodyStyle","title","loading","bordered","variant","size","type","cover","actions","tabList","children","activeTabKey","defaultActiveTabKey","tabBarExtraContent","hoverable","tabProps","classNames","styles"]),{getPrefixCls:z,direction:D,card:U}=t.useContext(l.ConfigContext),[W]=(0,m.default)("card",I,C),q=e=>{var t;return(0,i.default)(null==(t=null==U?void 0:U.classNames)?void 0:t[e],null==H?void 0:H[e])},G=e=>{var t;return Object.assign(Object.assign({},null==(t=null==U?void 0:U.styles)?void 0:t[e]),null==N?void 0:N[e])},F=t.useMemo(()=>{let e=!1;return t.Children.forEach(k,t=>{(null==t?void 0:t.type)===d&&(e=!0)}),e},[k]),Q=z("card",u),[V,K,Y]=h(Q),J=t.createElement(s.default,{loading:!0,active:!0,paragraph:{rows:4},title:!1},k),X=void 0!==_,Z=Object.assign(Object.assign({},$),{[X?"activeKey":"defaultActiveKey"]:X?_:T,tabBarExtraContent:j}),ee=(0,r.default)(w),et=ee&&"default"!==ee?ee:"large",ei=B?t.createElement(n.default,Object.assign({size:et},Z,{className:`${Q}-head-tabs`,onChange:t=>{var i;null==(i=e.onTabChange)||i.call(e,t)},items:B.map(e=>{var{tab:t}=e;return Object.assign({label:t},f(e,["tab"]))})})):null;if(v||x||ei){let e=(0,i.default)(`${Q}-head`,q("header")),a=(0,i.default)(`${Q}-head-title`,q("title")),l=(0,i.default)(`${Q}-extra`,q("extra")),r=Object.assign(Object.assign({},O),G("header"));c=t.createElement("div",{className:e,style:r},t.createElement("div",{className:`${Q}-head-wrapper`},v&&t.createElement("div",{className:a,style:G("title")},v),x&&t.createElement("div",{className:l,style:G("extra")},x)),ei)}let ea=(0,i.default)(`${Q}-cover`,q("cover")),el=R?t.createElement("div",{className:ea,style:G("cover")},R):null,er=(0,i.default)(`${Q}-body`,q("body")),es=Object.assign(Object.assign({},y),G("body")),en=t.createElement("div",{className:er,style:es},E?J:k),eo=(0,i.default)(`${Q}-actions`,q("actions")),ed=(null==L?void 0:L.length)?t.createElement(p,{actionClasses:eo,actionStyle:G("actions"),actions:L}):null,ec=(0,a.default)(P,["onTabChange"]),eu=(0,i.default)(Q,null==U?void 0:U.className,{[`${Q}-loading`]:E,[`${Q}-bordered`]:"borderless"!==W,[`${Q}-hoverable`]:M,[`${Q}-contain-grid`]:F,[`${Q}-contain-tabs`]:null==B?void 0:B.length,[`${Q}-${ee}`]:ee,[`${Q}-type-${S}`]:!!S,[`${Q}-rtl`]:"rtl"===D},A,g,K,Y),eA=Object.assign(Object.assign({},null==U?void 0:U.style),b);return V(t.createElement("div",Object.assign({ref:o},ec,{className:eu,style:eA}),c,el,en,ed))});var x=function(e,t){var i={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(i[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var l=0,a=Object.getOwnPropertySymbols(e);lt.indexOf(a[l])&&Object.prototype.propertyIsEnumerable.call(e,a[l])&&(i[a[l]]=e[a[l]]);return i};b.Grid=d,b.Meta=e=>{let{prefixCls:a,className:r,avatar:s,title:n,description:o}=e,d=x(e,["prefixCls","className","avatar","title","description"]),{getPrefixCls:c}=t.useContext(l.ConfigContext),u=c("card",a),A=(0,i.default)(`${u}-meta`,r),g=s?t.createElement("div",{className:`${u}-meta-avatar`},s):null,h=n?t.createElement("div",{className:`${u}-meta-title`},n):null,m=o?t.createElement("div",{className:`${u}-meta-description`},o):null,f=h||m?t.createElement("div",{className:`${u}-meta-detail`},h,m):null;return t.createElement("div",Object.assign({},d,{className:A}),g,f)},e.s(["Card",0,b],175712)},869216,e=>{"use strict";e.i(247167);var t=e.i(271645),i=e.i(343794),a=e.i(908206),l=e.i(242064),r=e.i(517455),s=e.i(150073);let n={xxl:3,xl:3,lg:3,md:3,sm:2,xs:1},o=t.default.createContext({});var d=e.i(876556),c=function(e,t){var i={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(i[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var l=0,a=Object.getOwnPropertySymbols(e);lt.indexOf(a[l])&&Object.prototype.propertyIsEnumerable.call(e,a[l])&&(i[a[l]]=e[a[l]]);return i},u=function(e,t){var i={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(i[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var l=0,a=Object.getOwnPropertySymbols(e);lt.indexOf(a[l])&&Object.prototype.propertyIsEnumerable.call(e,a[l])&&(i[a[l]]=e[a[l]]);return i};let A=e=>{let{itemPrefixCls:a,component:l,span:r,className:s,style:n,labelStyle:d,contentStyle:c,bordered:u,label:A,content:g,colon:h,type:m,styles:f}=e,{classNames:p}=t.useContext(o),b=Object.assign(Object.assign({},d),null==f?void 0:f.label),x=Object.assign(Object.assign({},c),null==f?void 0:f.content);if(u)return t.createElement(l,{colSpan:r,style:n,className:(0,i.default)(s,{[`${a}-item-${m}`]:"label"===m||"content"===m,[null==p?void 0:p.label]:(null==p?void 0:p.label)&&"label"===m,[null==p?void 0:p.content]:(null==p?void 0:p.content)&&"content"===m})},null!=A&&t.createElement("span",{style:b},A),null!=g&&t.createElement("span",{style:x},g));return t.createElement(l,{colSpan:r,style:n,className:(0,i.default)(`${a}-item`,s)},t.createElement("div",{className:`${a}-item-container`},null!=A&&t.createElement("span",{style:b,className:(0,i.default)(`${a}-item-label`,null==p?void 0:p.label,{[`${a}-item-no-colon`]:!h})},A),null!=g&&t.createElement("span",{style:x,className:(0,i.default)(`${a}-item-content`,null==p?void 0:p.content)},g)))};function g(e,{colon:i,prefixCls:a,bordered:l},{component:r,type:s,showLabel:n,showContent:o,labelStyle:d,contentStyle:c,styles:u}){return e.map(({label:e,children:g,prefixCls:h=a,className:m,style:f,labelStyle:p,contentStyle:b,span:x=1,key:O,styles:y},v)=>"string"==typeof r?t.createElement(A,{key:`${s}-${O||v}`,className:m,style:f,styles:{label:Object.assign(Object.assign(Object.assign(Object.assign({},d),null==u?void 0:u.label),p),null==y?void 0:y.label),content:Object.assign(Object.assign(Object.assign(Object.assign({},c),null==u?void 0:u.content),b),null==y?void 0:y.content)},span:x,colon:i,component:r,itemPrefixCls:h,bordered:l,label:n?e:null,content:o?g:null,type:s}):[t.createElement(A,{key:`label-${O||v}`,className:m,style:Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},d),null==u?void 0:u.label),f),p),null==y?void 0:y.label),span:1,colon:i,component:r[0],itemPrefixCls:h,bordered:l,label:e,type:"label"}),t.createElement(A,{key:`content-${O||v}`,className:m,style:Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},c),null==u?void 0:u.content),f),b),null==y?void 0:y.content),span:2*x-1,component:r[1],itemPrefixCls:h,bordered:l,content:g,type:"content"})])}let h=e=>{let i=t.useContext(o),{prefixCls:a,vertical:l,row:r,index:s,bordered:n}=e;return l?t.createElement(t.Fragment,null,t.createElement("tr",{key:`label-${s}`,className:`${a}-row`},g(r,e,Object.assign({component:"th",type:"label",showLabel:!0},i))),t.createElement("tr",{key:`content-${s}`,className:`${a}-row`},g(r,e,Object.assign({component:"td",type:"content",showContent:!0},i)))):t.createElement("tr",{key:s,className:`${a}-row`},g(r,e,Object.assign({component:n?["th","td"]:"td",type:"item",showLabel:!0,showContent:!0},i)))};e.i(296059);var m=e.i(915654),f=e.i(183293),p=e.i(246422),b=e.i(838378);let x=(0,p.genStyleHooks)("Descriptions",e=>(e=>{let{componentCls:t,extraColor:i,itemPaddingBottom:a,itemPaddingEnd:l,colonMarginRight:r,colonMarginLeft:s,titleMarginBottom:n}=e;return{[t]:Object.assign(Object.assign(Object.assign({},(0,f.resetComponent)(e)),(e=>{let{componentCls:t,labelBg:i}=e;return{[`&${t}-bordered`]:{[`> ${t}-view`]:{border:`${(0,m.unit)(e.lineWidth)} ${e.lineType} ${e.colorSplit}`,"> table":{tableLayout:"auto"},[`${t}-row`]:{borderBottom:`${(0,m.unit)(e.lineWidth)} ${e.lineType} ${e.colorSplit}`,"&:first-child":{"> th:first-child, > td:first-child":{borderStartStartRadius:e.borderRadiusLG}},"&:last-child":{borderBottom:"none","> th:first-child, > td:first-child":{borderEndStartRadius:e.borderRadiusLG}},[`> ${t}-item-label, > ${t}-item-content`]:{padding:`${(0,m.unit)(e.padding)} ${(0,m.unit)(e.paddingLG)}`,borderInlineEnd:`${(0,m.unit)(e.lineWidth)} ${e.lineType} ${e.colorSplit}`,"&:last-child":{borderInlineEnd:"none"}},[`> ${t}-item-label`]:{color:e.colorTextSecondary,backgroundColor:i,"&::after":{display:"none"}}}},[`&${t}-middle`]:{[`${t}-row`]:{[`> ${t}-item-label, > ${t}-item-content`]:{padding:`${(0,m.unit)(e.paddingSM)} ${(0,m.unit)(e.paddingLG)}`}}},[`&${t}-small`]:{[`${t}-row`]:{[`> ${t}-item-label, > ${t}-item-content`]:{padding:`${(0,m.unit)(e.paddingXS)} ${(0,m.unit)(e.padding)}`}}}}}})(e)),{"&-rtl":{direction:"rtl"},[`${t}-header`]:{display:"flex",alignItems:"center",marginBottom:n},[`${t}-title`]:Object.assign(Object.assign({},f.textEllipsis),{flex:"auto",color:e.titleColor,fontWeight:e.fontWeightStrong,fontSize:e.fontSizeLG,lineHeight:e.lineHeightLG}),[`${t}-extra`]:{marginInlineStart:"auto",color:i,fontSize:e.fontSize},[`${t}-view`]:{width:"100%",borderRadius:e.borderRadiusLG,table:{width:"100%",tableLayout:"fixed",borderCollapse:"collapse"}},[`${t}-row`]:{"> th, > td":{paddingBottom:a,paddingInlineEnd:l},"> th:last-child, > td:last-child":{paddingInlineEnd:0},"&:last-child":{borderBottom:"none","> th, > td":{paddingBottom:0}}},[`${t}-item-label`]:{color:e.labelColor,fontWeight:"normal",fontSize:e.fontSize,lineHeight:e.lineHeight,textAlign:"start","&::after":{content:'":"',position:"relative",top:-.5,marginInline:`${(0,m.unit)(s)} ${(0,m.unit)(r)}`},[`&${t}-item-no-colon::after`]:{content:'""'}},[`${t}-item-no-label`]:{"&::after":{margin:0,content:'""'}},[`${t}-item-content`]:{display:"table-cell",flex:1,color:e.contentColor,fontSize:e.fontSize,lineHeight:e.lineHeight,wordBreak:"break-word",overflowWrap:"break-word"},[`${t}-item`]:{paddingBottom:0,verticalAlign:"top","&-container":{display:"flex",[`${t}-item-label`]:{display:"inline-flex",alignItems:"baseline"},[`${t}-item-content`]:{display:"inline-flex",alignItems:"baseline",minWidth:"1em"}}},"&-middle":{[`${t}-row`]:{"> th, > td":{paddingBottom:e.paddingSM}}},"&-small":{[`${t}-row`]:{"> th, > td":{paddingBottom:e.paddingXS}}}})}})((0,b.mergeToken)(e,{})),e=>({labelBg:e.colorFillAlter,labelColor:e.colorTextTertiary,titleColor:e.colorText,titleMarginBottom:e.fontSizeSM*e.lineHeightSM,itemPaddingBottom:e.padding,itemPaddingEnd:e.padding,colonMarginRight:e.marginXS,colonMarginLeft:e.marginXXS/2,contentColor:e.colorText,extraColor:e.colorText}));var O=function(e,t){var i={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(i[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var l=0,a=Object.getOwnPropertySymbols(e);lt.indexOf(a[l])&&Object.prototype.propertyIsEnumerable.call(e,a[l])&&(i[a[l]]=e[a[l]]);return i};let y=e=>{let A,{prefixCls:g,title:m,extra:f,column:p,colon:b=!0,bordered:y,layout:v,children:E,className:C,rootClassName:I,style:w,size:S,labelStyle:R,contentStyle:L,styles:B,items:k,classNames:_}=e,T=O(e,["prefixCls","title","extra","column","colon","bordered","layout","children","className","rootClassName","style","size","labelStyle","contentStyle","styles","items","classNames"]),{getPrefixCls:j,direction:M,className:$,style:H,classNames:N,styles:P}=(0,l.useComponentConfig)("descriptions"),z=j("descriptions",g),D=(0,s.default)(),U=t.useMemo(()=>{var e;return"number"==typeof p?p:null!=(e=(0,a.matchScreen)(D,Object.assign(Object.assign({},n),p)))?e:3},[D,p]),W=(A=t.useMemo(()=>k||(0,d.default)(E).map(e=>Object.assign(Object.assign({},null==e?void 0:e.props),{key:e.key})),[k,E]),t.useMemo(()=>A.map(e=>{var{span:t}=e,i=c(e,["span"]);return"filled"===t?Object.assign(Object.assign({},i),{filled:!0}):Object.assign(Object.assign({},i),{span:"number"==typeof t?t:(0,a.matchScreen)(D,t)})}),[A,D])),q=(0,r.default)(S),G=((e,i)=>{let[a,l]=(0,t.useMemo)(()=>{let t,a,l,r;return t=[],a=[],l=!1,r=0,i.filter(e=>e).forEach(i=>{let{filled:s}=i,n=u(i,["filled"]);if(s){a.push(n),t.push(a),a=[],r=0;return}let o=e-r;(r+=i.span||1)>=e?(r>e?(l=!0,a.push(Object.assign(Object.assign({},n),{span:o}))):a.push(n),t.push(a),a=[],r=0):a.push(n)}),a.length>0&&t.push(a),[t=t.map(t=>{let i=t.reduce((e,t)=>e+(t.span||1),0);if(i({labelStyle:R,contentStyle:L,styles:{content:Object.assign(Object.assign({},P.content),null==B?void 0:B.content),label:Object.assign(Object.assign({},P.label),null==B?void 0:B.label)},classNames:{label:(0,i.default)(N.label,null==_?void 0:_.label),content:(0,i.default)(N.content,null==_?void 0:_.content)}}),[R,L,B,_,N,P]);return F(t.createElement(o.Provider,{value:K},t.createElement("div",Object.assign({className:(0,i.default)(z,$,N.root,null==_?void 0:_.root,{[`${z}-${q}`]:q&&"default"!==q,[`${z}-bordered`]:!!y,[`${z}-rtl`]:"rtl"===M},C,I,Q,V),style:Object.assign(Object.assign(Object.assign(Object.assign({},H),P.root),null==B?void 0:B.root),w)},T),(m||f)&&t.createElement("div",{className:(0,i.default)(`${z}-header`,N.header,null==_?void 0:_.header),style:Object.assign(Object.assign({},P.header),null==B?void 0:B.header)},m&&t.createElement("div",{className:(0,i.default)(`${z}-title`,N.title,null==_?void 0:_.title),style:Object.assign(Object.assign({},P.title),null==B?void 0:B.title)},m),f&&t.createElement("div",{className:(0,i.default)(`${z}-extra`,N.extra,null==_?void 0:_.extra),style:Object.assign(Object.assign({},P.extra),null==B?void 0:B.extra)},f)),t.createElement("div",{className:`${z}-view`},t.createElement("table",null,t.createElement("tbody",null,G.map((e,i)=>t.createElement(h,{key:i,index:i,colon:b,prefixCls:z,vertical:"vertical"===v,bordered:y,row:e}))))))))};y.Item=({children:e})=>e,e.s(["Descriptions",0,y],869216)},368869,e=>{"use strict";e.i(296059);var t=e.i(868297),i=e.i(732961),a=e.i(289882),l=e.i(170517),r=e.i(628882),s=e.i(320890),n=e.i(104458),o=e.i(722319),d=e.i(8398),c=e.i(279728);e.i(765846);var u=e.i(602716),A=e.i(328052),g=e.i(135551);let h=(e,t)=>new g.FastColor(e).setA(t).toRgbString(),m=(e,t)=>new g.FastColor(e).lighten(t).toHexString(),f=e=>{let t=(0,u.generate)(e,{theme:"dark"});return{1:t[0],2:t[1],3:t[2],4:t[3],5:t[6],6:t[5],7:t[4],8:t[6],9:t[5],10:t[4]}},p=(e,t)=>{let i=e||"#000",a=t||"#fff";return{colorBgBase:i,colorTextBase:a,colorText:h(a,.85),colorTextSecondary:h(a,.65),colorTextTertiary:h(a,.45),colorTextQuaternary:h(a,.25),colorFill:h(a,.18),colorFillSecondary:h(a,.12),colorFillTertiary:h(a,.08),colorFillQuaternary:h(a,.04),colorBgSolid:h(a,.95),colorBgSolidHover:h(a,1),colorBgSolidActive:h(a,.9),colorBgElevated:m(i,12),colorBgContainer:m(i,8),colorBgLayout:m(i,0),colorBgSpotlight:m(i,26),colorBgBlur:h(a,.04),colorBorder:m(i,26),colorBorderSecondary:m(i,19)}},b={defaultSeed:s.defaultConfig.token,useToken:function(){let[e,t,i]=(0,n.useToken)();return{theme:e,token:t,hashId:i}},defaultAlgorithm:o.default,darkAlgorithm:(e,t)=>{let i=Object.keys(l.defaultPresetColors).map(t=>{let i=(0,u.generate)(e[t],{theme:"dark"});return Array.from({length:10},()=>1).reduce((e,a,l)=>(e[`${t}-${l+1}`]=i[l],e[`${t}${l+1}`]=i[l],e),{})}).reduce((e,t)=>e=Object.assign(Object.assign({},e),t),{}),a=null!=t?t:(0,o.default)(e),r=(0,A.default)(e,{generateColorPalettes:f,generateNeutralColorPalettes:p});return Object.assign(Object.assign(Object.assign(Object.assign({},a),i),r),{colorPrimaryBg:r.colorPrimaryBorder,colorPrimaryBgHover:r.colorPrimaryBorderHover})},compactAlgorithm:(e,t)=>{let i=null!=t?t:(0,o.default)(e),a=i.fontSizeSM,l=i.controlHeight-4;return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},i),function(e){let{sizeUnit:t,sizeStep:i}=e,a=i-2;return{sizeXXL:t*(a+10),sizeXL:t*(a+6),sizeLG:t*(a+2),sizeMD:t*(a+2),sizeMS:t*(a+1),size:t*a,sizeSM:t*a,sizeXS:t*(a-1),sizeXXS:t*(a-1)}}(null!=t?t:e)),(0,c.default)(a)),{controlHeight:l}),(0,d.default)(Object.assign(Object.assign({},i),{controlHeight:l})))},getDesignToken:e=>{let s=(null==e?void 0:e.algorithm)?(0,t.createTheme)(e.algorithm):a.default,n=Object.assign(Object.assign({},l.default),null==e?void 0:e.token);return(0,i.getComputedToken)(n,{override:null==e?void 0:e.token},s,r.default)},defaultConfig:s.defaultConfig,_internalContext:s.DesignTokenContext};e.s(["theme",0,b],368869)},127952,e=>{"use strict";var t=e.i(843476),i=e.i(560445),a=e.i(175712),l=e.i(869216),r=e.i(311451),s=e.i(212931),n=e.i(898586),o=e.i(368869),d=e.i(270377),c=e.i(271645);e.s(["default",0,function({isOpen:e,title:u,alertMessage:A,message:g,resourceInformationTitle:h,resourceInformation:m,onCancel:f,onOk:p,confirmLoading:b,requiredConfirmation:x}){let{Title:O,Text:y}=n.Typography,{token:v}=o.theme.useToken(),[E,C]=(0,c.useState)("");return(0,c.useEffect)(()=>{e&&C("")},[e]),(0,t.jsx)(s.Modal,{title:u,open:e,onOk:p,onCancel:f,confirmLoading:b,okText:b?"Deleting...":"Delete",cancelText:"Cancel",okButtonProps:{danger:!0,disabled:!!x&&E!==x||b},cancelButtonProps:{disabled:b},children:(0,t.jsxs)("div",{className:"space-y-4",children:[A&&(0,t.jsx)(i.Alert,{message:A,type:"warning"}),(0,t.jsx)(a.Card,{title:h,className:"mt-4",styles:{body:{padding:"16px"},header:{backgroundColor:v.colorErrorBg,borderColor:v.colorErrorBorder}},style:{backgroundColor:v.colorErrorBg,borderColor:v.colorErrorBorder},children:(0,t.jsx)(l.Descriptions,{column:1,size:"small",children:m&&m.map(({label:e,value:i,...a})=>(0,t.jsx)(l.Descriptions.Item,{label:(0,t.jsx)("span",{className:"font-semibold",children:e}),children:(0,t.jsx)(y,{...a,children:i??"-"})},e))})}),(0,t.jsx)("div",{children:(0,t.jsx)(y,{children:g})}),x&&(0,t.jsxs)("div",{className:"mb-6 mt-4 pt-4 border-t border-gray-200 dark:border-gray-700",children:[(0,t.jsxs)(y,{className:"block text-base font-medium text-gray-700 dark:text-gray-300 mb-2",children:[(0,t.jsx)(y,{children:"Type "}),(0,t.jsx)(y,{strong:!0,type:"danger",children:x}),(0,t.jsx)(y,{children:" to confirm deletion:"})]}),(0,t.jsx)(r.Input,{value:E,onChange:e=>C(e.target.value),placeholder:x,className:"rounded-md",prefix:(0,t.jsx)(d.ExclamationCircleOutlined,{style:{color:v.colorError}}),autoFocus:!0})]})]})})}])},954616,e=>{"use strict";var t=e.i(271645),i=e.i(114272),a=e.i(540143),l=e.i(915823),r=e.i(619273),s=class extends l.Subscribable{#e;#t=void 0;#i;#a;constructor(e,t){super(),this.#e=e,this.setOptions(t),this.bindMethods(),this.#l()}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(e){let t=this.options;this.options=this.#e.defaultMutationOptions(e),(0,r.shallowEqualObjects)(this.options,t)||this.#e.getMutationCache().notify({type:"observerOptionsUpdated",mutation:this.#i,observer:this}),t?.mutationKey&&this.options.mutationKey&&(0,r.hashKey)(t.mutationKey)!==(0,r.hashKey)(this.options.mutationKey)?this.reset():this.#i?.state.status==="pending"&&this.#i.setOptions(this.options)}onUnsubscribe(){this.hasListeners()||this.#i?.removeObserver(this)}onMutationUpdate(e){this.#l(),this.#r(e)}getCurrentResult(){return this.#t}reset(){this.#i?.removeObserver(this),this.#i=void 0,this.#l(),this.#r()}mutate(e,t){return this.#a=t,this.#i?.removeObserver(this),this.#i=this.#e.getMutationCache().build(this.#e,this.options),this.#i.addObserver(this),this.#i.execute(e)}#l(){let e=this.#i?.state??(0,i.getDefaultState)();this.#t={...e,isPending:"pending"===e.status,isSuccess:"success"===e.status,isError:"error"===e.status,isIdle:"idle"===e.status,mutate:this.mutate,reset:this.reset}}#r(e){a.notifyManager.batch(()=>{if(this.#a&&this.hasListeners()){let t=this.#t.variables,i=this.#t.context,a={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};if(e?.type==="success"){try{this.#a.onSuccess?.(e.data,t,i,a)}catch(e){Promise.reject(e)}try{this.#a.onSettled?.(e.data,null,t,i,a)}catch(e){Promise.reject(e)}}else if(e?.type==="error"){try{this.#a.onError?.(e.error,t,i,a)}catch(e){Promise.reject(e)}try{this.#a.onSettled?.(void 0,e.error,t,i,a)}catch(e){Promise.reject(e)}}}this.listeners.forEach(e=>{e(this.#t)})})}},n=e.i(912598);e.s(["useMutation",0,function(e,i){let l=(0,n.useQueryClient)(i),[o]=t.useState(()=>new s(l,e));t.useEffect(()=>{o.setOptions(e)},[o,e]);let d=t.useSyncExternalStore(t.useCallback(e=>o.subscribe(a.notifyManager.batchCalls(e)),[o]),()=>o.getCurrentResult(),()=>o.getCurrentResult()),c=t.useCallback((e,t)=>{o.mutate(e,t).catch(r.noop)},[o]);if(d.error&&(0,r.shouldThrowError)(o.options.throwOnError,[d.error]))throw d.error;return{...d,mutate:c,mutateAsync:d.mutate}}],954616)},107233,603908,e=>{"use strict";let t=(0,e.i(475254).default)("plus",[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]]);e.s(["default",0,t],603908),e.s(["Plus",0,t],107233)},361653,e=>{"use strict";let t=(0,e.i(475254).default)("circle-alert",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"12",x2:"12",y1:"8",y2:"12",key:"1pkeuh"}],["line",{x1:"12",x2:"12.01",y1:"16",y2:"16",key:"4dfq90"}]]);e.s(["default",0,t])},519380,e=>{e.q("/litellm-asset-prefix/_next/static/media/bedrock.2vhamxr8q6y4o.svg")},946013,e=>{e.q("/litellm-asset-prefix/_next/static/media/google.3y8ypywwtqob_.svg")},641266,e=>{e.q("/litellm-asset-prefix/_next/static/media/microsoft_azure.3626-7zx7wf09.svg")},956529,e=>{e.q("/litellm-asset-prefix/_next/static/media/openai_small.3rj8nedgjpevh.svg")},913383,e=>{e.q("/litellm-asset-prefix/_next/static/media/a2a_agent.14el5-6dflh1h.png")},238564,e=>{e.q("/litellm-asset-prefix/_next/static/media/ai21.0m_u-tih8nm0v.svg")},941533,e=>{e.q("/litellm-asset-prefix/_next/static/media/aiml_api.1dq6dbpwklhlg.svg")},994636,e=>{e.q("/litellm-asset-prefix/_next/static/media/anthropic.3s95kgy8jpc64.svg")},824770,e=>{e.q("/litellm-asset-prefix/_next/static/media/assemblyai_small.0w_dslsra91uw.png")},920076,e=>{e.q("/litellm-asset-prefix/_next/static/media/baseten.41k2lnwp3c5g9.svg")},96889,e=>{e.q("/litellm-asset-prefix/_next/static/media/cerebras.1ur1xyfqk9ncz.svg")},348026,e=>{e.q("/litellm-asset-prefix/_next/static/media/cloudflare.2n1spdq7u5yki.svg")},727148,e=>{e.q("/litellm-asset-prefix/_next/static/media/cohere.22i3i2e449j5i.svg")},411703,e=>{e.q("/litellm-asset-prefix/_next/static/media/cometapi.3w0xficbg3dkk.svg")},922480,e=>{e.q("/litellm-asset-prefix/_next/static/media/cursor.1q1ev-_5l7exg.svg")},769490,e=>{e.q("/litellm-asset-prefix/_next/static/media/databricks.2hiet9qlqzn-g.svg")},829789,e=>{e.q("/litellm-asset-prefix/_next/static/media/deepgram.3-krp20p_xed3.png")},41730,e=>{e.q("/litellm-asset-prefix/_next/static/media/deepinfra.3lr_lsr7mhui6.png")},662576,e=>{e.q("/litellm-asset-prefix/_next/static/media/deepseek.3n4cu0x32i_7w.svg")},987202,e=>{e.q("/litellm-asset-prefix/_next/static/media/elevenlabs.2982m_dk2-h_y.png")},953265,e=>{e.q("/litellm-asset-prefix/_next/static/media/fal_ai.3rahrirki8sby.jpg")},346512,e=>{e.q("/litellm-asset-prefix/_next/static/media/featherless.3hmlef3fhc0h5.svg")},84416,e=>{e.q("/litellm-asset-prefix/_next/static/media/fireworks.3t1b6p8edeqyo.svg")},104602,e=>{e.q("/litellm-asset-prefix/_next/static/media/friendli.0ymiswh6l35bq.svg")},888193,e=>{e.q("/litellm-asset-prefix/_next/static/media/github_copilot.3k-8jyadoaq2u.svg")},447500,e=>{e.q("/litellm-asset-prefix/_next/static/media/groq.20csmqzsusp1k.svg")},270039,e=>{e.q("/litellm-asset-prefix/_next/static/media/huggingface.1-07ypt9ii_-p.svg")},227733,e=>{e.q("/litellm-asset-prefix/_next/static/media/hyperbolic.3le20v59sebn8.svg")},823929,e=>{e.q("/litellm-asset-prefix/_next/static/media/infinity.0s9s12bl4lccx.png")},333845,e=>{e.q("/litellm-asset-prefix/_next/static/media/jina.0ukab8o-3-5m_.png")},813878,e=>{e.q("/litellm-asset-prefix/_next/static/media/lambda.26_vz7cmyuodo.svg")},767788,e=>{e.q("/litellm-asset-prefix/_next/static/media/lmstudio.35s3-83mlhcms.svg")},808630,e=>{e.q("/litellm-asset-prefix/_next/static/media/meta_llama.1kxk24vwsem49.svg")},468732,e=>{e.q("/litellm-asset-prefix/_next/static/media/minimax.2mfkkqc-lnsen.svg")},610503,e=>{e.q("/litellm-asset-prefix/_next/static/media/mistral.0n8jv_67hgq4h.svg")},438969,e=>{e.q("/litellm-asset-prefix/_next/static/media/moonshot.3__i9wvf37ksm.svg")},852459,e=>{e.q("/litellm-asset-prefix/_next/static/media/morph.2av06eo-t0-ve.svg")},494696,e=>{e.q("/litellm-asset-prefix/_next/static/media/nebius.2ipf7rmjccira.svg")},148973,e=>{e.q("/litellm-asset-prefix/_next/static/media/novita.0_nzmm_rl4lrf.svg")},570036,e=>{e.q("/litellm-asset-prefix/_next/static/media/nvidia_nim.1fz-5ugf_um0v.svg")},151170,e=>{e.q("/litellm-asset-prefix/_next/static/media/nvidia_triton.1aotoxig_m2w1.png")},862330,e=>{e.q("/litellm-asset-prefix/_next/static/media/ollama.144cif369atc5.svg")},788158,e=>{e.q("/litellm-asset-prefix/_next/static/media/openrouter.1xk7748-_jixf.svg")},350567,e=>{e.q("/litellm-asset-prefix/_next/static/media/oracle.43kws59xxr8ig.svg")},822797,e=>{e.q("/litellm-asset-prefix/_next/static/media/perplexity-ai.2do8hoc8tw__0.svg")},840911,e=>{e.q("/litellm-asset-prefix/_next/static/media/qwen.0a49x9i08_0gz.png")},445735,e=>{e.q("/litellm-asset-prefix/_next/static/media/recraft.2f4cv-c9ad-mo.svg")},635202,e=>{e.q("/litellm-asset-prefix/_next/static/media/replicate.445bidyix2tyh.svg")},669161,e=>{e.q("/litellm-asset-prefix/_next/static/media/runway.2zzwye1fddnnn.png")},159508,e=>{e.q("/litellm-asset-prefix/_next/static/media/sambanova.1vcyu0faw1x8h.svg")},544025,e=>{e.q("/litellm-asset-prefix/_next/static/media/sap.1367hgge0s0xl.png")},100588,e=>{e.q("/litellm-asset-prefix/_next/static/media/snowflake.2_p8qqpi0r3lr.svg")},183804,e=>{e.q("/litellm-asset-prefix/_next/static/media/soniox.3nhjmssy7ybh7.svg")},865502,e=>{e.q("/litellm-asset-prefix/_next/static/media/togetherai.1wk-mouzgw5ho.svg")},96912,e=>{e.q("/litellm-asset-prefix/_next/static/media/topaz.3rjp3zvx4eags.svg")},395073,e=>{e.q("/litellm-asset-prefix/_next/static/media/v0.15trd3tb1ulop.svg")},407829,e=>{e.q("/litellm-asset-prefix/_next/static/media/vercel.1mvnwxofolt8y.svg")},846077,e=>{e.q("/litellm-asset-prefix/_next/static/media/vllm.3_gqby46r7s3x.png")},914861,e=>{e.q("/litellm-asset-prefix/_next/static/media/volcengine.23ly9ik_qc138.png")},285328,e=>{e.q("/litellm-asset-prefix/_next/static/media/voyage.0krq6ew-yr8dk.webp")},779278,e=>{e.q("/litellm-asset-prefix/_next/static/media/watsonx.19rrg39yvhpk8.svg")},325532,e=>{e.q("/litellm-asset-prefix/_next/static/media/xai.2kc3gjiopn9om.svg")},288330,e=>{e.q("/litellm-asset-prefix/_next/static/media/xinference.063cy_ievvy5u.svg")},922158,e=>{"use strict";let t={src:e.i(519380).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,t])},336712,e=>{"use strict";let t={src:e.i(946013).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,t])},39182,e=>{"use strict";let t={src:e.i(641266).default,width:200,height:58,blurWidth:0,blurHeight:0};e.s(["default",0,t])},980385,e=>{"use strict";let t={src:e.i(956529).default,width:28,height:28,blurWidth:0,blurHeight:0};e.s(["default",0,t])},555987,938137,301035,470524,901539,434339,857152,e=>{"use strict";var t=e.i(221688),i=e.i(950643);let a=/^(https?:|data:|blob:|\/\/)/i;e.s(["resolveLogoSrc",0,(e,l=t.serverRootPath)=>{let r;if(!e)return;if(a.test(e)||e.includes("/_next/static/"))return e;let s=(0,i.normalizeRootPath)(l);return s&&(e===s||e.startsWith(`${s}/`))?e:(r=(0,i.normalizeRootPath)(l),`${r}${e.startsWith("/")?e:`/${e}`}`)}],555987);let l={src:e.i(913383).default,width:661,height:768,blurWidth:7,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAICAYAAAA1BOUGAAAAwUlEQVR42h2OvQpGcByFf5dK7oBJFoooJDaDZFA+BoVkNBikGJSUJINyGeft/Q+nznDO00O6rsM0Tfi+z6JpGizLgqqqIEVR0HUdqqqCbdus930PWZZBy7IgCAKkaYpxHJEkCaIowjAMoGmaEMcx8jxHURRo2xZ1XaMsS9B5nrjvG+u6Yp5nfN+H53nwJ9JxHMiyDO/7Mtx1Xey1bRvIcRzwPI993xGGIZqmgSAIMAwDJEkSPM9jphzHsYHruhBFET+hn6S8pf6D7AAAAABJRU5ErkJggg=="};e.s(["default",0,l],938137);let r={src:e.i(238564).default,width:103,height:24,blurWidth:0,blurHeight:0};e.s(["default",0,r],301035);let s={src:e.i(941533).default,width:115,height:24,blurWidth:0,blurHeight:0};e.s(["default",0,s],470524);let n={src:e.i(994636).default,width:46,height:46,blurWidth:0,blurHeight:0};e.s(["default",0,n],901539);let o={src:e.i(824770).default,width:28,height:28,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAlElEQVR42m3OoQqDUBQGYF9mwzLbFiZsMNgwuGa4YFgYLC6PgcXuA5gFzdcgFkExGr33ogZRNIr6CAoKKgo/nAMfh/NTu3u4GWoY+0fIAsIC3C8LuIiRH9S2W5wBXsBPKaHTGGb+lZMJmCe2vPYjZe9/bMDswKMRbq9Yg9VRICcBq3p6FckINIf6o6ECwyOam/1YpwPFUFu+Lt2F2QAAAABJRU5ErkJggg=="};e.s(["default",0,o],434339);let d={src:e.i(920076).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,d],857152)},896614,9774,503119,272896,144923,562171,533881,837957,227247,708889,859320,586455,921117,21296,579967,e=>{"use strict";let t={src:e.i(96889).default,width:800,height:600,blurWidth:0,blurHeight:0};e.s(["default",0,t],896614);let i={src:e.i(348026).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,i],9774);let a={src:e.i(727148).default,width:75,height:75,blurWidth:0,blurHeight:0};e.s(["default",0,a],503119);let l={src:e.i(411703).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,l],272896);let r={src:e.i(922480).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,r],144923);let s={src:e.i(769490).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,s],562171);let n={src:e.i(829789).default,width:32,height:32,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAzklEQVR42k3PPWuDcBDHccUiiikYSwqhg+IkuKUpOLqK4OYrcBZcFCcH352mgy/Ch9mxEPg2+s+Q4W65zx33kyRJQpZlFOUNTdM4HN45Hi1Op0/O56+7tAHbtqmqiqZpKIqCy+Uby/rYkABhGLKuK+M4siwLt9sv1+sPpmkKEAQBwzCQpilxHDNNE3VdYxiGALqu47oujuMQRdEOyrJEVVUBFEUhSRK6rmOeZ/q+x/f97XkBHqfIsoy2bcnzHM/z9mSP2Q62dt/0c+O1/v4BYRxu3LgaR7UAAAAASUVORK5CYII="};e.s(["default",0,n],533881);let o={src:e.i(41730).default,width:225,height:225,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAfklEQVR42nVOvQqDMBjM+7+RpbQ0Ji2NQhwdzGIwCMZFiCAa9STqIPhxw/3A3UeW41w/AKck87w0tqvbngkFgEDCJOPoKc+fovxKzaUGiXkOk0zei1TRzDxYEbHiIw0kzD2Aji5BqHolW9Uv0+9U01AVxivr4r8CjHX7+N27K5WIrP56XsFTAAAAAElFTkSuQmCC"};e.s(["default",0,o],837957);let d={src:e.i(662576).default,width:293,height:215,blurWidth:0,blurHeight:0};e.s(["default",0,d],227247);let c={src:e.i(987202).default,width:1563,height:1563,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA5ElEQVR42h2PPwtBURjGj3wCk93sC1iUCYXFIlGSMjC4QiiTBSE3Ge5M3e0uPoJFBhOTP8NNukXduFL3uJ1Xzz11znCe93me38uitQSL11Pe8rAanKpycbSa5Ar9UiAmJT3QXHGwHGfvj/vJcRzOfz/7fLvsW4tuJFZLehicEIUQpOs6GYZBOIfrcZPp5f0MsXDatk2yLJOqqu6A9f2YjXk7zNCJWM45KYpCmqa5A6/P+ynNGiEGIHSiwjRNsiyLBAmxPezW6U7Gx0ALIHQiFk6I5WEl6G6BB7QAQqc0a4bgxD/uH4FYoVec8m3dAAAAAElFTkSuQmCC"};e.s(["default",0,c],708889);let u={src:e.i(953265).default,width:400,height:400,blurWidth:8,blurHeight:8,blurDataURL:"data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/wAARCAAIAAgDAREAAhEBAxEB/9sAQwAKBwcIBwYKCAgICwoKCw4YEA4NDQ4dFRYRGCMfJSQiHyIhJis3LyYpNCkhIjBBMTQ5Oz4+PiUuRElDPEg3PT47/9sAQwEKCwsODQ4cEBAcOygiKDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDtP9N/tL+PO/8A7ZeV/wDFUj6L917L+r3/AMj/2Q=="};e.s(["default",0,u],859320);let A={src:e.i(346512).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,A],586455);let g={src:e.i(84416).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,g],921117);let h={src:e.i(104602).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,h],21296);let m={src:e.i(888193).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,m],579967)},770752,383963,862493,902860,901372,206258,176228,728685,e=>{"use strict";let t={src:e.i(447500).default,width:26,height:26,blurWidth:0,blurHeight:0};e.s(["default",0,t],770752);let i={src:e.i(270039).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,i],383963);let a={src:e.i(227733).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,a],862493);let l={src:e.i(823929).default,width:128,height:129,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAApklEQVR42oWPsQrCMBRFY51KaUuwNktBQbcgSenQ32hCTBAHXfSfulUK4g/0/7yvdO+FB5dzXhLC2FoiJE3TfZ7nYrskyzIBVpBjTdPcu677GGPGtm1fmDd1YnVd31gIYSrLUnLOjxC9tfaLfhBCXLz307xQVZUCODvnfljoceAEpskxKeUV1w0Qo1LqobV+Lk8McI5tkDiOeZIkO9SIBr0gRm71l38QASaqNz7VMwAAAABJRU5ErkJggg=="};e.s(["default",0,l],902860);let r={src:e.i(333845).default,width:200,height:200,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAXUlEQVR42p2MoRHAMAwDQwJzCcgCwQEZIDjA03gED2BzewSPWRe2qFcB3Z+kU0o/lXNea4U/0t77OYeIACDq4FLKnSKimYmIqjKzu885U6117z3GCGitxTbg/flJF7DAEmcR3WAqAAAAAElFTkSuQmCC"};e.s(["default",0,r],901372);let s={src:e.i(813878).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,s],206258);let n={src:e.i(767788).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,n],176228);let o={src:e.i(808630).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,o],728685)},272967,551726,399495,740876,709103,277207,836473,768493,297720,e=>{"use strict";let t={src:e.i(468732).default,width:490,height:412,blurWidth:0,blurHeight:0};e.s(["default",0,t],272967);let i={src:e.i(610503).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,i],551726);let a={src:e.i(438969).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,a],399495);let l={src:e.i(852459).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,l],740876);let r={src:e.i(494696).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,r],709103);let s={src:e.i(148973).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,s],277207);let n={src:e.i(570036).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,n],836473);let o={src:e.i(151170).default,width:95,height:81,blurWidth:8,blurHeight:7,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAHCAYAAAA1WQxeAAAA0UlEQVR42kXMvWrCUBjG8VxIL6KX0IvobXQSOrQd2kLSdMzQdMmaEErPUqoIIYtKOC5iFD9BEBwiaAY/AgpH/2oUfOGFh4cfj8blNmrNKK0zmFUZzmust2nea7u9IlkNkBMPv1Xgs3JHoXRDe1o+g8Vmyl/vnf++wU/7EdF54jW8JU6K14WTPgE3fjiCZ77kPa2kdAZZlrFcLpikXYLeN6Lxxm/zhfEsRimF5roupmli2zbGh4Gu61iWlX8URWhSSsIwxPM8hBA4jpNn3/cJgoADqC23HJzwo6wAAAAASUVORK5CYII="};e.s(["default",0,o],768493);let d={src:e.i(862330).default,width:646,height:854,blurWidth:0,blurHeight:0};e.s(["default",0,d],297720)},916925,247044,e=>{"use strict";var t,i=e.i(555987),a=e.i(938137),l=e.i(301035),r=e.i(470524),s=e.i(901539),n=e.i(434339),o=e.i(857152),d=e.i(922158),c=e.i(896614),u=e.i(9774),A=e.i(503119),g=e.i(272896),h=e.i(144923),m=e.i(562171),f=e.i(533881),p=e.i(837957),b=e.i(227247),x=e.i(708889),O=e.i(859320),y=e.i(586455),v=e.i(921117),E=e.i(21296),C=e.i(579967),I=e.i(336712),w=e.i(770752),S=e.i(383963),R=e.i(862493),L=e.i(902860),B=e.i(901372),k=e.i(206258),_=e.i(176228),T=e.i(728685),j=e.i(39182),M=e.i(272967),$=e.i(551726),H=e.i(399495),N=e.i(740876),P=e.i(709103),z=e.i(277207),D=e.i(836473),U=e.i(768493),W=e.i(297720),q=e.i(980385);let G={src:e.i(788158).default,width:300,height:300,blurWidth:0,blurHeight:0},F={src:e.i(350567).default,width:231,height:30,blurWidth:0,blurHeight:0},Q={src:e.i(822797).default,width:48,height:48,blurWidth:0,blurHeight:0},V={src:e.i(840911).default,width:1024,height:1024,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAABE0lEQVR42gEIAff+AAAAAAAAAAAAEQwmIT8ukIAgGUpBBwYQDQQECggAAAAAAAIBBQQJBhUTFw80LUIxloZbR8+5UUS3oUA4kH0HBxEOACMUUkolFlVNCAYTERwUPzgkHFJIIhxNRCsmYVQODSAbADYffnNFKaCSDgkgHD0tjX4+MI5+CQgVEhQSLCULChgUABAKJiJTM7+vJhlYTzEkb2MxJm9iIh1NQywnZFYhH0o/AA8JIh9RMrurNiR9cQkHFhMQDSUgUkW5o1hOx60pJlxPAAcEDw4qGmJYFw80LhENKCMyKHFjUEK0nhEOJSADAwYFAAAAAAADAgcGBQMLCRUPMClCNJeEIRtLQgAAAQAAAAAAfjQxsiCQipMAAAAASUVORK5CYII="},K={src:e.i(445735).default,width:16,height:16,blurWidth:0,blurHeight:0},Y={src:e.i(635202).default,width:16,height:16,blurWidth:0,blurHeight:0},J={src:e.i(669161).default,width:320,height:320,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA7UlEQVR42lVQvc7BYBR+B/kM3w+fFm2p9m3fRhFRP7UQlTAQCVfAgE3EHZAQMTBIzBIrYbEgFhaza3ABrsF7bIYnOTl5/s5BVrvIY5MMvYTbUuy8hN8CcFIdW62CgHCKjFwie/So3PqH+b0Hc+GebkW6Lj9zUtJaHwEbFvGqWf7zOG5GJVmjcwUEAUOevQmhfKSTqJol+7f94RT+L7Yv25OV3PtsI699EByc85qpW5qcUCaMyB7orH9EgJLaTnPNgk/QfUsxKi0QtKWFzjRzA7a08IEPCivYKWkyQHAKtJUMPIVMQCAmz3FKHcELXnxEPy9E1lYCAAAAAElFTkSuQmCC"},X={src:e.i(159508).default,width:52,height:46,blurWidth:0,blurHeight:0},Z={src:e.i(544025).default,width:3300,height:2550,blurWidth:8,blurHeight:6,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAYAAAD+Bd/7AAAAZklEQVR42oWNTQ5AMBhE3f8mEhux4BgiIRzAwoKWplrt146fBbVhkpnNvORF+El0jvMAV4RR2qtsJZD3DyA2h6xhiMsRSTUhrRm0DYBFE4qOI285yl5CGvdW0OEYhEE/G6jgvIGv7A4KupBwIVaNAAAAAElFTkSuQmCC"},ee={src:e.i(100588).default,width:146,height:139,blurWidth:0,blurHeight:0};e.s(["default",0,ee],247044);let et={src:e.i(183804).default,width:100,height:18,blurWidth:0,blurHeight:0},ei={src:e.i(865502).default,width:32,height:32,blurWidth:0,blurHeight:0},ea={src:e.i(96912).default,width:16,height:16,blurWidth:0,blurHeight:0},el={src:e.i(395073).default,width:16,height:16,blurWidth:0,blurHeight:0},er={src:e.i(407829).default,width:16,height:16,blurWidth:0,blurHeight:0},es={src:e.i(846077).default,width:64,height:64,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAo0lEQVR42lWOvQrCQBCE7718oUA6wVcQBF/AXhu1CIgcpgoi/qCNguBPwCaChYHDv9zt3e6ZeEFxq9n5GGaY/T8iq3QhWKFRkZGWzEPaaE/RDoiI5e7z0BILP7tMB2vyO3p2BJew8syvvJLGQTPEaheSVJeAQIi5F2+CWt80uHopLMEnNBpPuNc2vaXK+38A4RauTvUhbBNwDvsOlYD3DA2Se99kLK7hC5QFVQAAAABJRU5ErkJggg=="},en={src:e.i(914861).default,width:1024,height:1024,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA7ElEQVR42l2LMUsCAQBG39GFFQ02ad1oq0QIRR2hER4EGdQqdBDdQZAFEudU0NZSQ0O1VUOjiuJvEBxc3BQFEVQQdFAU9OROFwf9lg/e48Hiti8vWJekBSoIODacLK04CP79IAX8815cW2X3IYL3JIhaKuKLGdNGmMUCTo+Hq1yO72yB6HDEeTrFjm8fUVwGt3uL00eD5+6AfMvixbS5bTR5/43jcm2CooT4KNf56ltUGzafA5uoZZGpdJDlABhPr7z1TJLtMbXamMT0702T//aQu0gM5GOFvRuNkKoTDuucqRpeXePoWuPg0D8BGkpJjYhKXN4AAAAASUVORK5CYII="},eo={src:e.i(285328).default,width:400,height:399,blurWidth:8,blurHeight:8,blurDataURL:"data:image/webp;base64,UklGRgwBAABXRUJQVlA4TAABAAAvB8ABEM1VICICHggIDgIAAIDzDAACD0FoAAARAAwAAAAAAAAAAAAAAACAAABAAcBCAoAWgGyoXwUAAA8E7QQCAADg/N+nrsYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4CClZXkg5AQAAAAA538LAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL0HIgEAAAAAcP4ZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMsHA39Qutr2AtkROu8CASWZ/nMlNyzz5AdgWmkK65oUI6AlgIkY/M3zxIW3iABq15KT/Uz7BXidCOONHieAz1v6HqAC"},ed={src:e.i(779278).default,width:16,height:16,blurWidth:0,blurHeight:0},ec={src:e.i(325532).default,width:1e3,height:1e3,blurWidth:0,blurHeight:0},eu={src:e.i(288330).default,width:16,height:16,blurWidth:0,blurHeight:0};var eA=((t={}).A2A_Agent="A2A Agent",t.AI21="Ai21",t.AI21_CHAT="Ai21 Chat",t.AIML="AI/ML API",t.AIOHTTP_OPENAI="Aiohttp Openai",t.Anthropic="Anthropic",t.ANTHROPIC_TEXT="Anthropic Text",t.AssemblyAI="AssemblyAI",t.AUTO_ROUTER="Auto Router",t.Bedrock="Amazon Bedrock",t.BedrockMantle="Amazon Bedrock Mantle",t.SageMaker="AWS SageMaker",t.Azure="Azure",t.Azure_AI_Studio="Azure AI Foundry (Studio)",t.AZURE_TEXT="Azure Text",t.BASETEN="Baseten",t.BYTEZ="Bytez",t.Cerebras="Cerebras",t.CLARIFAI="Clarifai",t.CLOUDFLARE="Cloudflare",t.CODESTRAL="Codestral",t.Cohere="Cohere",t.COHERE_CHAT="Cohere Chat",t.COMETAPI="Cometapi",t.COMPACTIFAI="Compactifai",t.Cursor="Cursor",t.Dashscope="Dashscope",t.Databricks="Databricks (Qwen API)",t.DATAROBOT="Datarobot",t.DeepInfra="DeepInfra",t.Deepgram="Deepgram",t.Deepseek="Deepseek",t.DOCKER_MODEL_RUNNER="Docker Model Runner",t.DOTPROMPT="Dotprompt",t.ElevenLabs="ElevenLabs",t.EMPOWER="Empower",t.FalAI="Fal AI",t.FEATHERLESS_AI="Featherless Ai",t.FireworksAI="Fireworks AI",t.FRIENDLIAI="Friendliai",t.GALADRIEL="Galadriel",t.GITHUB_COPILOT="Github Copilot",t.Google_AI_Studio="Google AI Studio",t.GradientAI="GradientAI",t.Groq="Groq",t.HEROKU="Heroku",t.Hosted_Vllm="vllm",t.HUGGINGFACE="Huggingface",t.HYPERBOLIC="Hyperbolic",t.Infinity="Infinity",t.JinaAI="Jina AI",t.LAMBDA_AI="Lambda Ai",t.LEMONADE="Lemonade",t.LLAMAFILE="Llamafile",t.LM_STUDIO="Lm Studio",t.LLAMA="Meta Llama",t.MARITALK="Maritalk",t.MiniMax="MiniMax",t.MistralAI="Mistral AI",t.MOONSHOT="Moonshot",t.MORPH="Morph",t.NEBIUS="Nebius",t.NLP_CLOUD="Nlp Cloud",t.NOVITA="Novita",t.NSCALE="Nscale",t.NVIDIA_NIM="Nvidia Nim",t.Ollama="Ollama",t.OLLAMA_CHAT="Ollama Chat",t.OOBABOOGA="Oobabooga",t.OpenAI="OpenAI",t.OPENAI_LIKE="Openai Like",t.OpenAI_Compatible="OpenAI-Compatible Chat Completions (Together AI, vLLM, etc.)",t.OpenAI_Text="OpenAI Text Completion",t.OpenAI_Text_Compatible="OpenAI-Compatible Completions (legacy /v1/completions)",t.Openrouter="Openrouter",t.Oracle="Oracle Cloud Infrastructure (OCI)",t.OVHCLOUD="Ovhcloud",t.Perplexity="Perplexity",t.PETALS="Petals",t.PG_VECTOR="Pg Vector",t.PREDIBASE="Predibase",t.RECRAFT="Recraft",t.REPLICATE="Replicate",t.RunwayML="RunwayML",t.SAGEMAKER_LEGACY="Sagemaker",t.Sambanova="Sambanova",t.SAP="SAP Generative AI Hub",t.Snowflake="Snowflake",t.Soniox="Soniox",t.TEXT_COMPLETION_CODESTRAL="Text-Completion-Codestral",t.TogetherAI="TogetherAI",t.TOPAZ="Topaz",t.Triton="Triton",t.V0="V0",t.VERCEL_AI_GATEWAY="Vercel Ai Gateway",t.Vertex_AI="Vertex AI (Anthropic, Gemini, etc.)",t.VERTEX_AI_BETA="Vertex Ai Beta",t.VLLM="Vllm",t.VolcEngine="VolcEngine",t.Voyage="Voyage AI",t.WANDB="Wandb",t.WATSONX="Watsonx",t.WATSONX_TEXT="Watsonx Text",t.xAI="xAI",t.XINFERENCE="Xinference",t.ZAI="Z.AI (Zhipu AI)",t);let eg={A2A_Agent:"a2a_agent",AI21:"ai21",AI21_CHAT:"ai21_chat",AIML:"aiml",AIOHTTP_OPENAI:"aiohttp_openai",Anthropic:"anthropic",ANTHROPIC_TEXT:"anthropic_text",AssemblyAI:"assemblyai",AUTO_ROUTER:"auto_router",Azure:"azure",Azure_AI_Studio:"azure_ai",AZURE_TEXT:"azure_text",BASETEN:"baseten",Bedrock:"bedrock",BedrockMantle:"bedrock_mantle",BYTEZ:"bytez",Cerebras:"cerebras",CLARIFAI:"clarifai",CLOUDFLARE:"cloudflare",CODESTRAL:"codestral",Cohere:"cohere",COHERE_CHAT:"cohere_chat",COMETAPI:"cometapi",COMPACTIFAI:"compactifai",Cursor:"cursor",Dashscope:"dashscope",Databricks:"databricks",DATAROBOT:"datarobot",DeepInfra:"deepinfra",Deepgram:"deepgram",Deepseek:"deepseek",DOCKER_MODEL_RUNNER:"docker_model_runner",DOTPROMPT:"dotprompt",ElevenLabs:"elevenlabs",EMPOWER:"empower",FalAI:"fal_ai",FEATHERLESS_AI:"featherless_ai",FireworksAI:"fireworks_ai",FRIENDLIAI:"friendliai",GALADRIEL:"galadriel",GITHUB_COPILOT:"github_copilot",Google_AI_Studio:"gemini",GradientAI:"gradient_ai",Groq:"groq",HEROKU:"heroku",Hosted_Vllm:"hosted_vllm",HUGGINGFACE:"huggingface",HYPERBOLIC:"hyperbolic",Infinity:"infinity",JinaAI:"jina_ai",LAMBDA_AI:"lambda_ai",LEMONADE:"lemonade",LLAMAFILE:"llamafile",LLAMA:"meta_llama",LM_STUDIO:"lm_studio",MARITALK:"maritalk",MiniMax:"minimax",MistralAI:"mistral",MOONSHOT:"moonshot",MORPH:"morph",NEBIUS:"nebius",NLP_CLOUD:"nlp_cloud",NOVITA:"novita",NSCALE:"nscale",NVIDIA_NIM:"nvidia_nim",Ollama:"ollama",OLLAMA_CHAT:"ollama_chat",OOBABOOGA:"oobabooga",OpenAI:"openai",OPENAI_LIKE:"openai_like",OpenAI_Compatible:"openai",OpenAI_Text:"text-completion-openai",OpenAI_Text_Compatible:"text-completion-openai",Openrouter:"openrouter",Oracle:"oci",OVHCLOUD:"ovhcloud",Perplexity:"perplexity",PETALS:"petals",PG_VECTOR:"pg_vector",PREDIBASE:"predibase",RECRAFT:"recraft",REPLICATE:"replicate",RunwayML:"runwayml",SAGEMAKER_LEGACY:"sagemaker",SageMaker:"sagemaker_chat",Sambanova:"sambanova",SAP:"sap",Snowflake:"snowflake",Soniox:"soniox",TEXT_COMPLETION_CODESTRAL:"text-completion-codestral",TogetherAI:"together_ai",TOPAZ:"topaz",Triton:"triton",V0:"v0",VERCEL_AI_GATEWAY:"vercel_ai_gateway",Vertex_AI:"vertex_ai",VERTEX_AI_BETA:"vertex_ai_beta",VLLM:"vllm",VolcEngine:"volcengine",Voyage:"voyage",WANDB:"wandb",WATSONX:"watsonx",WATSONX_TEXT:"watsonx_text",xAI:"xai",XINFERENCE:"xinference",ZAI:"zai"},eh=new Set(["bedrock_mantle"]),em={"A2A Agent":a.default.src,Ai21:l.default.src,"Ai21 Chat":l.default.src,"AI/ML API":r.default.src,"Aiohttp Openai":q.default.src,Anthropic:s.default.src,"Anthropic Text":s.default.src,AssemblyAI:n.default.src,Azure:j.default.src,"Azure AI Foundry (Studio)":j.default.src,"Azure Text":j.default.src,Baseten:o.default.src,"Amazon Bedrock":d.default.src,"Amazon Bedrock Mantle":d.default.src,"AWS SageMaker":d.default.src,Cerebras:c.default.src,Cloudflare:u.default.src,Codestral:$.default.src,Cohere:A.default.src,"Cohere Chat":A.default.src,Cometapi:g.default.src,Cursor:h.default.src,"Databricks (Qwen API)":m.default.src,Dashscope:V.src,Deepseek:b.default.src,Deepgram:f.default.src,DeepInfra:p.default.src,ElevenLabs:x.default.src,"Fal AI":O.default.src,"Featherless Ai":y.default.src,"Fireworks AI":v.default.src,Friendliai:E.default.src,"Github Copilot":C.default.src,"Google AI Studio":I.default.src,Groq:w.default.src,vllm:es.src,Huggingface:S.default.src,Hyperbolic:R.default.src,Infinity:L.default.src,"Jina AI":B.default.src,"Lambda Ai":k.default.src,"Lm Studio":_.default.src,"Meta Llama":T.default.src,MiniMax:M.default.src,"Mistral AI":$.default.src,Moonshot:H.default.src,Morph:N.default.src,Nebius:P.default.src,Novita:z.default.src,"Nvidia Nim":D.default.src,Ollama:W.default.src,"Ollama Chat":W.default.src,Oobabooga:q.default.src,OpenAI:q.default.src,"Openai Like":q.default.src,"OpenAI Text Completion":q.default.src,"OpenAI-Compatible Completions (legacy /v1/completions)":q.default.src,"OpenAI-Compatible Chat Completions (Together AI, vLLM, etc.)":q.default.src,Openrouter:G.src,"Oracle Cloud Infrastructure (OCI)":F.src,Perplexity:Q.src,Recraft:K.src,Replicate:Y.src,RunwayML:J.src,Sagemaker:d.default.src,Sambanova:X.src,"SAP Generative AI Hub":Z.src,Snowflake:ee.src,Soniox:et.src,"Text-Completion-Codestral":$.default.src,TogetherAI:ei.src,Topaz:ea.src,Triton:U.default.src,V0:el.src,"Vercel Ai Gateway":er.src,"Vertex AI (Anthropic, Gemini, etc.)":I.default.src,"Vertex Ai Beta":I.default.src,Vllm:es.src,VolcEngine:en.src,"Voyage AI":eo.src,Watsonx:ed.src,"Watsonx Text":ed.src,xAI:ec.src,Xinference:eu.src};e.s(["Providers",()=>eA,"getPlaceholder",0,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)return"claude-3-opus";if("Amazon Bedrock"==e)return"claude-3-opus";if("AWS SageMaker"==e)return"sagemaker/jumpstart-dft-meta-textgeneration-llama-2-7b";else if("Google AI Studio"==e)return"gemini-pro";else if("Azure AI Foundry (Studio)"==e)return"azure_ai/command-r-plus";else if("Azure"==e)return"my-deployment";else if("Oracle Cloud Infrastructure (OCI)"==e)return"oci/xai.grok-4";else if("Snowflake"==e)return"snowflake/mistral-7b";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 if("Fal AI"==e)return"fal_ai/fal-ai/flux-pro/v1.1-ultra";else if("RunwayML"==e)return"runwayml/gen4_turbo";else if("Watsonx"===e)return"watsonx/ibm/granite-3-3-8b-instruct";else if("Cursor"===e)return"cursor/claude-4-sonnet";else if("Z.AI (Zhipu AI)"===e)return"zai/glm-4.5";else return"gpt-3.5-turbo"},"getProviderLogoAndName",0,e=>{if(!e)return{logo:"",displayName:"-"};if("gemini"===e.toLowerCase()){let e="Google AI Studio";return{logo:(0,i.resolveLogoSrc)(em[e])??"",displayName:e}}let t=Object.keys(eg).find(t=>eg[t].toLowerCase()===e.toLowerCase())??Object.keys(eg).find(t=>t.toLowerCase()===e.toLowerCase());if(!t)return{logo:"",displayName:e};let a=eA[t];return{logo:(0,i.resolveLogoSrc)(em[a])??"",displayName:a}},"getProviderModels",0,(e,t)=>{let i=eg[e],a=[];return e&&"object"==typeof t&&(Object.entries(t).forEach(([e,t])=>{if(null!==t&&"object"==typeof t&&"litellm_provider"in t){let l=t.litellm_provider,r="string"==typeof l&&(l.startsWith(`${i}_`)||l.startsWith(`${i}-`));(l===i||r&&!eh.has(l))&&a.push(e)}}),"Cohere"==e&&Object.entries(t).forEach(([e,t])=>{null!==t&&"object"==typeof t&&"litellm_provider"in t&&"cohere_chat"===t.litellm_provider&&a.push(e)}),"AWS SageMaker"==e&&Object.entries(t).forEach(([e,t])=>{null!==t&&"object"==typeof t&&"litellm_provider"in t&&"sagemaker_chat"===t.litellm_provider&&a.push(e)})),a},"providerLogoMap",0,em,"provider_map",0,eg],916925)},174553,e=>{"use strict";var t=e.i(843476),i=e.i(271645),a=e.i(916925),l=e.i(555987);e.s(["Logo",0,({provider:e,src:r,label:s,className:n="w-4 h-4"})=>{let[o,d]=(0,i.useState)(null),c=void 0!==e?(0,a.getProviderLogoAndName)(e).logo:(0,l.resolveLogoSrc)(r)??"",u=s??e??"";return o!==c&&c?(0,t.jsx)("img",{src:c,alt:`${u||"-"} logo`,className:n,onError:()=>{console.warn(`Logo failed to load: ${c}`),d(c)}}):(0,t.jsx)("div",{className:`${n} rounded-full bg-gray-200 flex items-center justify-center text-xs`,children:u.charAt(0)||"-"})}])},646563,e=>{"use strict";var t=e.i(959013);e.s(["PlusOutlined",()=>t.default])}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/046q5lwe95zp6.js b/litellm/proxy/_experimental/out/_next/static/chunks/046q5lwe95zp6.js deleted file mode 100644 index 947a1f5f744..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/046q5lwe95zp6.js +++ /dev/null @@ -1 +0,0 @@ -(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,727612,e=>{"use strict";let t=(0,e.i(475254).default)("trash-2",[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}],["line",{x1:"10",x2:"10",y1:"11",y2:"17",key:"1uufr5"}],["line",{x1:"14",x2:"14",y1:"11",y2:"17",key:"xtxkd"}]]);e.s(["Trash2",0,t],727612)},541071,373488,e=>{"use strict";let t=(0,e.i(475254).default)("ellipsis",[["circle",{cx:"12",cy:"12",r:"1",key:"41hilf"}],["circle",{cx:"19",cy:"12",r:"1",key:"1wjl8i"}],["circle",{cx:"5",cy:"12",r:"1",key:"1pcz8c"}]]);e.s(["default",0,t],373488),e.s(["MoreHorizontal",0,t],541071)},755146,e=>{"use strict";var t=e.i(843476),r=e.i(451512),a=e.i(115504);e.i(233565),e.i(678784),e.s(["DropdownMenu",0,function({...e}){return(0,t.jsx)(r.Menu.Root,{"data-slot":"dropdown-menu",...e})},"DropdownMenuContent",0,function({align:e="start",alignOffset:n=0,side:s="bottom",sideOffset:i=4,className:l,...o}){return(0,t.jsx)(r.Menu.Portal,{children:(0,t.jsx)(r.Menu.Positioner,{className:"isolate z-50 outline-none",align:e,alignOffset:n,side:s,sideOffset:i,children:(0,t.jsx)(r.Menu.Popup,{"data-slot":"dropdown-menu-content",className:(0,a.cn)("z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-md bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95",l),...o})})})},"DropdownMenuItem",0,function({className:e,inset:n,variant:s="default",...i}){return(0,t.jsx)(r.Menu.Item,{"data-slot":"dropdown-menu-item","data-inset":n,"data-variant":s,className:(0,a.cn)("group/dropdown-menu-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",e),...i})},"DropdownMenuSeparator",0,function({className:e,...n}){return(0,t.jsx)(r.Menu.Separator,{"data-slot":"dropdown-menu-separator",className:(0,a.cn)("-mx-1 my-1 h-px bg-border",e),...n})},"DropdownMenuTrigger",0,function({...e}){return(0,t.jsx)(r.Menu.Trigger,{"data-slot":"dropdown-menu-trigger",...e})}])},788699,e=>{"use strict";let t=(0,e.i(475254).default)("pencil",[["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",key:"1a8usu"}],["path",{d:"m15 5 4 4",key:"1mk7zo"}]]);e.s(["Pencil",0,t],788699)},118366,e=>{"use strict";var t=e.i(991124);e.s(["CopyIcon",()=>t.default])},21548,e=>{"use strict";var t=e.i(616303);e.s(["Empty",()=>t.default])},655063,e=>{"use strict";var t=e.i(399029),r=e.i(271645);e.s(["useDebouncedValue",0,function(e,a,n){let[s,i,l]=(0,t.useDebouncedState)(e,a,n);return(0,r.useEffect)(()=>{i(e)},[e,i]),[s,l]}])},624687,e=>{"use strict";var t=e.i(843476),r=e.i(271645),a=e.i(115504);let n=r.forwardRef(({className:e,...r},n)=>(0,t.jsx)("textarea",{ref:n,"data-slot":"textarea",className:(0,a.cn)("flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",e),...r}));n.displayName="Textarea",e.s(["Textarea",0,n])},950594,e=>{"use strict";var t=e.i(843476),r=e.i(271645),a=e.i(115504),n=e.i(519455),s=e.i(793479),i=e.i(624687);let l=(0,a.cva)({base:"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",variants:{align:{"inline-start":"order-first pl-2 has-[>button]:-ml-1 has-[>kbd]:ml-[-0.15rem]","inline-end":"order-last pr-2 has-[>button]:-mr-1 has-[>kbd]:mr-[-0.15rem]","block-start":"order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2","block-end":"order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2"}},defaultVariants:{align:"inline-start"}}),o=(0,a.cva)({base:"flex items-center gap-2 text-sm shadow-none",variants:{size:{xs:"h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",sm:"","icon-xs":"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0","icon-sm":"size-8 p-0 has-[>svg]:p-0"}},defaultVariants:{size:"xs"}}),d=r.forwardRef(({className:e,type:r="button",variant:s="ghost",size:i="xs",...l},d)=>(0,t.jsx)(n.Button,{ref:d,type:r,"data-size":i,variant:s,className:(0,a.cn)(o({size:i}),e),...l}));d.displayName="InputGroupButton";let u=r.forwardRef(({className:e,...r},n)=>(0,t.jsx)(s.Input,{ref:n,"data-slot":"input-group-control",className:(0,a.cn)("flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",e),...r}));u.displayName="InputGroupInput",r.forwardRef(({className:e,...r},n)=>(0,t.jsx)(i.Textarea,{ref:n,"data-slot":"input-group-control",className:(0,a.cn)("flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",e),...r})).displayName="InputGroupTextarea",e.s(["InputGroup",0,function({className:e,...r}){return(0,t.jsx)("div",{"data-slot":"input-group",role:"group",className:(0,a.cn)("group/input-group relative flex h-9 w-full min-w-0 items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",e),...r})},"InputGroupAddon",0,function({className:e,align:r="inline-start",...n}){return(0,t.jsx)("div",{role:"group","data-slot":"input-group-addon","data-align":r,className:(0,a.cn)(l({align:r}),e),onClick:e=>{e.target.closest("button")||e.currentTarget.parentElement?.querySelector("input")?.focus()},...n})},"InputGroupButton",0,d,"InputGroupInput",0,u,"InputGroupText",0,function({className:e,...r}){return(0,t.jsx)("span",{className:(0,a.cn)("flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",e),...r})}])},552546,e=>{"use strict";var t=e.i(843476),r=e.i(131792);let a=(e,t)=>{let r=t.trim().toLowerCase();return!r||e.label.toLowerCase().includes(r)||(e.sublabel?.toLowerCase().includes(r)??!1)};e.s(["SearchSelect",0,function({options:e,value:n,onValueChange:s,placeholder:i="Select…",emptyText:l="No results",disabled:o=!1,className:d}){let u=e.find(e=>e.value===n)??null;return(0,t.jsxs)(r.Combobox,{items:e,value:u,onValueChange:e=>s(e?.value??""),isItemEqualToValue:(e,t)=>e.value===t.value,itemToStringLabel:e=>e.label,filter:a,disabled:o,children:[(0,t.jsx)(r.ComboboxInput,{placeholder:i,showClear:null!=n&&""!==n,className:`w-full ${d??""}`}),(0,t.jsxs)(r.ComboboxContent,{children:[(0,t.jsx)(r.ComboboxEmpty,{children:l}),(0,t.jsx)(r.ComboboxList,{children:e=>(0,t.jsx)(r.ComboboxItem,{value:e,children:(0,t.jsxs)("span",{className:"flex min-w-0 flex-col",children:[(0,t.jsx)("span",{className:"truncate",children:e.label}),null!=e.sublabel&&""!==e.sublabel&&(0,t.jsx)("span",{className:"truncate text-xs text-muted-foreground",children:e.sublabel})]})},e.value)})]})]})}])},751904,e=>{"use strict";var t=e.i(401361);e.s(["EditOutlined",()=>t.default])},263005,e=>{"use strict";var t=e.i(843476);e.s(["PageHeader",0,function({title:e,subtitle:r,icon:a,actions:n}){return(0,t.jsxs)("div",{className:"flex flex-wrap items-start justify-between gap-4",children:[(0,t.jsxs)("div",{className:"flex items-center gap-2.5",children:[null!=a&&(0,t.jsx)("span",{className:"flex flex-none items-center text-foreground",children:a}),(0,t.jsxs)("div",{className:"min-w-0",children:[(0,t.jsx)("h1",{className:"text-xl font-semibold tracking-tight text-foreground",children:e}),null!=r&&(0,t.jsx)("p",{className:"mt-0.5 text-sm text-muted-foreground",children:r})]})]}),null!=n&&(0,t.jsx)("div",{className:"flex items-center gap-2",children:n})]})}])},91979,e=>{"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={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"};var n=e.i(9583),s=r.forwardRef(function(e,s){return r.createElement(n.default,(0,t.default)({},e,{ref:s,icon:a}))});e.s(["ReloadOutlined",0,s],91979)},987432,e=>{"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={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"};var n=e.i(9583),s=r.forwardRef(function(e,s){return r.createElement(n.default,(0,t.default)({},e,{ref:s,icon:a}))});e.s(["SaveOutlined",0,s],987432)},564897,e=>{"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={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"};var n=e.i(9583),s=r.forwardRef(function(e,s){return r.createElement(n.default,(0,t.default)({},e,{ref:s,icon:a}))});e.s(["MinusCircleOutlined",0,s],564897)},822315,(e,t,r)=>{e.e,t.exports=function(){"use strict";var e="millisecond",t="second",r="minute",a="hour",n="week",s="month",i="quarter",l="year",o="date",d="Invalid Date",u=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,c=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,m=function(e,t,r){var a=String(e);return!a||a.length>=t?e:""+Array(t+1-a.length).join(r)+e},f="en",h={};h[f]={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ordinal:function(e){var t=["th","st","nd","rd"],r=e%100;return"["+e+(t[(r-20)%10]||t[r]||t[0])+"]"}};var p="$isDayjsObject",g=function(e){return e instanceof w||!(!e||!e[p])},x=function e(t,r,a){var n;if(!t)return f;if("string"==typeof t){var s=t.toLowerCase();h[s]&&(n=s),r&&(h[s]=r,n=s);var i=t.split("-");if(!n&&i.length>1)return e(i[0])}else{var l=t.name;h[l]=t,n=l}return!a&&n&&(f=n),n||!a&&f},b=function(e,t){if(g(e))return e.clone();var r="object"==typeof t?t:{};return r.date=e,r.args=arguments,new w(r)},v={s:m,z:function(e){var t=-e.utcOffset(),r=Math.abs(t);return(t<=0?"+":"-")+m(Math.floor(r/60),2,"0")+":"+m(r%60,2,"0")},m:function e(t,r){if(t.date(){"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M854.4 800.9c.2-.3.5-.6.7-.9C920.6 722.1 960 621.7 960 512s-39.4-210.1-104.8-288c-.2-.3-.5-.5-.7-.8-1.1-1.3-2.1-2.5-3.2-3.7-.4-.5-.8-.9-1.2-1.4l-4.1-4.7-.1-.1c-1.5-1.7-3.1-3.4-4.6-5.1l-.1-.1c-3.2-3.4-6.4-6.8-9.7-10.1l-.1-.1-4.8-4.8-.3-.3c-1.5-1.5-3-2.9-4.5-4.3-.5-.5-1-1-1.6-1.5-1-1-2-1.9-3-2.8-.3-.3-.7-.6-1-1C736.4 109.2 629.5 64 512 64s-224.4 45.2-304.3 119.2c-.3.3-.7.6-1 1-1 .9-2 1.9-3 2.9-.5.5-1 1-1.6 1.5-1.5 1.4-3 2.9-4.5 4.3l-.3.3-4.8 4.8-.1.1c-3.3 3.3-6.5 6.7-9.7 10.1l-.1.1c-1.6 1.7-3.1 3.4-4.6 5.1l-.1.1c-1.4 1.5-2.8 3.1-4.1 4.7-.4.5-.8.9-1.2 1.4-1.1 1.2-2.1 2.5-3.2 3.7-.2.3-.5.5-.7.8C103.4 301.9 64 402.3 64 512s39.4 210.1 104.8 288c.2.3.5.6.7.9l3.1 3.7c.4.5.8.9 1.2 1.4l4.1 4.7c0 .1.1.1.1.2 1.5 1.7 3 3.4 4.6 5l.1.1c3.2 3.4 6.4 6.8 9.6 10.1l.1.1c1.6 1.6 3.1 3.2 4.7 4.7l.3.3c3.3 3.3 6.7 6.5 10.1 9.6 80.1 74 187 119.2 304.5 119.2s224.4-45.2 304.3-119.2a300 300 0 0010-9.6l.3-.3c1.6-1.6 3.2-3.1 4.7-4.7l.1-.1c3.3-3.3 6.5-6.7 9.6-10.1l.1-.1c1.5-1.7 3.1-3.3 4.6-5 0-.1.1-.1.1-.2 1.4-1.5 2.8-3.1 4.1-4.7.4-.5.8-.9 1.2-1.4a99 99 0 003.3-3.7zm4.1-142.6c-13.8 32.6-32 62.8-54.2 90.2a444.07 444.07 0 00-81.5-55.9c11.6-46.9 18.8-98.4 20.7-152.6H887c-3 40.9-12.6 80.6-28.5 118.3zM887 484H743.5c-1.9-54.2-9.1-105.7-20.7-152.6 29.3-15.6 56.6-34.4 81.5-55.9A373.86 373.86 0 01887 484zM658.3 165.5c39.7 16.8 75.8 40 107.6 69.2a394.72 394.72 0 01-59.4 41.8c-15.7-45-35.8-84.1-59.2-115.4 3.7 1.4 7.4 2.9 11 4.4zm-90.6 700.6c-9.2 7.2-18.4 12.7-27.7 16.4V697a389.1 389.1 0 01115.7 26.2c-8.3 24.6-17.9 47.3-29 67.8-17.4 32.4-37.8 58.3-59 75.1zm59-633.1c11 20.6 20.7 43.3 29 67.8A389.1 389.1 0 01540 327V141.6c9.2 3.7 18.5 9.1 27.7 16.4 21.2 16.7 41.6 42.6 59 75zM540 640.9V540h147.5c-1.6 44.2-7.1 87.1-16.3 127.8l-.3 1.2A445.02 445.02 0 00540 640.9zm0-156.9V383.1c45.8-2.8 89.8-12.5 130.9-28.1l.3 1.2c9.2 40.7 14.7 83.5 16.3 127.8H540zm-56 56v100.9c-45.8 2.8-89.8 12.5-130.9 28.1l-.3-1.2c-9.2-40.7-14.7-83.5-16.3-127.8H484zm-147.5-56c1.6-44.2 7.1-87.1 16.3-127.8l.3-1.2c41.1 15.6 85 25.3 130.9 28.1V484H336.5zM484 697v185.4c-9.2-3.7-18.5-9.1-27.7-16.4-21.2-16.7-41.7-42.7-59.1-75.1-11-20.6-20.7-43.3-29-67.8 37.2-14.6 75.9-23.3 115.8-26.1zm0-370a389.1 389.1 0 01-115.7-26.2c8.3-24.6 17.9-47.3 29-67.8 17.4-32.4 37.8-58.4 59.1-75.1 9.2-7.2 18.4-12.7 27.7-16.4V327zM365.7 165.5c3.7-1.5 7.3-3 11-4.4-23.4 31.3-43.5 70.4-59.2 115.4-21-12-40.9-26-59.4-41.8 31.8-29.2 67.9-52.4 107.6-69.2zM165.5 365.7c13.8-32.6 32-62.8 54.2-90.2 24.9 21.5 52.2 40.3 81.5 55.9-11.6 46.9-18.8 98.4-20.7 152.6H137c3-40.9 12.6-80.6 28.5-118.3zM137 540h143.5c1.9 54.2 9.1 105.7 20.7 152.6a444.07 444.07 0 00-81.5 55.9A373.86 373.86 0 01137 540zm228.7 318.5c-39.7-16.8-75.8-40-107.6-69.2 18.5-15.8 38.4-29.7 59.4-41.8 15.7 45 35.8 84.1 59.2 115.4-3.7-1.4-7.4-2.9-11-4.4zm292.6 0c-3.7 1.5-7.3 3-11 4.4 23.4-31.3 43.5-70.4 59.2-115.4 21 12 40.9 26 59.4 41.8a373.81 373.81 0 01-107.6 69.2z"}}]},name:"global",theme:"outlined"};var n=e.i(9583),s=r.forwardRef(function(e,s){return r.createElement(n.default,(0,t.default)({},e,{ref:s,icon:a}))});e.s(["GlobalOutlined",0,s],160818)},728889,e=>{"use strict";var t=e.i(290571),r=e.i(271645),a=e.i(829087),n=e.i(480731),s=e.i(444755),i=e.i(673706),l=e.i(95779);let o={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"}},u={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:""}},c=(0,i.makeClassName)("Icon"),m=r.default.forwardRef((e,m)=>{let{icon:f,variant:h="simple",tooltip:p,size:g=n.Sizes.SM,color:x,className:b}=e,v=(0,t.__rest)(e,["icon","variant","tooltip","size","color","className"]),w=((e,t)=>{switch(e){case"simple":return{textColor:t?(0,i.getColorClassNames)(t,l.colorPalette.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:"",borderColor:"",ringColor:""};case"light":return{textColor:t?(0,i.getColorClassNames)(t,l.colorPalette.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,s.tremorTwMerge)((0,i.getColorClassNames)(t,l.colorPalette.background).bgColor,"bg-opacity-20"):"bg-tremor-brand-muted dark:bg-dark-tremor-brand-muted",borderColor:"",ringColor:""};case"shadow":return{textColor:t?(0,i.getColorClassNames)(t,l.colorPalette.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,s.tremorTwMerge)((0,i.getColorClassNames)(t,l.colorPalette.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,i.getColorClassNames)(t,l.colorPalette.text).textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",bgColor:t?(0,s.tremorTwMerge)((0,i.getColorClassNames)(t,l.colorPalette.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,i.getColorClassNames)(t,l.colorPalette.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,s.tremorTwMerge)((0,i.getColorClassNames)(t,l.colorPalette.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:t?(0,i.getColorClassNames)(t,l.colorPalette.ring).borderColor:"border-tremor-brand-subtle dark:border-dark-tremor-brand-subtle",ringColor:t?(0,s.tremorTwMerge)((0,i.getColorClassNames)(t,l.colorPalette.ring).ringColor,"ring-opacity-40"):"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"}}})(h,x),{tooltipProps:y,getReferenceProps:C}=(0,a.useTooltip)();return r.default.createElement("span",Object.assign({ref:(0,i.mergeRefs)([m,y.refs.setReference]),className:(0,s.tremorTwMerge)(c("root"),"inline-flex shrink-0 items-center justify-center",w.bgColor,w.textColor,w.borderColor,w.ringColor,u[h].rounded,u[h].border,u[h].shadow,u[h].ring,o[g].paddingX,o[g].paddingY,b)},C,v),r.default.createElement(a.default,Object.assign({text:p},y)),r.default.createElement(f,{className:(0,s.tremorTwMerge)(c("icon"),"shrink-0",d[g].height,d[g].width)}))});m.displayName="Icon",e.s(["default",0,m],728889)},752978,e=>{"use strict";var t=e.i(728889);e.s(["Icon",()=>t.default])},591935,e=>{"use strict";var t=e.i(271645);let r=t.forwardRef(function(e,r){return t.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),t.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"}))});e.s(["PencilAltIcon",0,r],591935)},122577,e=>{"use strict";var t=e.i(271645);let r=t.forwardRef(function(e,r){return t.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),t.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"}),t.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M21 12a9 9 0 11-18 0 9 9 0 0118 0z"}))});e.s(["PlayIcon",0,r],122577)},360820,e=>{"use strict";var t=e.i(271645);let r=t.forwardRef(function(e,r){return t.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),t.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M5 15l7-7 7 7"}))});e.s(["ChevronUpIcon",0,r],360820)},434626,e=>{"use strict";var t=e.i(271645);let r=t.forwardRef(function(e,r){return t.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),t.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"}))});e.s(["ExternalLinkIcon",0,r],434626)},902555,e=>{"use strict";var t=e.i(843476),r=e.i(591935),a=e.i(122577),n=e.i(278587),s=e.i(68155),i=e.i(360820),l=e.i(871943),o=e.i(434626),d=e.i(271645);let u=d.forwardRef(function(e,t){return d.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),d.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"}))});var c=e.i(592968),m=e.i(115504),f=e.i(752978);function h({icon:e,onClick:r,className:a,disabled:n,dataTestId:s}){return n?(0,t.jsx)(f.Icon,{icon:e,size:"sm",className:"opacity-50 cursor-not-allowed","data-testid":s}):(0,t.jsx)(f.Icon,{icon:e,size:"sm",onClick:r,className:(0,m.cx)("cursor-pointer",a),"data-testid":s})}let p={Edit:{icon:r.PencilAltIcon,className:"hover:text-blue-600"},Delete:{icon:s.TrashIcon,className:"hover:text-red-600"},Test:{icon:a.PlayIcon,className:"hover:text-blue-600"},Regenerate:{icon:n.RefreshIcon,className:"hover:text-green-600"},Up:{icon:i.ChevronUpIcon,className:"hover:text-blue-600"},Down:{icon:l.ChevronDownIcon,className:"hover:text-blue-600"},Open:{icon:o.ExternalLinkIcon,className:"hover:text-green-600"},Copy:{icon:u,className:"hover:text-blue-600"}};e.s(["default",0,function({onClick:e,tooltipText:r,disabled:a=!1,disabledTooltipText:n,dataTestId:s,variant:i}){let{icon:l,className:o}=p[i];return(0,t.jsx)(c.Tooltip,{title:a?n:r,children:(0,t.jsx)("span",{children:(0,t.jsx)(h,{icon:l,onClick:e,className:o,disabled:a,dataTestId:s})})})}],902555)},738014,e=>{"use strict";var t=e.i(135214),r=e.i(602869),a=e.i(266027);let n=(0,e.i(243652).createQueryKeys)("users");e.s(["useCurrentUser",0,()=>{let{accessToken:e,userId:s}=(0,t.default)();return(0,a.useQuery)({queryKey:n.detail(s),queryFn:async()=>await (0,r.userGetInfoV2)(e),enabled:!!(e&&s)})}])},162386,e=>{"use strict";var t=e.i(843476),r=e.i(625901),a=e.i(109799),n=e.i(785242),s=e.i(738014),i=e.i(199133),l=e.i(981339),o=e.i(592968);let d={label:"All Proxy Models",value:"all-proxy-models"},u={label:"No Default Models",value:"no-default-models"},c=[d,u],m={user:({allProxyModels:e,userModels:t,options:r})=>t&&r?.includeUserModels?t:[],team:({allProxyModels:e,selectedOrganization:t,userModels:r})=>t?t.models.includes(d.value)||0===t.models.length?e:e.filter(e=>t.models.includes(e)):e??[],organization:({allProxyModels:e})=>e,global:({allProxyModels:e})=>e};e.s(["MODEL_SENTINEL_OPTIONS",0,c,"ModelSelect",0,e=>{let{teamID:f,organizationID:h,options:p,context:g,dataTestId:x,value:b=[],onChange:v,style:w}=e,{includeUserModels:y,showAllTeamModelsOption:C,showAllProxyModelsOverride:j,includeSpecialOptions:k}=p||{},{data:M,isLoading:N}=(0,r.useAllProxyModels)(),{data:S,isLoading:$}=(0,n.useTeam)(f),{data:O,isLoading:_}=(0,a.useOrganization)(h),{data:I,isLoading:z}=(0,s.useCurrentUser)(),T=e=>c.some(t=>t.value===e),D=b.some(T),E=O?.models.includes(d.value)||O?.models.length===0;if(N||$||_||z)return(0,t.jsx)(l.Skeleton.Input,{active:!0,block:!0});let{wildcard:L,regular:A}=(e=>{let t=[],r=[];for(let a of e)a.endsWith("/*")?t.push(a):r.push(a);return{wildcard:t,regular:r}})(((e,t,r)=>{let a=Array.from(new Map(e.map(e=>[e.id,e])).values()).map(e=>e.id);if(t.options?.showAllProxyModelsOverride)return a;let n=m[t.context];return n?n({allProxyModels:a,...r,options:t.options}):[]})(M?.data??[],e,{selectedTeam:S,selectedOrganization:O,userModels:I?.models}));return(0,t.jsx)(i.Select,{"data-testid":x,value:b,onChange:e=>{let t=e.filter(T);v(t.length>0?[t[t.length-1]]:e)},style:w,options:[...k?[{label:(0,t.jsx)("span",{children:"Special Options"}),title:"Special Options",options:[...j||E&&k||"global"===g?[{label:(0,t.jsx)("span",{children:"All Proxy Models"}),value:d.value,disabled:b.length>0&&b.some(e=>T(e)&&e!==d.value),key:d.value}]:[],{label:(0,t.jsx)("span",{children:"No Default Models"}),value:u.value,disabled:b.length>0&&b.some(e=>T(e)&&e!==u.value),key:u.value}]}]:[],...L.length>0?[{label:(0,t.jsx)("span",{children:"Wildcard Options"}),title:"Wildcard Options",options:L.map(e=>{let r=e.replace("/*",""),a=r.charAt(0).toUpperCase()+r.slice(1);return{label:(0,t.jsx)("span",{children:`All ${a} models`}),value:e,disabled:D}})}]:[],{label:(0,t.jsx)("span",{children:"Models"}),title:"Models",options:A.map(e=>({label:(0,t.jsx)("span",{children:e}),value:e,disabled:D}))}],mode:"multiple",placeholder:"Select Models",allowClear:!0,maxTagCount:"responsive",maxTagPlaceholder:e=>(0,t.jsx)(o.Tooltip,{styles:{root:{pointerEvents:"none"}},title:e.map(({value:e})=>e).join(", "),children:(0,t.jsxs)("span",{children:["+",e.length," more"]})})})}],162386)},907308,276173,e=>{"use strict";var t=e.i(843476),r=e.i(271645),a=e.i(212931),n=e.i(808613),s=e.i(464571),i=e.i(199133),l=e.i(592968),o=e.i(213205),d=e.i(343488),u=e.i(602869),c=e.i(741466);e.s(["default",0,({isVisible:e,onCancel:m,onSubmit:f,accessToken:h,title:p="Add Team Member",roles:g=[{label:"admin",value:"admin",description:"Admin role. Can create team keys, add members, and manage settings."},{label:"user",value:"user",description:"User role. Can view team info, but not manage it."}],defaultRole:x="user",teamId:b})=>{let[v]=n.Form.useForm(),[w,y]=(0,r.useState)([]),[C,j]=(0,r.useState)(!1),[k,M]=(0,r.useState)("user_email"),[N,S]=(0,r.useState)(!1),$=async(e,t)=>{if(!e)return void y([]);j(!0);try{let r=new URLSearchParams;if(r.append(t,e),b&&r.append("team_id",b),null==h)return;let a=(await (0,u.userFilterUICall)(h,r)).map(e=>({label:"user_email"===t?`${e.user_email}`:`${e.user_id}`,value:"user_email"===t?e.user_email:e.user_id,user:e}));y(a)}catch(e){console.error("Error fetching users:",e)}finally{j(!1)}},O=(0,d.useDebouncedCallback)((e,t)=>$(e,t),{wait:c.DEBOUNCE_WAIT_MS}),_=(e,t)=>{M(t),O(e,t)},I=(e,t)=>{let r=t.user;v.setFieldsValue({user_email:r.user_email,user_id:r.user_id,role:v.getFieldValue("role")})},z=async e=>{S(!0);try{await f(e)}finally{S(!1)}};return(0,t.jsx)(a.Modal,{title:p,open:e,onCancel:()=>{v.resetFields(),y([]),m()},footer:null,width:800,maskClosable:!N,children:(0,t.jsxs)(n.Form,{form:v,onFinish:z,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",initialValues:{role:x},children:[(0,t.jsx)(n.Form.Item,{label:"Email",name:"user_email",className:"mb-4",children:(0,t.jsx)(i.Select,{showSearch:!0,className:"w-full",placeholder:"Search by email",filterOption:!1,onSearch:e=>_(e,"user_email"),onSelect:(e,t)=>I(e,t),options:"user_email"===k?w:[],loading:C,allowClear:!0,"data-testid":"member-email-search"})}),(0,t.jsx)("div",{className:"text-center mb-4",children:"OR"}),(0,t.jsx)(n.Form.Item,{label:"User ID",name:"user_id",className:"mb-4",children:(0,t.jsx)(i.Select,{showSearch:!0,className:"w-full",placeholder:"Search by user ID",filterOption:!1,onSearch:e=>_(e,"user_id"),onSelect:(e,t)=>I(e,t),options:"user_id"===k?w:[],loading:C,allowClear:!0})}),(0,t.jsx)(n.Form.Item,{label:"Member Role",name:"role",className:"mb-4",children:(0,t.jsx)(i.Select,{defaultValue:x,children:g.map(e=>(0,t.jsx)(i.Select.Option,{value:e.value,children:(0,t.jsxs)(l.Tooltip,{title:e.description,children:[(0,t.jsx)("span",{className:"font-medium",children:e.label}),(0,t.jsxs)("span",{className:"ml-2 text-gray-500 text-sm",children:["- ",e.description]})]})},e.value))})}),(0,t.jsx)("div",{className:"text-right mt-4",children:(0,t.jsx)(s.Button,{type:"primary",htmlType:"submit",icon:(0,t.jsx)(o.UserAddOutlined,{}),loading:N,children:N?"Adding...":"Add Member"})})]})})}],907308);var m=e.i(599724),f=e.i(779241),h=e.i(435451),p=e.i(860585);e.s(["default",0,({visible:e,onCancel:l,onSubmit:o,initialData:d,mode:u,config:c})=>{let g,[x]=n.Form.useForm(),[b,v]=(0,r.useState)(!1);(0,r.useEffect)(()=>{if(e)if("edit"===u&&d){let e={...d,role:d.role||c.defaultRole,max_budget_in_team:d.max_budget_in_team||null,tpm_limit:d.tpm_limit||null,rpm_limit:d.rpm_limit||null,budget_duration:d.budget_duration||null,allowed_models:d.allowed_models||[]};x.setFieldsValue(e)}else x.resetFields(),x.setFieldsValue({role:c.defaultRole||c.roleOptions[0]?.value})},[e,d,u,x,c.defaultRole,c.roleOptions]);let w=async e=>{try{v(!0);let t=Object.entries(e).reduce((e,[t,r])=>{if("string"==typeof r){let a=r.trim();return""===a&&("max_budget_in_team"===t||"tpm_limit"===t||"rpm_limit"===t)?{...e,[t]:null}:{...e,[t]:a}}return{...e,[t]:r}},{});await Promise.resolve(o(t)),x.resetFields()}catch(e){console.error("Form submission error:",e)}finally{v(!1)}};return(0,t.jsx)(a.Modal,{title:c.title||("add"===u?"Add Member":"Edit Member"),open:e,width:1e3,footer:null,onCancel:l,children:(0,t.jsxs)(n.Form,{form:x,onFinish:w,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",children:[c.showEmail&&(0,t.jsx)(n.Form.Item,{label:"Email",name:"user_email",className:"mb-4",rules:[{type:"email",message:"Please enter a valid email!"}],children:(0,t.jsx)(f.TextInput,{placeholder:"user@example.com"})}),c.showEmail&&c.showUserId&&(0,t.jsx)("div",{className:"text-center mb-4",children:(0,t.jsx)(m.Text,{children:"OR"})}),c.showUserId&&(0,t.jsx)(n.Form.Item,{label:"User ID",name:"user_id",className:"mb-4",children:(0,t.jsx)(f.TextInput,{placeholder:"user_123"})}),(0,t.jsx)(n.Form.Item,{label:(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{children:"Role"}),"edit"===u&&d&&(0,t.jsxs)("span",{className:"text-gray-500 text-sm",children:["(Current: ",(g=d.role,c.roleOptions.find(e=>e.value===g)?.label||g),")"]})]}),name:"role",className:"mb-4",rules:[{required:!0,message:"Please select a role!"}],children:(0,t.jsx)(i.Select,{children:"edit"===u&&d?[...c.roleOptions.filter(e=>e.value===d.role),...c.roleOptions.filter(e=>e.value!==d.role)].map(e=>(0,t.jsx)(i.Select.Option,{value:e.value,children:e.label},e.value)):c.roleOptions.map(e=>(0,t.jsx)(i.Select.Option,{value:e.value,children:e.label},e.value))})}),c.additionalFields?.map(e=>(0,t.jsx)(n.Form.Item,{label:e.label,name:e.name,className:"mb-4",rules:e.rules,children:(e=>{switch(e.type){case"input":return(0,t.jsx)(f.TextInput,{placeholder:e.placeholder});case"numerical":return(0,t.jsx)(h.default,{step:e.step||1,min:e.min||0,style:{width:"100%"},placeholder:e.placeholder||"Enter a numerical value"});case"select":return(0,t.jsx)(i.Select,{children:e.options?.map(e=>(0,t.jsx)(i.Select.Option,{value:e.value,children:e.label},e.value))});case"multi-select":return(0,t.jsx)(i.Select,{mode:"multiple",placeholder:e.placeholder||"Select options",options:e.options,allowClear:!0});case"budget-duration":return(0,t.jsx)(p.default,{});default:return null}})(e)},e.name)),(0,t.jsxs)("div",{className:"text-right mt-6",children:[(0,t.jsx)(s.Button,{onClick:l,className:"mr-2",disabled:b,children:"Cancel"}),(0,t.jsx)(s.Button,{type:"default",htmlType:"submit",loading:b,children:"add"===u?b?"Adding...":"Add Member":b?"Saving...":"Save Changes"})]})]})})}],276173)},294612,e=>{"use strict";var t=e.i(843476),r=e.i(100486),a=e.i(827252),n=e.i(213205),s=e.i(771674),i=e.i(464571),l=e.i(770914),o=e.i(291542),d=e.i(262218),u=e.i(592968),c=e.i(898586),m=e.i(902555);let{Text:f}=c.Typography;e.s(["default",0,function({members:e,canEdit:c,onEdit:h,onDelete:p,onAddMember:g,roleColumnTitle:x="Role",roleTooltip:b,extraColumns:v=[],showDeleteForMember:w,emptyText:y}){let C=[{title:"User Email",dataIndex:"user_email",key:"user_email",render:e=>(0,t.jsx)(f,{children:e||"-"})},{title:"User ID",dataIndex:"user_id",key:"user_id",render:e=>"default_user_id"===e?(0,t.jsx)(d.Tag,{color:"blue",children:"Default Proxy Admin"}):(0,t.jsx)(f,{children:e||"-"})},{title:b?(0,t.jsxs)(l.Space,{direction:"horizontal",children:[x,(0,t.jsx)(u.Tooltip,{title:b,children:(0,t.jsx)(a.InfoCircleOutlined,{})})]}):x,dataIndex:"role",key:"role",render:e=>(0,t.jsxs)(l.Space,{children:[e?.toLowerCase()==="admin"||e?.toLowerCase()==="org_admin"?(0,t.jsx)(r.CrownOutlined,{}):(0,t.jsx)(s.UserOutlined,{}),(0,t.jsx)(f,{style:{textTransform:"capitalize"},children:e||"-"})]})},...v,{title:"Actions",key:"actions",fixed:"right",width:120,render:(e,r)=>c?(0,t.jsxs)(l.Space,{children:[(0,t.jsx)(m.default,{variant:"Edit",tooltipText:"Edit member",dataTestId:"edit-member",onClick:()=>h(r)}),(!w||w(r))&&(0,t.jsx)(m.default,{variant:"Delete",tooltipText:"Delete member",dataTestId:"delete-member",onClick:()=>p(r)})]}):null}];return(0,t.jsxs)(l.Space,{direction:"vertical",style:{width:"100%"},children:[(0,t.jsxs)("span",{className:"inline-flex text-sm text-gray-700",children:[e.length," Member",1!==e.length?"s":""]}),(0,t.jsx)(o.Table,{columns:C,dataSource:e,rowKey:e=>e.user_id??e.user_email??JSON.stringify(e),pagination:!1,size:"small",scroll:{x:"max-content"},locale:y?{emptyText:y}:void 0}),g&&c&&(0,t.jsx)(i.Button,{icon:(0,t.jsx)(n.UserAddOutlined,{}),type:"primary",onClick:g,children:"Add Member"})]})}])},113625,e=>{"use strict";let t=(0,e.i(475254).default)("layers",[["path",{d:"M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z",key:"zw3jo"}],["path",{d:"M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12",key:"1wduqc"}],["path",{d:"M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17",key:"kqbvx6"}]]);e.s(["default",0,t])},852008,e=>{"use strict";var t=e.i(113625);e.s(["Layers",()=>t.default])},372943,e=>{"use strict";e.i(247167);var t=e.i(8211),r=e.i(271645),a=e.i(343794),n=e.i(529681),s=e.i(242064),i=e.i(704914),l=e.i(876556),o=e.i(290224),d=e.i(251224),u=function(e,t){var r={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(r[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var n=0,a=Object.getOwnPropertySymbols(e);nt.indexOf(a[n])&&Object.prototype.propertyIsEnumerable.call(e,a[n])&&(r[a[n]]=e[a[n]]);return r};function c({suffixCls:e,tagName:t,displayName:a}){return a=>r.forwardRef((n,s)=>r.createElement(a,Object.assign({ref:s,suffixCls:e,tagName:t},n)))}let m=r.forwardRef((e,t)=>{let{prefixCls:n,suffixCls:i,className:l,tagName:o}=e,c=u(e,["prefixCls","suffixCls","className","tagName"]),{getPrefixCls:m}=r.useContext(s.ConfigContext),f=m("layout",n),[h,p,g]=(0,d.default)(f),x=i?`${f}-${i}`:f;return h(r.createElement(o,Object.assign({className:(0,a.default)(n||x,l,p,g),ref:t},c)))}),f=r.forwardRef((e,c)=>{let{direction:m}=r.useContext(s.ConfigContext),[f,h]=r.useState([]),{prefixCls:p,className:g,rootClassName:x,children:b,hasSider:v,tagName:w,style:y}=e,C=u(e,["prefixCls","className","rootClassName","children","hasSider","tagName","style"]),j=(0,n.default)(C,["suffixCls"]),{getPrefixCls:k,className:M,style:N}=(0,s.useComponentConfig)("layout"),S=k("layout",p),$="boolean"==typeof v?v:!!f.length||(0,l.default)(b).some(e=>e.type===o.default),[O,_,I]=(0,d.default)(S),z=(0,a.default)(S,{[`${S}-has-sider`]:$,[`${S}-rtl`]:"rtl"===m},M,g,x,_,I),T=r.useMemo(()=>({siderHook:{addSider:e=>{h(r=>[].concat((0,t.default)(r),[e]))},removeSider:e=>{h(t=>t.filter(t=>t!==e))}}}),[]);return O(r.createElement(i.LayoutContext.Provider,{value:T},r.createElement(w,Object.assign({ref:c,className:z,style:Object.assign(Object.assign({},N),y)},j),b)))}),h=c({tagName:"div",displayName:"Layout"})(f),p=c({suffixCls:"header",tagName:"header",displayName:"Header"})(m),g=c({suffixCls:"footer",tagName:"footer",displayName:"Footer"})(m),x=c({suffixCls:"content",tagName:"main",displayName:"Content"})(m);h.Header=p,h.Footer=g,h.Content=x,h.Sider=o.default,h._InternalSiderContext=o.SiderContext,e.s(["Layout",0,h],372943)}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/048wkdcpsnwne.js b/litellm/proxy/_experimental/out/_next/static/chunks/048wkdcpsnwne.js new file mode 100644 index 00000000000..eeae0c450a9 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/048wkdcpsnwne.js @@ -0,0 +1,31 @@ +(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,863679,e=>{"use strict";var t=e.i(843476),l=e.i(271645),a=e.i(304967),r=e.i(269200),s=e.i(427612),n=e.i(496020),i=e.i(64848),o=e.i(977572),d=e.i(942232),c=e.i(629569),u=e.i(599724),g=e.i(994388),m=e.i(752978),p=e.i(793130),f=e.i(677572),h=e.i(602869),x=e.i(28651),y=e.i(199133),b=e.i(68155);e.i(622826);var _=e.i(112179),j=e.i(464571),v=e.i(727749),C=e.i(158392);let k=({accessToken:e,userRole:a,userID:r})=>{let[s,n]=(0,l.useState)({routerSettings:{},selectedStrategy:null,enableTagFiltering:!1}),[i,o]=(0,l.useState)([]),[d,c]=(0,l.useState)({}),[u,g]=(0,l.useState)({});(0,l.useEffect)(()=>{e&&a&&r&&((0,h.getCallbacksCall)(e,r,a).then(e=>{let t=e.router_settings;"model_group_retry_policy"in t&&delete t.model_group_retry_policy;let l=t.routing_strategy||null;n(e=>({...e,routerSettings:t,selectedStrategy:l}))}),(0,h.getRouterSettingsCall)(e).then(e=>{if(e.fields){let t={};e.fields.forEach(e=>{t[e.field_name]={ui_field_name:e.ui_field_name,field_description:e.field_description,options:e.options,link:e.link}}),c(t);let l=e.fields.find(e=>"routing_strategy"===e.field_name);l?.options&&o(l.options),e.routing_strategy_descriptions&&g(e.routing_strategy_descriptions);let a=e.fields.find(e=>"enable_tag_filtering"===e.field_name);a?.field_value!==null&&a?.field_value!==void 0&&n(e=>({...e,enableTagFiltering:a.field_value}))}}))},[e,a,r]);let m=async()=>{if(!e)return;let t=s.routerSettings,l=new Set(["allowed_fails","cooldown_time","num_retries","timeout","retry_after"]),a=new Set(["model_group_alias"]),r=new Set(["retry_policy","model_group_retry_policy","routing_groups"]),n=Object.fromEntries(Object.entries({...t,enable_tag_filtering:s.enableTagFiltering}).map(([e,t])=>{if(r.has(e))return null;if("routing_strategy_args"!==e&&"routing_strategy"!==e&&"enable_tag_filtering"!==e){let r=document.querySelector(`input[name="${e}"]`),s=((e,t,r)=>{if(void 0===t)return r;let s=t.trim();if("null"===s.toLowerCase())return null;if(l.has(e)){let e=Number(s);return Number.isNaN(e)?r:e}if(a.has(e)){if(""===s)return null;try{return JSON.parse(s)}catch{return r}}return"true"===s.toLowerCase()||"false"!==s.toLowerCase()&&s})(e,r?.value,t);return[e,s]}if("routing_strategy"===e)return[e,s.selectedStrategy];if("enable_tag_filtering"===e)return[e,s.enableTagFiltering];if("routing_strategy_args"===e&&"latency-based-routing"===s.selectedStrategy){let e={},t=document.querySelector('input[name="lowest_latency_buffer"]'),l=document.querySelector('input[name="ttl"]');return t?.value&&(e.lowest_latency_buffer=Number(t.value)),l?.value&&(e.ttl=Number(l.value)),["routing_strategy_args",e]}return null}).filter(e=>null!=e));try{await (0,h.setCallbacksCall)(e,{router_settings:n}),v.default.success("router settings updated successfully")}catch(e){v.default.fromBackend("Failed to update router settings: "+e)}};return e?(0,t.jsxs)("div",{className:"w-full",children:[(0,t.jsx)(C.default,{value:s,onChange:n,routerFieldsMetadata:d,availableRoutingStrategies:i,routingStrategyDescriptions:u}),(0,t.jsxs)("div",{className:"border-t border-gray-200 pt-6 flex justify-end gap-3",children:[(0,t.jsx)(j.Button,{onClick:()=>window.location.reload(),children:"Reset"}),(0,t.jsx)(j.Button,{type:"primary",onClick:m,children:"Save Changes"})]})]}):null};e.i(247167);var w=e.i(368670);let S=l.forwardRef(function(e,t){return l.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),l.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M14 5l7 7m0 0l-7 7m7-7H3"}))});var N=e.i(591935),T=e.i(122577),M=e.i(592968),A=e.i(898586),I=e.i(356449),F=e.i(127952),L=e.i(418371),E=e.i(708347),O=e.i(888259),B=e.i(695411),D=e.i(212931),P=e.i(972520);function R({open:e,onCancel:l,children:a}){return(0,t.jsx)(D.Modal,{title:(0,t.jsx)("div",{className:"pb-4 border-b border-gray-100",children:(0,t.jsxs)("div",{className:"flex items-center gap-2 text-gray-800",children:[(0,t.jsx)("div",{className:"p-2 bg-indigo-50 rounded-lg",children:(0,t.jsx)(P.ArrowRight,{className:"w-5 h-5 text-indigo-600"})}),(0,t.jsxs)("div",{children:[(0,t.jsx)("h2",{className:"text-lg font-bold m-0",children:"Configure Model Fallbacks"}),(0,t.jsx)("p",{className:"text-sm text-gray-500 font-normal m-0",children:"Manage multiple fallback chains for different models (up to 5 groups at a time)"})]})]})}),open:e,width:900,footer:null,onCancel:l,maskClosable:!1,className:"top-8",styles:{body:{padding:"24px"},header:{padding:"24px 24px 0 24px",border:"none"}},children:(0,t.jsx)("div",{className:"mt-6",children:a})})}var $=e.i(419470);function H({accessToken:e,value:a=[],onChange:r}){let[s,n]=(0,l.useState)(!1),[i,o]=(0,l.useState)([]),[d,c]=(0,l.useState)(0),[u,m]=(0,l.useState)(!1),[p,f]=(0,l.useState)([{id:"1",primaryModel:null,fallbackModels:[]}]);(0,l.useEffect)(()=>{s&&(f([{id:"1",primaryModel:null,fallbackModels:[]}]),c(e=>e+1))},[s]),(0,l.useEffect)(()=>{let t=async()=>{try{let t=await (0,B.fetchAvailableModels)(e);o(t)}catch(e){console.error("Error fetching model info for fallbacks:",e)}};s&&t()},[e,s]);let h=Array.from(new Set(i.map(e=>e.model_group))).sort(),x=()=>{n(!1),f([{id:"1",primaryModel:null,fallbackModels:[]}])},y=async()=>{let e=p.filter(e=>!e.primaryModel||0===e.fallbackModels.length);if(e.length>0)return void O.default.error(`Please complete configuration for all groups. ${e.length} group(s) incomplete.`);let t=[...a||[],...p.map(e=>({[e.primaryModel]:e.fallbackModels}))];if(r){m(!0);try{await r(t),v.default.success(`${p.length} fallback configuration(s) added successfully!`),x()}catch(e){console.error("Error saving fallbacks:",e)}finally{m(!1)}}else v.default.fromBackend("onChange callback not provided")};return(0,t.jsxs)("div",{children:[(0,t.jsx)(g.Button,{className:"mx-auto",onClick:()=>n(!0),icon:()=>(0,t.jsx)("span",{className:"mr-1",children:"+"}),children:"Add Fallbacks"}),(0,t.jsxs)(R,{open:s,onCancel:x,children:[(0,t.jsx)($.FallbackSelectionForm,{groups:p,onGroupsChange:f,availableModels:h,maxFallbacks:10,maxGroups:5},d),p.length>0&&(0,t.jsxs)("div",{className:"flex items-center justify-end space-x-3 pt-6 mt-6 border-t border-gray-100",children:[(0,t.jsx)(j.Button,{type:"default",onClick:x,disabled:u,children:"Cancel"}),(0,t.jsx)(j.Button,{type:"default",onClick:y,disabled:0===p.length||u,loading:u,children:u?"Saving Configuration...":"Save All Configurations"})]})]})]})}var q=e.i(266027),z=e.i(788699),G=e.i(334115);function K({accessToken:e,fallbackEntry:a,value:r,onChange:s,onClose:n,maxFallbacks:i=10}){let[o,d]=(0,l.useState)(()=>{let e;return{id:"edit",primaryModel:e=Object.keys(a)[0]??null,fallbackModels:e?[...a[e]??[]]:[]}}),[c,u]=(0,l.useState)(!1),{data:g=[]}=(0,q.useQuery)({queryKey:["availableModels","fallbacks"],queryFn:()=>(0,B.fetchAvailableModels)(e),enabled:!!e}),m=(0,l.useMemo)(()=>Array.from(new Set(g.map(e=>e.model_group))).sort(),[g]),p=async()=>{let e=o.primaryModel;if(!e)return;let t=(r||[]).map(t=>e in t?{...t,[e]:o.fallbackModels}:t);u(!0);try{await s(t),v.default.success(`Fallbacks for ${e} updated successfully!`),n()}catch(e){console.error("Error updating fallbacks:",e)}finally{u(!1)}};return(0,t.jsxs)(R,{open:!0,onCancel:n,children:[(0,t.jsx)(G.FallbackGroupConfig,{group:o,onChange:d,availableModels:m,maxFallbacks:i,disablePrimaryModel:!0}),(0,t.jsxs)("div",{className:"flex items-center justify-end space-x-3 pt-6 mt-6 border-t border-gray-100",children:[(0,t.jsx)(j.Button,{type:"default",onClick:n,disabled:c,children:"Cancel"}),(0,t.jsx)(j.Button,{type:"primary",icon:(0,t.jsx)(z.Pencil,{className:"w-4 h-4"}),onClick:p,disabled:c||0===o.fallbackModels.length,loading:c,children:c?"Saving Changes...":"Save Changes"})]})]})}let U="inline-flex items-center gap-2 px-2.5 py-1 rounded-md border border-gray-200 bg-gray-50 text-sm font-medium text-gray-800 shrink-0";async function J(e,l){console.log=function(){};let a=window.location.origin,r=new I.default.OpenAI({apiKey:l,baseURL:a,dangerouslyAllowBrowser:!0});try{v.default.info("Testing fallback model response...");let l=await r.chat.completions.create({model:e,messages:[{role:"user",content:"Hi, this is a test message"}],mock_testing_fallbacks:!0});v.default.success((0,t.jsxs)("span",{children:["Test model=",(0,t.jsx)("strong",{children:e}),", received model=",(0,t.jsx)("strong",{children:l.model}),". See"," ",(0,t.jsx)("a",{href:"#",onClick:()=>window.open("https://docs.litellm.ai/docs/proxy/reliability","_blank"),style:{textDecoration:"underline",color:"blue"},children:"curl"})]}))}catch(e){v.default.fromBackend(`Error occurred while generating model response. Please try again. Error: ${e}`)}}let Q=({accessToken:e,userRole:a,userID:c})=>{let[u,g]=(0,l.useState)({}),[p,f]=(0,l.useState)(!1),[x,y]=(0,l.useState)(null),[_,j]=(0,l.useState)(!1),[C,k]=(0,l.useState)(null),{data:I}=(0,w.useModelCostMap)(),O=e=>null!=I&&"object"==typeof I&&e in I?I[e].litellm_provider??"":"";(0,l.useEffect)(()=>{e&&a&&c&&(0,h.getCallbacksCall)(e,c,a).then(e=>{let t=e.router_settings;"model_group_retry_policy"in t&&delete t.model_group_retry_policy,g(t)})},[e,a,c]);let B=e=>{y(e),j(!0)},D=e=>{k(e)},P=async()=>{if(!x||!e)return;let t=Object.keys(x)[0];if(!t)return;f(!0);let l=u.fallbacks.map(e=>{let l={...e};return t in l&&Array.isArray(l[t])&&delete l[t],l}).filter(e=>Object.keys(e).length>0),a={...u,fallbacks:l};try{await (0,h.setCallbacksCall)(e,{router_settings:a}),g(a),v.default.success("Router settings updated successfully")}catch(e){v.default.fromBackend("Failed to update router settings: "+e)}finally{f(!1),j(!1),y(null)}};if(!e)return null;let R=async t=>{if(!e)return;let l={...u,fallbacks:t};try{await (0,h.setCallbacksCall)(e,{router_settings:l}),g(l)}catch(t){throw v.default.fromBackend("Failed to update router settings: "+t),e&&a&&c&&(0,h.getCallbacksCall)(e,c,a).then(e=>{let t=e.router_settings;"model_group_retry_policy"in t&&delete t.model_group_retry_policy,g(t)}),t}},$=Array.isArray(u.fallbacks)&&u.fallbacks.length>0,q=(0,E.isProxyAdminRole)(a??"");return(0,t.jsxs)(t.Fragment,{children:[q&&(0,t.jsx)(H,{accessToken:e||"",value:u.fallbacks||[],onChange:R}),$?(0,t.jsxs)(r.Table,{children:[(0,t.jsx)(s.TableHead,{children:(0,t.jsxs)(n.TableRow,{children:[(0,t.jsx)(i.TableHeaderCell,{children:"Model Name"}),(0,t.jsx)(i.TableHeaderCell,{children:"Fallbacks"}),(0,t.jsx)(i.TableHeaderCell,{children:"Actions"})]})}),(0,t.jsx)(d.TableBody,{children:u.fallbacks.map((a,r)=>Object.entries(a).map(([s,i])=>{let d;return(0,t.jsxs)(n.TableRow,{children:[(0,t.jsx)(o.TableCell,{className:"align-top",children:(d=O?.(s)??s,(0,t.jsxs)("span",{className:U,children:[(0,t.jsx)(L.ProviderLogo,{provider:d,className:"w-4 h-4 shrink-0"}),(0,t.jsx)("span",{children:s})]}))}),(0,t.jsx)(o.TableCell,{className:"align-top",children:function(e,a){let r=Array.isArray(e)?e:[];if(0===r.length)return null;let s=({modelName:e})=>{let l=a?.(e)??e;return(0,t.jsxs)("span",{className:U,children:[(0,t.jsx)(L.ProviderLogo,{provider:l,className:"w-4 h-4 shrink-0"}),(0,t.jsx)("span",{children:e})]})};return(0,t.jsxs)("span",{className:"grid grid-cols-[auto_1fr] items-start gap-x-2 w-full min-w-0",children:[(0,t.jsx)("span",{className:"inline-flex items-center justify-center w-8 h-8 shrink-0 self-start text-blue-600","aria-hidden":!0,children:(0,t.jsx)(S,{className:"w-5 h-5 stroke-[2.5]"})}),(0,t.jsx)("span",{className:"flex flex-wrap items-start gap-1 min-w-0",children:r.map((e,a)=>(0,t.jsxs)(l.default.Fragment,{children:[a>0&&(0,t.jsx)(m.Icon,{icon:S,size:"xs",className:"shrink-0 text-gray-400"}),(0,t.jsx)(s,{modelName:e})]},e))})]})}(Array.isArray(i)?i:[],O)}),(0,t.jsx)(o.TableCell,{className:"align-top",children:q&&(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(M.Tooltip,{title:"Test fallback",children:(0,t.jsx)(m.Icon,{icon:T.PlayIcon,size:"sm",onClick:()=>J(Object.keys(a)[0],e||""),className:"cursor-pointer hover:text-blue-600"})}),(0,t.jsx)(M.Tooltip,{title:"Edit fallback",children:(0,t.jsx)("span",{"data-testid":"edit-fallback-button",role:"button",tabIndex:0,onClick:()=>D(a),onKeyDown:e=>"Enter"===e.key&&D(a),className:"cursor-pointer inline-flex",children:(0,t.jsx)(m.Icon,{icon:N.PencilAltIcon,size:"sm",className:"hover:text-blue-600"})})}),(0,t.jsx)(M.Tooltip,{title:"Delete fallback",children:(0,t.jsx)("span",{"data-testid":"delete-fallback-button",role:"button",tabIndex:0,onClick:()=>B(a),onKeyDown:e=>"Enter"===e.key&&B(a),className:"cursor-pointer inline-flex",children:(0,t.jsx)(m.Icon,{icon:b.TrashIcon,size:"sm",className:"hover:text-red-600"})})})]})})]},r.toString()+s)}))})]}):(0,t.jsx)("div",{className:"rounded-lg border border-gray-200 bg-gray-50 px-4 py-6 text-center",children:(0,t.jsx)(A.Typography.Text,{type:"secondary",children:"No fallbacks configured. Add fallbacks to automatically try another model when the primary fails."})}),q&&C&&(0,t.jsx)(K,{accessToken:e||"",fallbackEntry:C,value:u.fallbacks||[],onChange:R,onClose:()=>{k(null)}},Object.keys(C)[0]),(0,t.jsx)(F.default,{isOpen:_,title:"Delete Fallback?",message:"Are you sure you want to delete this fallback? This action cannot be undone.",resourceInformationTitle:"Fallback Information",resourceInformation:[{label:"Model Name",value:x?Object.keys(x)[0]:"",code:!0}],onCancel:()=>{j(!1),y(null)},onOk:P,confirmLoading:p})]})};var V=e.i(175712),W=e.i(525720),Y=e.i(311451),X=e.i(770914),Z=e.i(646563),ee=e.i(91979),et=e.i(928685),el=e.i(135214),ea=e.i(954616),er=e.i(912598),es=e.i(243652);let en=(0,es.createQueryKeys)("routingGroups"),ei=async e=>{let t=await (0,h.getRouterSettingsCall)(e),l=t?.current_values??{},a=(Array.isArray(t?.fields)?t.fields:[]).find(e=>e?.field_name==="routing_strategy");return{routingGroups:Array.isArray(l.routing_groups)?l.routing_groups:[],routingStrategy:l.routing_strategy??null,availableStrategies:Array.isArray(a?.options)?a.options:[]}},eo=(0,es.createQueryKeys)("routerFields"),ed=async e=>{try{let t=h.proxyBaseUrl?`${h.proxyBaseUrl}/router/fields`:"/router/fields",l=await fetch(t,{method:"GET",headers:{[(0,h.getGlobalLitellmHeaderName)()]:`Bearer ${e}`,"Content-Type":"application/json"}});if(!l.ok){let e=await l.json(),t=e?.error&&(e.error.message||e.error)||e?.message||e?.detail||e?.error||JSON.stringify(e);throw Error(t)}return await l.json()}catch(e){throw console.error("Failed to fetch router fields:",e),e}};var ec=e.i(625901),eu=e.i(592392),eg=e.i(332102);e.i(707701);var em=e.i(807235),ep=e.i(997625),ef=e.i(466828);let eh={"simple-shuffle":"Simple Shuffle","least-busy":"Least Busy","usage-based-routing":"Usage Based","latency-based-routing":"Latency Based"},ex=e=>eh[e]??e,ey=e=>e.models[0]??"",eb=[{value:"curl",label:"cURL",language:"bash",build:(e,t)=>`curl -X POST '${t}/v1/chat/completions' \\ + -H 'Content-Type: application/json' \\ + -H 'Authorization: Bearer $LITELLM_API_KEY' \\ + -d '{ + "model": "${ey(e)}", + "messages": [{"role": "user", "content": "Hello!"}] + }'`},{value:"python",label:"Python (OpenAI SDK)",language:"python",build:(e,t)=>`from openai import OpenAI + +client = OpenAI( + api_key="$LITELLM_API_KEY", + base_url="${t}", +) + +response = client.chat.completions.create( + model="${ey(e)}", + messages=[{"role": "user", "content": "Hello!"}], +) + +print(response)`},{value:"javascript",label:"JavaScript (OpenAI SDK)",language:"javascript",build:(e,t)=>`import OpenAI from "openai"; + +const client = new OpenAI({ + apiKey: process.env.LITELLM_API_KEY, + baseURL: "${t}", +}); + +const response = await client.chat.completions.create({ + model: "${ey(e)}", + messages: [{ role: "user", content: "Hello!" }], +}); + +console.log(response);`}];function e_({group:e,baseUrl:l}){return(0,t.jsxs)("div",{className:"border-y bg-muted/40 px-4 py-4",children:[(0,t.jsxs)("div",{className:"mb-2 flex items-center gap-2",children:[(0,t.jsx)(ep.Code2,{className:"size-4 text-primary"}),(0,t.jsx)("span",{className:"text-sm font-medium text-foreground",children:"How routing works for this group"})]}),(0,t.jsxs)("p",{className:"mb-3 text-sm text-muted-foreground",children:["Callers request any model in the group by name; LiteLLM picks a deployment behind the scenes using the"," ",(0,t.jsx)("span",{className:"font-medium text-foreground",children:ex(e.routing_strategy)})," strategy."]}),(0,t.jsxs)(f.Tabs,{defaultValue:"curl",children:[(0,t.jsx)(f.TabsList,{variant:"line",className:"h-auto w-full justify-start rounded-none border-b p-0",children:eb.map(e=>(0,t.jsx)(f.TabsTrigger,{value:e.value,className:"flex-none rounded-none px-4 py-2",children:e.label},e.value))}),eb.map(a=>(0,t.jsx)(f.TabsContent,{value:a.value,className:"pt-3",children:(0,t.jsx)(ef.default,{language:a.language,code:a.build(e,l)})},a.value))]})]})}let ej=(0,e.i(475254).default)("git-branch",[["line",{x1:"6",x2:"6",y1:"3",y2:"15",key:"17qcm7"}],["circle",{cx:"18",cy:"6",r:"3",key:"1h7g24"}],["circle",{cx:"6",cy:"18",r:"3",key:"fqmcym"}],["path",{d:"M18 9a9 9 0 0 1-9 9",key:"n2h4wq"}]]);var ev=e.i(541071),eC=e.i(727612),ek=e.i(494862),ew=e.i(997422),eS=e.i(547227),eN=e.i(519455),eT=e.i(755146),eM=e.i(115504);function eA({group:e,onEdit:l,onDelete:a}){return(0,t.jsxs)(eT.DropdownMenu,{children:[(0,t.jsx)(eT.DropdownMenuTrigger,{"aria-label":`Open actions for ${e.group_name}`,"data-testid":`routing-group-actions-${e.group_name}`,className:(0,eM.cn)((0,eN.buttonVariants)({variant:"ghost",size:"icon-sm"}),"text-muted-foreground"),children:(0,t.jsx)(ev.MoreHorizontal,{className:"size-4"})}),(0,t.jsxs)(eT.DropdownMenuContent,{align:"end",className:"w-44",children:[(0,t.jsxs)(eT.DropdownMenuItem,{"data-testid":"routing-group-action-edit",onClick:()=>l(e),children:[(0,t.jsx)(z.Pencil,{}),"Edit"]}),(0,t.jsxs)(eT.DropdownMenuItem,{variant:"destructive","data-testid":"routing-group-action-delete",onClick:()=>a(e),children:[(0,t.jsx)(eC.Trash2,{}),"Delete"]})]})]})}function eI(){return(0,t.jsxs)("div",{className:"flex flex-col items-center gap-1 py-6",children:[(0,t.jsx)("div",{className:"mb-1 flex size-10 items-center justify-center rounded-lg bg-muted",children:(0,t.jsx)(eg.Inbox,{className:"size-5 text-muted-foreground"})}),(0,t.jsx)("div",{className:"text-sm font-medium text-foreground",children:"No routing groups yet"}),(0,t.jsx)("div",{className:"text-sm text-muted-foreground",children:"Create a group to load-balance a set of models behind one name."})]})}let eF=({groups:e,isLoading:a,onEdit:r,onDelete:s,proxyBaseUrl:n})=>{let[i,o]=(0,l.useState)([]),[d,c]=(0,l.useState)({}),u=n&&n.trim()?n:window.location?.origin?window.location.origin:"",g=(0,l.useCallback)(e=>{c(t=>{let l=!0===t?{}:t;return{...l,[e.group_name]:!0!==l[e.group_name]}})},[]),m=(0,l.useMemo)(()=>(({onEdit:e,onDelete:l,onToggleUsage:a})=>[{id:"group_name",accessorKey:"group_name",meta:{title:"Group Name",skeleton:"text"},header:({column:e})=>(0,t.jsx)(ek.DataTableSortHeader,{column:e,title:"Group Name"}),size:240,enableSorting:!0,cell:({row:e})=>(0,t.jsx)(ew.IdentityCell,{title:e.original.group_name,className:"max-w-60",onClick:()=>a(e.original)})},{id:"models",meta:{title:"Models",skeleton:"chips"},header:"Models",size:320,enableSorting:!1,cell:({row:e})=>(0,t.jsx)(eS.ModelsCell,{models:e.original.models})},{id:"routing_strategy",accessorKey:"routing_strategy",meta:{title:"Strategy",skeleton:"text"},header:({column:e})=>(0,t.jsx)(ek.DataTableSortHeader,{column:e,title:"Strategy"}),size:180,enableSorting:!0,cell:({row:e})=>(0,t.jsxs)("span",{className:"flex items-center gap-1.5 text-sm",children:[(0,t.jsx)(ej,{className:"size-4 shrink-0 text-muted-foreground"}),ex(e.original.routing_strategy)]})},{id:"actions",meta:{className:"text-right",headerClassName:"text-right"},header:()=>(0,t.jsx)("span",{className:"sr-only",children:"Actions"}),size:64,enableSorting:!1,enableHiding:!1,cell:({row:a})=>(0,t.jsx)("div",{className:"flex justify-end",children:(0,t.jsx)(eA,{group:a.original,onEdit:e,onDelete:l})})}])({onEdit:r,onDelete:s,onToggleUsage:g}),[r,s,g]);return(0,t.jsx)(em.DataTable,{data:e,columns:m,getRowId:e=>e.group_name,sortingMode:"client",sorting:i,onSortingChange:o,expanded:d,onExpandedChange:c,getRowCanExpand:()=>!0,renderSubComponent:({row:e})=>(0,t.jsx)(e_,{group:e.original,baseUrl:u}),isLoading:a,loadingMessage:"Loading routing groups…",noDataMessage:(0,t.jsx)(eI,{}),size:"compact"})};var eL=e.i(808613);let{Text:eE,Paragraph:eO}=A.Typography,eB=new Set(["latency-based-routing","usage-based-routing"]),eD=/^[A-Za-z0-9._-]+$/,eP=({open:e,mode:a,initialValue:r,availableStrategies:s,strategyDescriptions:n,modelOptions:i,existingGroupNames:o,onClose:d,onSubmit:c,saving:u})=>{let[g]=eL.Form.useForm(),m=eL.Form.useWatch("routing_strategy",g),p={group_name:r?.group_name??"",models:r?.models??[],routing_strategy:r?.routing_strategy??s[0]??"simple-shuffle",routing_strategy_args:r?.routing_strategy_args?JSON.stringify(r.routing_strategy_args,null,2):""},f=(0,l.useMemo)(()=>new Set(o.filter(e=>e!==r?.group_name).map(e=>e.toLowerCase())),[o,r]),h=async()=>{let e=await g.validateFields(),t=eB.has(String(e.routing_strategy)),l=null;if(t&&e.routing_strategy_args&&e.routing_strategy_args.trim())try{l=JSON.parse(e.routing_strategy_args)}catch{g.setFields([{name:"routing_strategy_args",errors:["Must be valid JSON"]}]);return}await c({group_name:e.group_name.trim(),models:e.models,routing_strategy:e.routing_strategy,routing_strategy_args:l})};return(0,t.jsx)(D.Modal,{title:"create"===a?"Create Routing Group":`Edit ${r?.group_name??""}`,open:e,onCancel:d,onOk:h,okText:"create"===a?"Create Group":"Save Changes",cancelText:"Cancel",confirmLoading:u,destroyOnClose:!0,width:560,children:(0,t.jsxs)(eL.Form,{form:g,layout:"vertical",preserve:!1,initialValues:p,children:[(0,t.jsx)(eL.Form.Item,{label:"Group Name",name:"group_name",rules:[{required:!0,message:"Group name is required"},{max:64,message:"Must be 64 characters or fewer"},{pattern:eD,message:"Only letters, numbers, dot, underscore, and dash are allowed"},{validator:(e,t)=>t&&f.has(t.trim().toLowerCase())?Promise.reject(Error("A group with this name already exists")):Promise.resolve()}],extra:"Use this name as the model in API calls — LiteLLM routes the request to one of the group's models.",children:(0,t.jsx)(Y.Input,{placeholder:"fast-chat",disabled:"edit"===a})}),(0,t.jsx)(eL.Form.Item,{label:"Models",name:"models",rules:[{required:!0,message:"Select at least one model"}],extra:"Models from your model list that this group routes between.",children:(0,t.jsx)(y.Select,{mode:"multiple",allowClear:!0,placeholder:"Select models",options:i.map(e=>({label:e,value:e})),optionFilterProp:"label"})}),(0,t.jsx)(eL.Form.Item,{label:"Routing Strategy",name:"routing_strategy",rules:[{required:!0,message:"Strategy is required"}],children:(0,t.jsx)(y.Select,{options:s.map(e=>({label:e,value:e})),placeholder:"Select strategy"})}),m&&n[m]&&(0,t.jsx)(eO,{className:"text-xs text-gray-500 -mt-2 mb-4",children:n[m]}),eB.has(String(m))&&(0,t.jsx)(eL.Form.Item,{label:"Strategy Arguments (JSON)",name:"routing_strategy_args",extra:"latency-based-routing"===m?'Example: { "ttl": 3600, "lowest_latency_buffer": 0 }':'Example: { "ttl": 60 }',children:(0,t.jsx)(Y.Input.TextArea,{rows:4,placeholder:'{ "ttl": 3600 }',className:"font-mono text-xs"})}),(0,t.jsx)(X.Space,{direction:"vertical",className:"w-full mt-2",children:(0,t.jsx)(eE,{type:"secondary",className:"text-xs",children:"Models not claimed by an explicit group fall through to the proxy's top-level routing strategy."})})]},"edit"===a?`edit-${r?.group_name??""}`:"create")})},{Text:eR}=A.Typography,e$=()=>{let{data:e,isLoading:a,refetch:r,isFetching:s}=(()=>{let{accessToken:e,userId:t,userRole:l}=(0,el.default)();return(0,q.useQuery)({queryKey:en.lists(),queryFn:()=>ei(e),enabled:!!(e&&t&&l)})})(),{data:n}=(()=>{let{accessToken:e,userId:t,userRole:l}=(0,el.default)();return(0,q.useQuery)({queryKey:eo.detail("fields"),queryFn:async()=>await ed(e),enabled:!!(e&&t&&l)})})(),{data:i}=(0,ec.useModelHub)(),{accessToken:o}=(0,el.default)(),d=(0,eu.default)(o),c=(()=>{let{accessToken:e}=(0,el.default)(),t=(0,er.useQueryClient)();return(0,ea.useMutation)({mutationFn:t=>(0,h.setCallbacksCall)(e,{router_settings:{routing_groups:t}}),onSuccess:()=>{t.invalidateQueries({queryKey:en.lists()})}})})(),[u,g]=(0,l.useState)(""),[m,p]=(0,l.useState)(!1),[f,x]=(0,l.useState)("create"),[y,b]=(0,l.useState)(null),[_,C]=(0,l.useState)(null),k=e?.routingGroups??[],w=(0,l.useMemo)(()=>{let e=u.trim().toLowerCase();return e?k.filter(t=>t.group_name.toLowerCase().includes(e)||t.routing_strategy.toLowerCase().includes(e)||t.models.some(t=>t.toLowerCase().includes(e))):k},[k,u]),S=(0,l.useMemo)(()=>e?.availableStrategies?.length?e.availableStrategies:n?.fields?.find(e=>"routing_strategy"===e.field_name)?.options??[],[e?.availableStrategies,n]),N=n?.routing_strategy_descriptions??{},T=(0,l.useMemo)(()=>Array.from(new Set((i?.data??[]).map(e=>e.model_group).filter(e=>!!e))),[i]),M=async e=>{let t="create"===f?[...k,e]:k.map(t=>t.group_name===y?.group_name?e:t);try{await c.mutateAsync(t),v.default.success("create"===f?`Created routing group "${e.group_name}"`:`Updated routing group "${e.group_name}"`),p(!1)}catch(e){v.default.error(e instanceof Error?e.message:"Failed to save routing group")}},A=async()=>{if(!_)return;let e=k.filter(e=>e.group_name!==_.group_name);try{await c.mutateAsync(e),v.default.success(`Deleted routing group "${_.group_name}"`),C(null)}catch(e){v.default.error(e instanceof Error?e.message:"Failed to delete routing group")}};return(0,t.jsxs)(X.Space,{direction:"vertical",size:16,className:"w-full",children:[(0,t.jsxs)(V.Card,{bodyStyle:{padding:16},children:[(0,t.jsxs)(W.Flex,{justify:"space-between",align:"center",gap:12,className:"mb-4",children:[(0,t.jsx)(Y.Input,{allowClear:!0,prefix:(0,t.jsx)(et.SearchOutlined,{className:"text-gray-400"}),placeholder:"Search groups...",value:u,onChange:e=>g(e.target.value),className:"max-w-sm"}),(0,t.jsxs)(W.Flex,{align:"center",gap:12,children:[(0,t.jsx)(j.Button,{icon:(0,t.jsx)(ee.ReloadOutlined,{}),onClick:()=>r(),loading:s&&!a,children:"Refresh"}),(0,t.jsx)(j.Button,{type:"primary",icon:(0,t.jsx)(Z.PlusOutlined,{}),onClick:()=>{x("create"),b(null),p(!0)},children:"Create Group"}),(0,t.jsxs)(eR,{type:"secondary",className:"text-sm whitespace-nowrap",children:["Showing ",w.length," ",1===w.length?"result":"results"]})]})]}),(0,t.jsx)(eF,{groups:w,isLoading:a,onEdit:e=>{x("edit"),b(e),p(!0)},onDelete:e=>C(e),proxyBaseUrl:d.LITELLM_UI_API_DOC_BASE_URL?.trim()||d.PROXY_BASE_URL||""})]}),(0,t.jsx)(eP,{open:m,mode:f,initialValue:y,availableStrategies:S,strategyDescriptions:N,modelOptions:T,existingGroupNames:k.map(e=>e.group_name),onClose:()=>p(!1),onSubmit:M,saving:c.isPending}),(0,t.jsx)(D.Modal,{open:!!_,title:"Delete routing group?",okText:"Delete",okButtonProps:{danger:!0,loading:c.isPending},cancelText:"Cancel",onOk:A,onCancel:()=>C(null),children:(0,t.jsxs)(eR,{children:["Models in ",(0,t.jsx)(eR,{strong:!0,children:_?.group_name}),"will fall back to the proxy's top-level routing strategy. This cannot be undone."]})})]})},eH="enable_anthropic_prompt_caching",eq="anthropic_prompt_caching_ttl",ez=({setting:e,onChange:l})=>"Integer"===e.field_type?(0,t.jsx)(x.InputNumber,{step:1,value:e.field_value,onChange:t=>l(e.field_name,t)}):"Boolean"===e.field_type?(0,t.jsx)(p.Switch,{checked:!0===e.field_value||"true"===e.field_value,onChange:t=>l(e.field_name,t)}):"Float"===e.field_type?(0,t.jsx)(x.InputNumber,{min:0,max:1,step:.05,value:e.field_value,onChange:t=>l(e.field_name,t)}):"Dollar"===e.field_type?(0,t.jsx)(x.InputNumber,{min:.01,step:.25,prefix:"$",value:e.field_value,onChange:t=>l(e.field_name,t)}):"Select"===e.field_type?(0,t.jsx)(y.Select,{allowClear:!0,style:{minWidth:"8rem"},placeholder:"Default",value:e.field_value||void 0,options:(e.field_options??[]).map(e=>({label:e,value:e})),onChange:t=>l(e.field_name,t??"")}):null,eG=({accessToken:e,settings:l,onChange:r})=>{let s=l.find(e=>e.field_name===eH),n=l.find(e=>e.field_name===eq);if(!s)return null;let i=!0===s.field_value||"true"===s.field_value,o=(t,l)=>{r(t,l),""===l||null==l?(0,h.deleteConfigFieldSetting)(e,t):(0,h.updateConfigFieldSetting)(e,t,l)};return(0,t.jsxs)(a.Card,{children:[(0,t.jsx)(c.Title,{children:"Prompt Caching"}),(0,t.jsxs)("div",{className:"mt-6 flex items-start justify-between gap-8",children:[(0,t.jsxs)("div",{className:"max-w-2xl",children:[(0,t.jsx)(u.Text,{className:"font-medium",children:"Automatic Anthropic prompt caching"}),(0,t.jsx)("p",{className:"mt-1 text-xs text-gray-500",children:s.field_description})]}),(0,t.jsx)(p.Switch,{checked:i,onChange:e=>o(eH,e)})]}),n&&(0,t.jsxs)("div",{className:"mt-6 flex items-start justify-between gap-8",children:[(0,t.jsxs)("div",{className:"max-w-2xl",children:[(0,t.jsx)(u.Text,{className:`font-medium ${i?"":"text-gray-400"}`,children:"Cache lifetime (TTL)"}),(0,t.jsx)("p",{className:"mt-1 text-xs text-gray-500",children:n.field_description})]}),(0,t.jsx)(y.Select,{allowClear:!0,disabled:!i,style:{minWidth:"10rem"},placeholder:"5m (default)",value:n.field_value||void 0,options:(n.field_options??[]).map(e=>({label:e,value:e})),onChange:e=>o(eq,e??"")})]})]})};e.s(["PromptCachingPanel",0,eG,"default",0,({accessToken:e,userRole:c,userID:p})=>{let[x,y]=(0,l.useState)([]);(0,l.useEffect)(()=>{e&&(0,h.getGeneralSettingsCall)(e).then(e=>{y(e)})},[e]);let j=(e,t)=>{y(x.map(l=>l.field_name===e?{...l,field_value:t}:l))};return e?(0,t.jsx)("div",{className:"w-full",children:(0,t.jsxs)(f.Tabs,{defaultValue:"loadbalancing",className:"h-[75vh] w-full",children:[(0,t.jsxs)(f.TabsList,{variant:"line",className:"mx-8 mt-4",children:[(0,t.jsx)(f.TabsTrigger,{value:"loadbalancing",children:"Loadbalancing"}),(0,t.jsx)(f.TabsTrigger,{value:"routing-groups",children:"Routing Groups"}),(0,t.jsx)(f.TabsTrigger,{value:"fallbacks",children:"Fallbacks"}),(0,t.jsx)(f.TabsTrigger,{value:"prompt-caching",children:"Prompt Caching"}),(0,t.jsx)(f.TabsTrigger,{value:"general",children:"General"})]}),(0,t.jsx)(f.TabsContent,{value:"loadbalancing",className:"px-8 py-6",children:(0,t.jsx)(k,{accessToken:e,userRole:c,userID:p})}),(0,t.jsx)(f.TabsContent,{value:"routing-groups",className:"px-8 py-6",children:(0,t.jsx)(e$,{})}),(0,t.jsx)(f.TabsContent,{value:"fallbacks",className:"px-8 py-6",children:(0,t.jsx)(Q,{accessToken:e,userRole:c,userID:p})}),(0,t.jsx)(f.TabsContent,{value:"prompt-caching",className:"px-8 py-6",children:(0,t.jsx)(eG,{accessToken:e,settings:x,onChange:j})}),(0,t.jsx)(f.TabsContent,{value:"general",className:"px-8 py-6",children:(0,t.jsx)(a.Card,{children:(0,t.jsxs)(r.Table,{children:[(0,t.jsx)(s.TableHead,{children:(0,t.jsxs)(n.TableRow,{children:[(0,t.jsx)(i.TableHeaderCell,{children:"Setting"}),(0,t.jsx)(i.TableHeaderCell,{children:"Value"}),(0,t.jsx)(i.TableHeaderCell,{children:"Status"}),(0,t.jsx)(i.TableHeaderCell,{children:"Action"})]})}),(0,t.jsx)(d.TableBody,{children:x.filter(e=>"TypedDictionary"!==e.field_type&&"prompt_caching"!==e.field_tab).map((l,a)=>(0,t.jsxs)(n.TableRow,{children:[(0,t.jsxs)(o.TableCell,{children:[(0,t.jsx)(u.Text,{children:l.field_name}),(0,t.jsx)("p",{style:{fontSize:"0.65rem",color:"#808080",fontStyle:"italic"},className:"mt-1",children:l.field_description})]}),(0,t.jsx)(o.TableCell,{children:(0,t.jsx)(ez,{setting:l,onChange:j})}),(0,t.jsx)(o.TableCell,{children:!0==l.stored_in_db?(0,t.jsx)(_.StatusBadge,{tone:"success",label:"In DB"}):!1==l.stored_in_db?(0,t.jsx)(_.StatusBadge,{tone:"neutral",label:"In Config"}):(0,t.jsx)(_.StatusBadge,{tone:"neutral",label:"Not Set"})}),(0,t.jsxs)(o.TableCell,{children:[(0,t.jsx)(g.Button,{onClick:()=>(t=>{if(!e)return;let l=x.find(e=>e.field_name===t)?.field_value;if(null!=l&&void 0!=l)try{(0,h.updateConfigFieldSetting)(e,t,l);let a=x.map(e=>e.field_name===t?{...e,stored_in_db:!0}:e);y(a)}catch(e){}})(l.field_name),children:"Update"}),(0,t.jsx)(m.Icon,{icon:b.TrashIcon,color:"red",onClick:()=>(t=>{if(e)try{(0,h.deleteConfigFieldSetting)(e,t);let l=x.map(e=>e.field_name===t?{...e,stored_in_db:null,field_value:e.field_default_value??null}:e);y(l)}catch(e){}})(l.field_name),children:"Reset"})]})]},a))})]})})})]})}):null}],863679)}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/04y2hqzy08peg.js b/litellm/proxy/_experimental/out/_next/static/chunks/04y2hqzy08peg.js deleted file mode 100644 index 73873aa04f2..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/04y2hqzy08peg.js +++ /dev/null @@ -1 +0,0 @@ -(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,56567,838932,788259,e=>{"use strict";var t=e.i(843476),l=e.i(135214),a=e.i(109799),s=e.i(912598),i=e.i(907308),r=e.i(602869),n=e.i(266027);let o=(0,e.i(243652).createQueryKeys)("guardrails"),d=()=>{let{accessToken:e,userId:t,userRole:a}=(0,l.default)();return(0,n.useQuery)({queryKey:o.list({}),queryFn:async()=>(0,r.getGuardrailsList)(e),enabled:!!(e&&t&&a),select:e=>{let t=e?.guardrails??[],l=new Set,a=new Set;for(let e of t)e.litellm_params?.default_on?l.add(e.guardrail_name):a.add(e.guardrail_name);return{guardrails:t,globalGuardrailNames:l,optionalGuardrailNames:a}}})};e.s(["useGuardrails",0,d],838932);var m=e.i(500330),c=e.i(11751),u=e.i(708347),g=e.i(751904),h=e.i(160818),p=e.i(827252),_=e.i(564897),x=e.i(646563),b=e.i(987432),j=e.i(530212),y=e.i(677667),f=e.i(130643),v=e.i(898667),T=e.i(389083),S=e.i(304967),w=e.i(350967),N=e.i(599724),C=e.i(779241),k=e.i(629569),M=e.i(464571),I=e.i(808613),A=e.i(311451),F=e.i(28651),P=e.i(199133),O=e.i(770914),D=e.i(790848),z=e.i(653496),L=e.i(262218),B=e.i(592968),R=e.i(888259),U=e.i(678784),E=e.i(118366),V=e.i(271645),G=e.i(9314),K=e.i(533882),$=e.i(552130),W=e.i(127952);function q({className:e,value:l,onChange:a}){return(0,t.jsxs)(P.Select,{className:e,value:l,onChange:a,children:[(0,t.jsx)(P.Select.Option,{value:"24h",children:"Daily"}),(0,t.jsx)(P.Select.Option,{value:"7d",children:"Weekly"}),(0,t.jsx)(P.Select.Option,{value:"30d",children:"Monthly"})]})}var H=e.i(844565),J=e.i(355619);let Y=function({globalGuardrailNames:e,teamGuardrails:l=[],optedOutGlobalGuardrails:a=[],killSwitchOn:s=!1,variant:i="card",className:r=""}){let n=new Set(a),o=Array.from(e).filter(e=>!n.has(e)),d=l.filter(t=>!e.has(t)),m=s||0!==o.length||0!==d.length?(0,t.jsxs)("div",{className:"flex flex-col gap-4",children:[(0,t.jsxs)("div",{children:[(0,t.jsxs)("span",{className:"block text-sm font-medium text-gray-700 mb-2",children:[(0,t.jsx)(h.GlobalOutlined,{style:{marginInlineEnd:4},"aria-label":"Global guardrail"}),"Global"]}),s?(0,t.jsx)(L.Tag,{color:"gold",children:"Bypassed for this team"}):o.length>0?(0,t.jsx)("div",{className:"flex flex-wrap gap-2",children:o.map(e=>(0,t.jsx)(L.Tag,{color:"blue",children:e},e))}):(0,t.jsx)("span",{className:"block text-sm text-gray-500",children:"None configured"})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("span",{className:"block text-sm font-medium text-gray-700 mb-2",children:"Team-specific"}),d.length>0?(0,t.jsx)("div",{className:"flex flex-wrap gap-2",children:d.map(e=>(0,t.jsx)(L.Tag,{color:"blue",children:e},e))}):(0,t.jsx)("span",{className:"block text-sm text-gray-500",children:"None configured"})]})]}):(0,t.jsx)("span",{className:"block text-gray-500",children:"No guardrails configured"});return"card"===i?(0,t.jsxs)("div",{className:`bg-white border border-gray-200 rounded-lg p-6 ${r}`,children:[(0,t.jsx)("div",{className:"flex items-center gap-2 mb-6",children:(0,t.jsxs)("div",{children:[(0,t.jsx)("span",{className:"block font-semibold text-gray-900",children:"Guardrails Settings"}),(0,t.jsx)("span",{className:"block text-xs text-gray-500",children:"Global and team-specific guardrails applied to this team"})]})}),m]}):(0,t.jsxs)("div",{className:`${r}`,children:[(0,t.jsx)("span",{className:"block font-medium text-gray-900 mb-3",children:"Guardrails Settings"}),m]})};var Q=e.i(643449),Z=e.i(75921),X=e.i(390605),ee=e.i(162386),et=e.i(727749),el=e.i(384767),ea=e.i(435451),es=e.i(916940);let ei=({onChange:e,value:l,className:a,accessToken:s,placeholder:i="Select search tools (optional)",disabled:n=!1})=>{let[o,d]=(0,V.useState)([]),[m,c]=(0,V.useState)(!1);return(0,V.useEffect)(()=>{(async()=>{if(s){c(!0);try{let e=await (0,r.fetchSearchTools)(s),t=Array.isArray(e?.search_tools)?e.search_tools:Array.isArray(e?.data)?e.data:[];d(t.map(e=>e?.search_tool_name).filter(e=>"string"==typeof e&&e.length>0).map(e=>({label:e,value:e})))}catch(e){console.error("Failed to load search tools:",e)}finally{c(!1)}}})()},[s]),(0,t.jsx)(P.Select,{mode:"multiple",allowClear:!0,showSearch:!0,optionFilterProp:"label",placeholder:i,onChange:e,value:l,loading:m,className:a,options:o,style:{width:"100%"},disabled:n})};e.s(["default",0,ei],788259);var er=e.i(183588),en=e.i(460285),eo=e.i(276173),ed=e.i(91979),em=e.i(269200),ec=e.i(942232),eu=e.i(977572),eg=e.i(427612),eh=e.i(64848),ep=e.i(496020),e_=e.i(536916),ex=e.i(21548);let eb={"/key/generate":"Member can generate a virtual key for this team","/key/service-account/generate":"Member can generate a service account key (not belonging to any user) for this team","/key/update":"Member can update a virtual key belonging to this team","/key/delete":"Member can delete a virtual key belonging to this team","/key/info":"Member can get info about a virtual key belonging to this team","/key/regenerate":"Member can regenerate a virtual key belonging to this team","/key/{key_id}/regenerate":"Member can regenerate a virtual key belonging to this team","/key/list":"Member can list virtual keys belonging to this team","/key/block":"Member can block a virtual key belonging to this team","/key/unblock":"Member can unblock a virtual key belonging to this team","/key/access_group_assignment":"Member can assign access groups to virtual keys for this team","/team/daily/activity":"Member can view all team usage data (not just their own)","/spend/logs":"Member can view spend logs for the entire team (not just their own)"},ej=({teamId:e,accessToken:l,canEditTeam:a})=>{let[s,i]=(0,V.useState)([]),[n,o]=(0,V.useState)([]),[d,m]=(0,V.useState)(!0),[c,u]=(0,V.useState)(!1),[g,h]=(0,V.useState)(!1),p=async()=>{try{if(m(!0),!l)return;let t=await (0,r.getTeamPermissionsCall)(l,e),a=t.all_available_permissions||[];i(a);let s=t.team_member_permissions||[];o(s),h(!1)}catch(e){et.default.fromBackend("Failed to load permissions"),console.error("Error fetching permissions:",e)}finally{m(!1)}};(0,V.useEffect)(()=>{p()},[e,l]);let _=async()=>{try{if(!l)return;u(!0),await (0,r.teamPermissionsUpdateCall)(l,e,n),et.default.success("Permissions updated successfully"),h(!1)}catch(e){et.default.fromBackend("Failed to update permissions"),console.error("Error updating permissions:",e)}finally{u(!1)}};if(d)return(0,t.jsx)("div",{className:"p-6 text-center",children:"Loading permissions..."});let x=s.length>0;return(0,t.jsxs)(S.Card,{className:"bg-white shadow-md rounded-md p-6",children:[(0,t.jsxs)("div",{className:"flex flex-col sm:flex-row justify-between items-start sm:items-center border-b pb-4 mb-6",children:[(0,t.jsx)(k.Title,{className:"mb-2 sm:mb-0",children:"Member Permissions"}),a&&g&&(0,t.jsxs)("div",{className:"flex gap-3",children:[(0,t.jsx)(M.Button,{icon:(0,t.jsx)(ed.ReloadOutlined,{}),onClick:()=>{p()},children:"Reset"}),(0,t.jsx)(M.Button,{onClick:_,loading:c,type:"primary",icon:(0,t.jsx)(b.SaveOutlined,{}),children:"Save Changes"})]})]}),(0,t.jsx)(N.Text,{className:"mb-6 text-gray-600",children:"Control what team members can do when they are not team admins."}),x?(0,t.jsx)("div",{className:"overflow-x-auto",children:(0,t.jsxs)(em.Table,{className:" min-w-full",children:[(0,t.jsx)(eg.TableHead,{children:(0,t.jsxs)(ep.TableRow,{children:[(0,t.jsx)(eh.TableHeaderCell,{children:"Method"}),(0,t.jsx)(eh.TableHeaderCell,{children:"Endpoint"}),(0,t.jsx)(eh.TableHeaderCell,{children:"Description"}),(0,t.jsx)(eh.TableHeaderCell,{className:"sticky right-0 bg-white shadow-[-4px_0_4px_-4px_rgba(0,0,0,0.1)] text-center",children:"Allow Access"})]})}),(0,t.jsx)(ec.TableBody,{children:s.map(e=>{let l=(e=>{let t=e.includes("/info")||e.includes("/list")||e.includes("/activity")||"/spend/logs"===e?"GET":"POST",l=eb[e];if(!l){for(let[t,a]of Object.entries(eb))if(e.includes(t)){l=a;break}}return l||(l=`Access ${e}`),{method:t,endpoint:e,description:l,route:e}})(e);return(0,t.jsxs)(ep.TableRow,{className:"hover:bg-gray-50 transition-colors",children:[(0,t.jsx)(eu.TableCell,{children:(0,t.jsx)("span",{className:`px-2 py-1 rounded text-xs font-medium ${"GET"===l.method?"bg-blue-100 text-blue-800":"bg-green-100 text-green-800"}`,children:l.method})}),(0,t.jsx)(eu.TableCell,{children:(0,t.jsx)("span",{className:"font-mono text-sm text-gray-800",children:l.endpoint})}),(0,t.jsx)(eu.TableCell,{className:"text-gray-700",children:l.description}),(0,t.jsx)(eu.TableCell,{className:"sticky right-0 bg-white shadow-[-4px_0_4px_-4px_rgba(0,0,0,0.1)] text-center",children:(0,t.jsx)(e_.Checkbox,{checked:n.includes(e),onChange:t=>{o(t.target.checked?[...n,e]:n.filter(t=>t!==e)),h(!0)},disabled:!a})})]},e)})})]})}):(0,t.jsx)("div",{className:"py-12",children:(0,t.jsx)(ex.Empty,{description:"No permissions available"})})]})};var ey=e.i(822315),ef=e.i(175712),ev=e.i(178654),eT=e.i(621192),eS=e.i(898586),ew=e.i(431703);let eN=async(e,t)=>{let l=(0,r.getProxyBaseUrl)(),a=l?`${l}/team/${encodeURIComponent(t)}/members/me`:`/team/${encodeURIComponent(t)}/members/me`,s=await fetch(a,{method:"GET",headers:{[(0,r.getGlobalLitellmHeaderName)()]:`Bearer ${e}`,"Content-Type":"application/json"}});if(404===s.status)return null;if(!s.ok){let e=await s.json().catch(()=>({}));throw Error((0,ew.deriveErrorMessage)(e))}return await s.json()},eC=(e,l)=>(0,t.jsxs)(O.Space,{size:4,children:[(0,t.jsx)(eS.Typography.Text,{type:"secondary",children:e}),(0,t.jsx)(B.Tooltip,{title:l,children:(0,t.jsx)(p.InfoCircleOutlined,{style:{color:"#8c8c8c"}})})]}),ek=(e,t=4)=>null==e?"0":(0,m.formatNumberWithCommas)(e,t),eM=e=>null==e?"Unlimited":(0,m.formatNumberWithCommas)(e,0);function eI({teamId:e}){let{data:a,isLoading:s,error:i}=(e=>{let{accessToken:t}=(0,l.default)();return(0,n.useQuery)({queryKey:["team",e,"members","me"],queryFn:()=>eN(t,e),enabled:!!(t&&e)})})(e);if(s)return(0,t.jsx)(ef.Card,{children:(0,t.jsx)(eS.Typography.Text,{type:"secondary",children:"Loading your membership info…"})});if(i)return(0,t.jsx)(ef.Card,{children:(0,t.jsx)(eS.Typography.Text,{type:"danger",children:i instanceof Error?i.message:"Failed to load your membership info for this team."})});if(!a)return(0,t.jsx)(ef.Card,{children:(0,t.jsx)(eS.Typography.Text,{type:"secondary",children:"No membership info available for the current user in this team."})});let r=a.litellm_budget_table??null,o=r?.max_budget??null,d=a.spend??0,m=a.total_spend??0,c=r?.tpm_limit??null,u=r?.rpm_limit??null,g=function(e){if(!e)return null;let t=(0,ey.default)(e);return t.isValid()?t.format("MMM D, YYYY"):null}(r?.budget_reset_at),h=r?.allowed_models??null;return(0,t.jsxs)(O.Space,{direction:"vertical",size:"middle",style:{width:"100%"},children:[(0,t.jsx)(ef.Card,{children:(0,t.jsxs)(eT.Row,{gutter:[24,16],children:[(0,t.jsxs)(ev.Col,{xs:24,sm:12,md:8,children:[(0,t.jsx)(eS.Typography.Text,{type:"secondary",children:"User"}),(0,t.jsx)("div",{style:{marginTop:4},children:(0,t.jsx)(eS.Typography.Text,{strong:!0,children:a.user_email||a.user_id})}),(0,t.jsx)(eS.Typography.Text,{type:"secondary",style:{fontSize:12,fontFamily:"monospace"},children:a.user_id})]}),(0,t.jsxs)(ev.Col,{xs:24,sm:12,md:8,children:[(0,t.jsx)(eS.Typography.Text,{type:"secondary",children:"Team Role"}),(0,t.jsx)("div",{style:{marginTop:4},children:(0,t.jsx)(L.Tag,{color:"admin"===a.role?"blue":"default",children:a.role||"user"})})]})]})}),(0,t.jsxs)(eT.Row,{gutter:[16,16],children:[(0,t.jsx)(ev.Col,{xs:24,md:12,children:(0,t.jsxs)(ef.Card,{children:[eC("Current Cycle Spend (USD)","Spend for the current budget cycle. Resets to $0 when the budget window rolls over."),(0,t.jsxs)("div",{style:{marginTop:8},children:[(0,t.jsxs)(eS.Typography.Title,{level:3,style:{margin:0},children:["$",ek(d,4)]}),(0,t.jsxs)(eS.Typography.Text,{type:"secondary",children:["of ",null===o?"Unlimited":`$${ek(o,4)}`]})]}),g&&(0,t.jsx)("div",{style:{marginTop:4},children:(0,t.jsxs)(eS.Typography.Text,{type:"secondary",children:["Resets ",g]})})]})}),(0,t.jsx)(ev.Col,{xs:24,md:12,children:(0,t.jsxs)(ef.Card,{children:[eC("Rate Limits","Your per-member rate limits within this team."),(0,t.jsxs)("div",{style:{marginTop:8},children:[(0,t.jsxs)(eS.Typography.Text,{children:["TPM: ",eM(c)]}),(0,t.jsx)("br",{}),(0,t.jsxs)(eS.Typography.Text,{children:["RPM: ",eM(u)]})]})]})}),(0,t.jsx)(ev.Col,{xs:24,md:12,children:(0,t.jsxs)(ef.Card,{children:[eC("Total Spend (USD)","Cumulative spend across all budget cycles within this team."),(0,t.jsx)("div",{style:{marginTop:8},children:(0,t.jsxs)(eS.Typography.Title,{level:4,style:{margin:0},children:["$",ek(m,4)]})})]})}),(0,t.jsx)(ev.Col,{xs:24,md:12,children:(0,t.jsxs)(ef.Card,{children:[eC("Model Scope","Models you can access within this team."),(0,t.jsx)("div",{style:{marginTop:8},children:h&&h.length>0?(0,t.jsx)(O.Space,{wrap:!0,children:h.map(e=>(0,t.jsx)(L.Tag,{children:e},e))}):(0,t.jsx)(eS.Typography.Text,{children:"All Team Models"})})]})})]})]})}let eA="overview",eF="my-user",eP="virtual-keys",eO="members",eD="member-permissions",ez="settings",eL={[eA]:"Overview",[eF]:"My User",[eP]:"Virtual Keys",[eO]:"Members",[eD]:"Member Permissions",[ez]:"Settings"};var eB=e.i(292639);e.i(622826);var eR=e.i(200208),eU=e.i(964471),eE=e.i(294612);function eV({teamData:e,canEditTeam:a,handleMemberDelete:s,setSelectedEditMember:i,setIsEditMemberModalVisible:r,setIsAddMemberModalVisible:n}){let o=e=>{if(null==e)return"0";if("number"==typeof e){let t=Number(e);return t===Math.floor(t)?t.toString():(0,m.formatNumberWithCommas)(t,8).replace(/\.?0+$/,"")}return"0"},{data:d}=(0,eB.useUISettings)(),{userId:c,userRole:g}=(0,l.default)(),h=!!d?.values?.disable_team_admin_delete_team_user,_=(0,u.isUserTeamAdminForSingleTeam)(e.team_info.members_with_roles,c||""),x=(0,u.isProxyAdminRole)(g||""),b=[{title:(0,t.jsxs)(O.Space,{direction:"horizontal",children:["Model Scope",(0,t.jsx)(B.Tooltip,{title:"Models this member can access. Empty means they inherit all team models.",children:(0,t.jsx)(p.InfoCircleOutlined,{})})]}),key:"model_scope",render:(l,a)=>{let s=(t=>{if(!t)return null;let l=e.team_memberships.find(e=>e.user_id===t),a=l?.litellm_budget_table?.allowed_models;return a&&a.length>0?a:null})(a.user_id);if(!s)return(0,t.jsx)(eS.Typography.Text,{type:"secondary",children:"(all team models)"});let i=s.slice(0,2),r=s.length-i.length;return(0,t.jsxs)(O.Space,{wrap:!0,children:[i.map(e=>(0,t.jsx)(eS.Typography.Text,{code:!0,style:{fontSize:"12px"},children:e},e)),r>0&&(0,t.jsx)(B.Tooltip,{title:s.slice(2).join(", "),children:(0,t.jsxs)(eS.Typography.Text,{type:"secondary",children:["+",r," more"]})})]})}},{title:(0,t.jsxs)(O.Space,{direction:"horizontal",children:["Current Cycle Spend (USD)",(0,t.jsx)(B.Tooltip,{title:"Spend for the current budget cycle. Resets to $0 when the member's budget window rolls over. This is the value checked against the member's budget.",children:(0,t.jsx)(p.InfoCircleOutlined,{})})]}),key:"spend",render:(l,a)=>(0,t.jsx)(eU.MoneyCell,{value:(t=>{if(!t)return 0;let l=e.team_memberships.find(e=>e.user_id===t);return l?.spend??0})(a.user_id),decimals:4})},{title:(0,t.jsxs)(O.Space,{direction:"horizontal",children:["Total Spend (USD)",(0,t.jsx)(B.Tooltip,{title:"Cumulative spend by this member within this team, across all budget cycles. Tracking began 2026-04-21; spend from before that date is not included.",children:(0,t.jsx)(p.InfoCircleOutlined,{})})]}),key:"total_spend",render:(l,a)=>(0,t.jsx)(eU.MoneyCell,{value:(t=>{if(!t)return 0;let l=e.team_memberships.find(e=>e.user_id===t);return l?.total_spend??0})(a.user_id),decimals:4})},{title:"Team Member Budget (USD)",key:"budget",render:(l,a)=>(0,t.jsx)(eU.MoneyCell,{value:(t=>{if(!t)return null;let l=e.team_memberships.find(e=>e.user_id===t);return l?.litellm_budget_table?.max_budget??null})(a.user_id),decimals:4,emptyText:"Unlimited",showZero:!0})},{title:"Budget Reset",key:"budget_reset",render:(l,a)=>(0,t.jsx)(eR.DateCell,{value:(t=>{if(!t)return null;let l=e.team_memberships.find(e=>e.user_id===t);return l?.litellm_budget_table?.budget_reset_at??null})(a.user_id),precision:"date"})},{title:(0,t.jsxs)(O.Space,{direction:"horizontal",children:["Team Member Rate Limits",(0,t.jsx)(B.Tooltip,{title:"Rate limits for this member's usage within this team.",children:(0,t.jsx)(p.InfoCircleOutlined,{})})]}),key:"rate_limits",render:(l,a)=>(0,t.jsx)(eS.Typography.Text,{children:(t=>{if(!t)return"No Limits";let l=e.team_memberships.find(e=>e.user_id===t),a=l?.litellm_budget_table?.rpm_limit,s=l?.litellm_budget_table?.tpm_limit,i=[a?`${o(a)} RPM`:null,s?`${o(s)} TPM`:null].filter(Boolean);return i.length>0?i.join(" / "):"No Limits"})(a.user_id)})}];return(0,t.jsx)(eE.default,{members:e.team_info.members_with_roles,canEdit:a,onEdit:t=>{let l=e.team_memberships.find(e=>e.user_id===t.user_id);i({...t,max_budget_in_team:l?.litellm_budget_table?.max_budget||null,tpm_limit:l?.litellm_budget_table?.tpm_limit||null,rpm_limit:l?.litellm_budget_table?.rpm_limit||null,budget_duration:l?.litellm_budget_table?.budget_duration||null,allowed_models:l?.litellm_budget_table?.allowed_models||[]}),r(!0)},onDelete:s,onAddMember:()=>n(!0),roleColumnTitle:"Team Role",roleTooltip:"This role applies only to this team and is independent from the user's proxy-level role.",extraColumns:b,showDeleteForMember:()=>x||a&&!_||_&&!h})}var eG=e.i(207082),eK=e.i(399536);e.i(707701);var e$=e.i(807235),eW=e.i(981080),eq=e.i(494862),eH=e.i(531649),eJ=e.i(793479),eY=e.i(741466),eQ=e.i(871943),eZ=e.i(502547),eX=e.i(655063),e0=e.i(752978),e1=e.i(282786),e4=e.i(304911),e2=e.i(146512),e6=e.i(20147);let e3=[{id:"created_at",desc:!0}];function e5({teamId:e,teamAlias:l,organization:a}){let[s,i]=(0,V.useState)(null),[r,n]=(0,V.useState)(e3),[o,d]=(0,V.useState)({pageIndex:0,pageSize:50}),[m,c]=(0,V.useState)([]),[u,g]=(0,V.useState)(!1),[h,p]=(0,V.useState)(""),[_]=(0,eX.useDebouncedValue)(h,{wait:eY.DEBOUNCE_WAIT_MS}),x=(0,V.useCallback)(e=>{p(e),d(e=>({...e,pageIndex:0}))},[]),b=(0,V.useCallback)(e=>{let t=m.find(t=>t.id===e);return"string"==typeof t?.value&&t.value.trim()?t.value.trim():void 0},[m]),j=r.length>0?r[0].id:"created_at",y=r.length>0?r[0].desc?"desc":"asc":"desc",f=o.pageIndex,v=o.pageSize,{data:S,isPending:w,isFetching:C,refetch:k}=(0,eG.useKeys)(f+1,v,{teamID:e,selectedKeyAlias:_.trim()||void 0,userID:b("user_id"),sortBy:j||void 0,sortOrder:y||void 0,expand:"user"}),M=(0,V.useMemo)(()=>{let e=S?.keys||[],t=a?.organization_id;return t?e.map(e=>({...e,organization_id:(e.organization_id??e.org_id)||t})):e},[S?.keys,a?.organization_id]),I=S?.total_count??0,[A,F]=(0,V.useState)({}),P=(0,V.useMemo)(()=>({team_id:e,team_alias:l||e,models:[],max_budget:null,budget_duration:null,tpm_limit:null,rpm_limit:null,organization_id:a?.organization_id||"",created_at:"",keys:[],members_with_roles:[],spend:0}),[e,l,a]),O=(0,V.useCallback)(()=>{k?.()},[k]);(0,V.useEffect)(()=>(window.addEventListener("storage",O),()=>window.removeEventListener("storage",O)),[O]);let D=(0,V.useCallback)(e=>{c(e),d(e=>({...e,pageIndex:0}))},[]),z=(0,V.useMemo)(()=>[{id:"token",accessorKey:"token",meta:{title:"Key ID"},header:({column:e})=>(0,t.jsx)(eq.DataTableSortHeader,{column:e,title:"Key ID",variant:"header-cycle"}),size:120,enableSorting:!0,cell:e=>(0,t.jsx)(eK.IdCell,{value:e.getValue(),onClick:()=>i(e.row.original)})},{id:"key_alias",accessorKey:"key_alias",meta:{title:"Key Alias"},header:({column:e})=>(0,t.jsx)(eq.DataTableSortHeader,{column:e,title:"Key Alias",variant:"header-cycle"}),size:150,enableSorting:!0,cell:e=>{let l=e.getValue(),a=e.cell.column.getSize();return(0,t.jsx)(B.Tooltip,{title:l,children:(0,t.jsx)("span",{className:"font-mono text-xs truncate block",style:{maxWidth:a,overflow:"hidden"},children:l??"-"})})}},{id:"key_name",accessorKey:"key_name",header:"Secret Key",size:120,enableSorting:!1,cell:e=>(0,t.jsx)("span",{className:"font-mono text-xs",children:e.getValue()})},{id:"organization_id",accessorKey:"organization_id",header:"Organization ID",size:140,enableSorting:!1,cell:e=>e.getValue()?e.renderValue():"-"},{id:"user_email",accessorKey:"user",header:"User Email",size:160,enableSorting:!1,cell:e=>{let l=e.getValue(),a=l?.user_email,s=e.cell.column.getSize();return(0,t.jsx)(B.Tooltip,{title:a,children:(0,t.jsx)("span",{className:"font-mono text-xs truncate block",style:{maxWidth:s,overflow:"hidden"},children:a??"-"})})}},{id:"user_id",accessorKey:"user_id",header:"User ID",size:70,enableSorting:!1,cell:e=>{let l=e.getValue(),a="default_user_id"===l?"Default Proxy Admin":l,s=e.cell.column.getSize();return(0,t.jsx)(B.Tooltip,{title:a,children:(0,t.jsx)("span",{className:"font-mono text-xs truncate block",style:{maxWidth:s,overflow:"hidden"},children:a??"-"})})}},{id:"created_at",accessorKey:"created_at",meta:{title:"Created At"},header:({column:e})=>(0,t.jsx)(eq.DataTableSortHeader,{column:e,title:"Created At",variant:"header-cycle"}),size:120,enableSorting:!0,cell:e=>(0,t.jsx)(eR.DateCell,{value:e.getValue(),precision:"date"})},{id:"created_by",accessorKey:"created_by",header:"Created By",size:130,enableSorting:!1,cell:e=>{let l=e.getValue();if(!l)return"-";let{created_by_user:a}=e.row.original,s=a?.user_alias??null,i=a?.user_email??null,r="default_user_id"===l,n=s||i||l,o=e.cell.column.getSize(),d=(0,t.jsx)("div",{className:"flex flex-col gap-2 text-xs min-w-[200px] max-w-[300px]",children:[{label:"User Alias",value:s},{label:"User Email",value:i},{label:"User ID",value:l}].map(({label:e,value:l})=>(0,t.jsxs)("div",{className:"flex flex-col min-w-0",children:[(0,t.jsx)("span",{className:"text-gray-400",children:e}),l?(0,t.jsx)(eS.Typography.Text,{className:"font-mono text-xs",ellipsis:{tooltip:l},copyable:!0,children:l}):(0,t.jsx)("span",{className:"font-mono",children:"-"})]},e))});return!r||s||i?(0,t.jsx)(e1.Popover,{content:d,trigger:"hover",placement:"bottomLeft",children:(0,t.jsx)("span",{className:"font-mono text-xs truncate block cursor-default",style:{maxWidth:o,overflow:"hidden"},children:n})}):(0,t.jsx)(e1.Popover,{content:d,trigger:"hover",placement:"bottomLeft",children:(0,t.jsx)("span",{className:"cursor-default",children:(0,t.jsx)(e4.default,{userId:l})})})}},{id:"updated_at",accessorKey:"updated_at",meta:{title:"Updated At"},header:({column:e})=>(0,t.jsx)(eq.DataTableSortHeader,{column:e,title:"Updated At",variant:"header-cycle"}),size:120,enableSorting:!0,cell:e=>(0,t.jsx)(eR.DateCell,{value:e.getValue(),precision:"date",fallback:"Never"})},{id:"last_active",accessorKey:"last_active",header:"Last Active",size:130,enableSorting:!1,cell:e=>(0,t.jsx)(eR.DateCell,{value:e.getValue(),precision:"date",fallback:"Unknown"})},{id:"expires",accessorKey:"expires",header:"Expires",size:120,enableSorting:!1,cell:e=>(0,t.jsx)(eR.DateCell,{value:e.getValue(),precision:"date",fallback:"Never"})},{id:"spend",accessorKey:"spend",meta:{title:"Spend (USD)"},header:({column:e})=>(0,t.jsx)(eq.DataTableSortHeader,{column:e,title:"Spend (USD)",variant:"header-cycle"}),size:100,enableSorting:!0,cell:e=>(0,t.jsx)(eU.MoneyCell,{value:e.getValue(),decimals:4})},{id:"max_budget",accessorKey:"max_budget",meta:{title:"Budget (USD)"},header:({column:e})=>(0,t.jsx)(eq.DataTableSortHeader,{column:e,title:"Budget (USD)",variant:"header-cycle"}),size:110,enableSorting:!0,cell:e=>(0,t.jsx)(eU.MoneyCell,{value:e.getValue(),decimals:0,emptyText:"Unlimited",showZero:!0})},{id:"budget_reset_at",accessorKey:"budget_reset_at",header:"Budget Reset",size:130,enableSorting:!1,cell:e=>(0,t.jsx)(eR.DateCell,{value:e.getValue(),fallback:"Never"})},{id:"models",accessorKey:"models",header:"Models",size:200,enableSorting:!1,cell:e=>{let l=e.getValue(),a=(0,e2.deriveKeyModelScope)(e.row.original.allowed_routes,e.row.original.key_type),s=a.hasModelAccess?(0,t.jsx)(T.Badge,{size:"xs",className:"mb-1",color:"red",children:(0,t.jsx)(N.Text,{children:"All Proxy Models"})}):(0,t.jsx)(B.Tooltip,{title:`Scoped to ${a.label} routes; this key cannot call any models`,children:(0,t.jsx)(T.Badge,{size:"xs",className:"mb-1",color:"gray",children:(0,t.jsx)(N.Text,{children:"No model access"})})});return(0,t.jsx)("div",{className:"flex flex-col py-2",children:Array.isArray(l)?(0,t.jsx)("div",{className:"flex flex-col",children:0===l.length?s:(0,t.jsx)(t.Fragment,{children:(0,t.jsxs)("div",{className:"flex items-start",children:[l.length>3&&(0,t.jsx)("div",{children:(0,t.jsx)(e0.Icon,{icon:A[e.row.id]?eQ.ChevronDownIcon:eZ.ChevronRightIcon,className:"cursor-pointer",size:"xs",onClick:()=>F(t=>({...t,[e.row.id]:!t[e.row.id]}))})}),(0,t.jsxs)("div",{className:"flex flex-wrap gap-1",children:[l.slice(0,3).map((e,l)=>"all-proxy-models"===e?(0,t.jsx)(T.Badge,{size:"xs",color:"red",children:(0,t.jsx)(N.Text,{children:"All Proxy Models"})},l):(0,t.jsx)(T.Badge,{size:"xs",color:"blue",children:(0,t.jsx)(N.Text,{children:e.length>30?`${(0,J.getModelDisplayName)(e).slice(0,30)}...`:(0,J.getModelDisplayName)(e)})},l)),l.length>3&&!A[e.row.id]&&(0,t.jsx)(T.Badge,{size:"xs",color:"gray",className:"cursor-pointer",children:(0,t.jsxs)(N.Text,{children:["+",l.length-3," ",l.length-3==1?"more model":"more models"]})}),A[e.row.id]&&(0,t.jsx)("div",{className:"flex flex-wrap gap-1",children:l.slice(3).map((e,l)=>"all-proxy-models"===e?(0,t.jsx)(T.Badge,{size:"xs",color:"red",children:(0,t.jsx)(N.Text,{children:"All Proxy Models"})},l+3):(0,t.jsx)(T.Badge,{size:"xs",color:"blue",children:(0,t.jsx)(N.Text,{children:e.length>30?`${(0,J.getModelDisplayName)(e).slice(0,30)}...`:(0,J.getModelDisplayName)(e)})},l+3))})]})]})})}):null})}},{id:"rate_limits",header:"Rate Limits",size:140,enableSorting:!1,cell:({row:e})=>{let l=e.original;return(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{children:["TPM: ",null!==l.tpm_limit?l.tpm_limit:"Unlimited"]}),(0,t.jsxs)("div",{children:["RPM: ",null!==l.rpm_limit?l.rpm_limit:"Unlimited"]})]})}}],[A]),L=(0,V.useCallback)(e=>{n(e),d(e=>({...e,pageIndex:0}))},[]);return(0,t.jsx)("div",{className:"w-full h-full overflow-hidden",children:s?(0,t.jsx)(e6.default,{keyId:s.token,onClose:()=>i(null),keyData:s,teams:[P],onDelete:k}):(0,t.jsx)("div",{className:"py-4 flex-1 overflow-hidden",children:(0,t.jsx)(e$.DataTable,{data:M,columns:z,sortingMode:"server",sorting:r,onSortingChange:L,paginationMode:"server",pagination:o,onPaginationChange:d,rowCount:I,filterMode:"server",columnFilters:m,onColumnFiltersChange:D,enableColumnResizing:!0,columnResizeMode:"onChange",isLoading:w||C,loadingMessage:"Loading keys...",maxBodyHeight:"75vh",size:"compact",toolbar:e=>(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(eH.DataTableToolbar,{table:e,searchValue:h,onSearchChange:x,searchPlaceholder:"Search by key alias…",onRefresh:()=>k?.(),isRefreshing:C,onOpenFilters:()=>g(!0),filterLabels:{user_id:"User ID"}}),(0,t.jsx)(eW.DataTableFilterDrawer,{table:e,open:u,onOpenChange:g,title:"Filters",description:`Narrow down keys for ${l??"this team"}`,children:({get:e,set:l})=>(0,t.jsx)(eW.DataTableFilterField,{label:"User ID",children:(0,t.jsx)(eJ.Input,{value:e("user_id")??"",onChange:e=>l("user_id",e.target.value),placeholder:"Filter by user ID…"})})})]})})})})}e.s(["default",0,({teamId:e,onClose:n,accessToken:o,is_team_admin:ed,is_proxy_admin:em,is_org_admin:ec=!1,userModels:eu,editTeam:eg,premiumUser:eh=!1,onUpdate:ep})=>{let e_,ex,eb,ey,ef,ev,eT,[eS,ew]=(0,V.useState)(null),[eN,eC]=(0,V.useState)(!0),[ek,eM]=(0,V.useState)(!1),[eB]=I.Form.useForm(),[eR,eU]=(0,V.useState)(!1),[eE,eG]=(0,V.useState)(null),[eK,e$]=(0,V.useState)(!1),[eW,eq]=(0,V.useState)([]),[eH,eJ]=(0,V.useState)(!1),[eY,eQ]=(0,V.useState)({}),{data:eZ,isLoading:eX}=d(),e0=eZ?.globalGuardrailNames??new Set,[e1,e4]=(0,V.useState)([]),[e2,e6]=(0,V.useState)({}),[e3,e8]=(0,V.useState)(!1),[e7,e9]=(0,V.useState)(null),[te,tt]=(0,V.useState)(!1),[tl,ta]=(0,V.useState)(!1),[ts,ti]=(0,V.useState)(!1),[tr,tn]=(0,V.useState)({}),to=V.default.useRef(null),[td,tm]=(0,V.useState)(null),{userRole:tc,userId:tu}=(0,l.default)(),{data:tg=[]}=(0,a.useOrganizations)(),th=(0,s.useQueryClient)(),tp=(0,V.useMemo)(()=>{let e=eS?.team_info?.organization_id;if(!e||!tu)return!1;let t=tg.find(t=>t.organization_id===e);return t?.members?.some(e=>e.user_id===tu&&"org_admin"===e.user_role)??!1},[eS,tg,tu]),t_=I.Form.useWatch("models",eB),tx=I.Form.useWatch("disable_global_guardrails",eB),tb=(0,V.useMemo)(()=>{let e=t_??eS?.team_info?.models??[];return e.includes("all-proxy-models")||e.includes("all-team-models")?eu:(0,J.unfurlWildcardModelsInList)(e,eu)},[t_,eS,eu]),tj=ed||em||ec||tp,ty=(0,V.useMemo)(()=>{let e;return e=[eA,eF,eP],tj?[...e,eO,eD,ez]:e},[tj]),tf=(0,V.useMemo)(()=>eg&&tj?ez:eA,[eg,tj]),tv=async()=>{try{if(eC(!0),!o)return;let t=await (0,r.teamInfoCall)(o,e);ew(t)}catch(e){et.default.fromBackend("Failed to load team information"),console.error("Error fetching team info:",e)}finally{eC(!1)}};(0,V.useEffect)(()=>{tv()},[e,o]),(0,V.useEffect)(()=>{(async()=>{if(!o||!eS?.team_info?.organization_id)return tm(null);try{let e=await (0,r.organizationInfoCall)(o,eS.team_info.organization_id);tm(e)}catch(e){console.error("Error fetching organization info:",e),tm(null)}})()},[o,eS?.team_info?.organization_id]),(0,V.useMemo)(()=>{let e;return e=[],e=td?td.models.includes("all-proxy-models")?eu:td.models.length>0?td.models:eu:eu,(0,J.unfurlWildcardModelsInList)(e,eu)},[td,eu]),(0,V.useEffect)(()=>{(async()=>{try{if(!o)return;let e=(await (0,r.getPoliciesList)(o)).policies.map(e=>e.policy_name);e4(e)}catch(e){console.error("Failed to fetch policies:",e)}})()},[o]),(0,V.useEffect)(()=>{(async()=>{if(!o||!eS?.team_info?.policies||0===eS.team_info.policies.length)return;e8(!0);let e={};try{await Promise.all(eS.team_info.policies.map(async t=>{try{let l=await (0,r.getPolicyInfoWithGuardrails)(o,t);e[t]=l.resolved_guardrails||[]}catch(l){console.error(`Failed to fetch guardrails for policy ${t}:`,l),e[t]=[]}})),e6(e)}catch(e){console.error("Failed to fetch policy guardrails:",e)}finally{e8(!1)}})()},[o,eS?.team_info?.policies]);let tT=async t=>{try{if(null==o)return;let l={user_email:t.user_email,user_id:t.user_id,role:t.role};await (0,r.teamMemberAddCall)(o,e,l),et.default.success("Team member added successfully"),eM(!1),eB.resetFields();let a=await (0,r.teamInfoCall)(o,e);ew(a),ep(a)}catch(t){let e="Failed to add team member";t?.raw?.detail?.error?.includes("Assigning team admins is a premium feature")?e="Assigning admins is an enterprise-only feature. Please upgrade your LiteLLM plan to enable this.":t?.message&&(e=t.message),et.default.fromBackend(e),console.error("Error adding team member:",t)}},tS=async t=>{try{if(null==o)return;let l={user_email:t.user_email,user_id:t.user_id,role:t.role,max_budget_in_team:t.max_budget_in_team,tpm_limit:t.tpm_limit,rpm_limit:t.rpm_limit,budget_duration:t.budget_duration,allowed_models:t.allowed_models};R.default.destroy(),await (0,r.teamMemberUpdateCall)(o,e,l),et.default.success("Team member updated successfully"),eU(!1);let a=await (0,r.teamInfoCall)(o,e);ew(a),ep(a)}catch(t){let e="Failed to update team member";t?.raw?.detail?.includes("Assigning team admins is a premium feature")?e="Assigning admins is an enterprise-only feature. Please upgrade your LiteLLM plan to enable this.":t?.message&&(e=t.message),eU(!1),R.default.destroy(),et.default.fromBackend(e),console.error("Error updating team member:",t)}},tw=async()=>{if(e7&&o){ta(!0);try{await (0,r.teamMemberDeleteCall)(o,e,e7),et.default.success("Team member removed successfully");let t=await (0,r.teamInfoCall)(o,e);ew(t),ep(t)}catch(e){et.default.fromBackend("Failed to remove team member"),console.error("Error removing team member:",e)}finally{ta(!1),tt(!1),e9(null)}}},tN=async t=>{try{let l;if(!o)return;ti(!0);let s={};try{let{soft_budget_alerting_emails:e,...l}=t.metadata?JSON.parse(t.metadata):{};s=l}catch(e){et.default.fromBackend("Invalid JSON in metadata field");return}if("string"==typeof t.secret_manager_settings&&t.secret_manager_settings.trim().length>0)try{l=JSON.parse(t.secret_manager_settings)}catch(e){et.default.fromBackend("Invalid JSON in secret manager settings");return}let i=e=>null==e||"string"==typeof e&&""===e.trim()||"number"==typeof e&&Number.isNaN(e)?null:e,n={},d={};for(let e of t.modelLimits??[])e?.model&&(null!=e.tpm&&(n[e.model]=e.tpm),null!=e.rpm&&(d[e.model]=e.rpm));let m=!0===t.disable_global_guardrails,u=m?Array.from(e0):Array.from(e0).filter(e=>!(t.guardrails||[]).includes(e)),g=em?{allowed_passthrough_routes:t.allowed_passthrough_routes||[]}:tC.metadata?.allowed_passthrough_routes?{allowed_passthrough_routes:tC.metadata.allowed_passthrough_routes}:{},h={team_id:e,team_alias:t.team_alias,models:t.models,tpm_limit:i(t.tpm_limit),rpm_limit:i(t.rpm_limit),model_tpm_limit:n,model_rpm_limit:d,max_budget:t.max_budget,soft_budget:i(t.soft_budget),budget_duration:t.budget_duration,metadata:{...s,...g,guardrails:(t.guardrails||[]).filter(e=>!e0.has(e)),opted_out_global_guardrails:u,...t.logging_settings?.length>0?{logging:t.logging_settings}:{},disable_global_guardrails:m,soft_budget_alerting_emails:"string"==typeof t.soft_budget_alerting_emails?t.soft_budget_alerting_emails.split(",").map(e=>e.trim()).filter(e=>e.length>0):t.soft_budget_alerting_emails||[],...void 0!==l?{secret_manager_settings:l}:{}},...t.policies?.length>0?{policies:t.policies}:{},...t.organization_id!==tC.organization_id?{organization_id:t.organization_id??null}:{}};h.max_budget=(0,c.mapEmptyStringToNull)(h.max_budget),h.team_member_budget_duration=t.team_member_budget_duration,void 0!==t.team_member_budget&&(h.team_member_budget=Number(t.team_member_budget)),void 0!==t.team_member_key_duration&&(h.team_member_key_duration=t.team_member_key_duration),(void 0!==t.team_member_tpm_limit||void 0!==t.team_member_rpm_limit)&&(h.team_member_tpm_limit=i(t.team_member_tpm_limit),h.team_member_rpm_limit=i(t.team_member_rpm_limit));let{servers:p,accessGroups:_,toolsets:x}=t.mcp_servers_and_groups||{servers:[],accessGroups:[],toolsets:[]},b=new Set(p||[]),j=Object.fromEntries(Object.entries(t.mcp_tool_permissions||{}).filter(([e])=>b.has(e)));h.object_permission={},p&&(h.object_permission.mcp_servers=p),_&&(h.object_permission.mcp_access_groups=_),j&&(h.object_permission.mcp_tool_permissions=j),x&&(h.object_permission.mcp_toolsets=x),delete t.mcp_servers_and_groups,delete t.mcp_tool_permissions;let{agents:y,accessGroups:f}=t.agents_and_groups||{agents:[],accessGroups:[]};y&&y.length>0&&(h.object_permission.agents=y),f&&f.length>0&&(h.object_permission.agent_access_groups=f),delete t.agents_and_groups,t.vector_stores&&t.vector_stores.length>0&&(h.object_permission.vector_stores=t.vector_stores),Array.isArray(t.object_permission_search_tools)&&(h.object_permission.search_tools=t.object_permission_search_tools),void 0!==t.access_group_ids&&(h.access_group_ids=t.access_group_ids),void 0!==t.default_team_member_models&&(h.default_team_member_models=t.default_team_member_models);let v=tC.litellm_model_table?.model_aliases??{};(Object.keys(tr).length>0||Object.keys(v).length>0)&&(h.model_aliases=tr);let T=to.current?.getValue();if(T?.router_settings){let e=e=>null!=e&&""!==e&&!1!==e&&!(Array.isArray(e)&&0===e.length),t=Object.values(T.router_settings).some(e),l=tC.router_settings&&Object.values(tC.router_settings).some(e);(t||l)&&(h.router_settings=T.router_settings)}await (0,r.teamUpdateCall)(o,h),th.invalidateQueries({queryKey:a.organizationKeys.all}),et.default.success("Team settings updated successfully"),e$(!1),tv()}catch(e){console.error("Error updating team:",e)}finally{ti(!1)}};if(eN)return(0,t.jsx)("div",{className:"p-4",children:"Loading..."});if(!eS?.team_info)return(0,t.jsx)("div",{className:"p-4",children:"Team not found"});let{team_info:tC}=eS,tk=tC.metadata?.disable_global_guardrails===!0,tM=new Set(Array.isArray(tC.metadata?.opted_out_global_guardrails)?tC.metadata.opted_out_global_guardrails:[]),tI=(Array.isArray(tC.metadata?.guardrails)?tC.metadata.guardrails:[]).filter(e=>!e0.has(e)),tA=tk?tI:[...Array.from(e0).filter(e=>!tM.has(e)),...tI],tF=eZ?.guardrails??[],tP=tF.filter(e=>e.litellm_params?.default_on),tO=tF.filter(e=>!e.litellm_params?.default_on),tD=(e,l)=>(0,t.jsx)(P.Select.Option,{value:e.guardrail_name,label:e.guardrail_name,disabled:l,children:e.guardrail_name},e.guardrail_name),tz=e=>{e.preventDefault(),e.stopPropagation()},tL=async(e,t)=>{await (0,m.copyToClipboard)(e)&&(eQ(e=>({...e,[t]:!0})),setTimeout(()=>{eQ(e=>({...e,[t]:!1}))},2e3))};return(0,t.jsxs)("div",{className:"p-4",children:[(0,t.jsx)("div",{className:"flex justify-between items-center mb-6",children:(0,t.jsxs)("div",{children:[(0,t.jsx)(M.Button,{type:"text",icon:(0,t.jsx)(j.ArrowLeftIcon,{className:"h-4 w-4"}),onClick:n,className:"mb-4",children:"Back to Teams"}),(0,t.jsx)(k.Title,{children:tC.team_alias}),(0,t.jsxs)("div",{className:"flex items-center",children:[(0,t.jsx)(N.Text,{className:"text-gray-500 font-mono",children:tC.team_id}),(0,t.jsx)(M.Button,{type:"text",size:"small",icon:eY["team-id"]?(0,t.jsx)(U.CheckIcon,{size:12}):(0,t.jsx)(E.CopyIcon,{size:12}),onClick:()=>tL(tC.team_id,"team-id"),className:`left-2 z-10 transition-all duration-200 ${eY["team-id"]?"text-green-600 bg-green-50 border-green-200":"text-gray-500 hover:text-gray-700 hover:bg-gray-100"}`})]})]})}),(0,t.jsx)(z.Tabs,{defaultActiveKey:tf,className:"mb-4",items:[{key:eA,label:eL[eA],children:(0,t.jsxs)(w.Grid,{numItems:1,numItemsSm:2,numItemsLg:3,className:"gap-6",children:[(0,t.jsxs)(S.Card,{children:[(0,t.jsx)(N.Text,{children:"Budget Status"}),(0,t.jsxs)("div",{className:"mt-2",children:[(0,t.jsxs)(k.Title,{children:["$",(0,m.formatNumberWithCommas)(tC.spend,4)]}),(0,t.jsxs)(N.Text,{children:["of ",null===tC.max_budget?"Unlimited":`$${(0,m.formatNumberWithCommas)(tC.max_budget,4)}`]}),tC.budget_duration&&(0,t.jsxs)(N.Text,{className:"text-gray-500",children:["Reset: ",tC.budget_duration]}),(0,t.jsx)("br",{}),tC.team_member_budget_table&&(0,t.jsxs)(N.Text,{className:"text-gray-500",children:["Team Member Budget: $",(0,m.formatNumberWithCommas)(tC.team_member_budget_table.max_budget,4)]})]})]}),(0,t.jsxs)(S.Card,{children:[(0,t.jsx)(N.Text,{children:"Rate Limits"}),(0,t.jsxs)("div",{className:"mt-2",children:[(0,t.jsxs)(N.Text,{children:["TPM: ",tC.tpm_limit||"Unlimited"]}),(0,t.jsxs)(N.Text,{children:["RPM: ",tC.rpm_limit||"Unlimited"]}),tC.max_parallel_requests&&(0,t.jsxs)(N.Text,{children:["Max Parallel Requests: ",tC.max_parallel_requests]}),(e_=tC.metadata?.model_tpm_limit??{},ex=tC.metadata?.model_rpm_limit??{},0===(eb=Array.from(new Set([...Object.keys(e_),...Object.keys(ex)]))).length?null:(0,t.jsxs)("div",{className:"mt-3",children:[(0,t.jsx)(N.Text,{className:"text-gray-500",children:"Per-model limits:"}),eb.map(e=>(0,t.jsxs)(N.Text,{className:"text-xs",children:[e,": TPM ",e_[e]??"—",", RPM ",ex[e]??"—"]},e))]}))]})]}),(0,t.jsxs)(S.Card,{children:[(0,t.jsx)(N.Text,{children:"Models"}),(0,t.jsx)("div",{className:"mt-2 flex flex-wrap gap-2",children:0===tC.models.length||tC.models.includes("all-proxy-models")?(0,t.jsx)(T.Badge,{color:"red",children:"All proxy models"}):(0,t.jsxs)(t.Fragment,{children:[tC.models.map((e,l)=>(0,t.jsx)(T.Badge,{color:"blue",children:e},`direct-${l}`)),(tC.access_group_models||[]).map((e,l)=>(0,t.jsx)(T.Badge,{color:"green",title:"From access group",children:e},`ag-${l}`))]})})]}),(0,t.jsxs)(S.Card,{children:[(0,t.jsx)(N.Text,{className:"font-semibold text-gray-900",children:"Virtual Keys"}),(0,t.jsxs)("div",{className:"mt-2",children:[(0,t.jsxs)(N.Text,{children:["User Keys: ",eS.keys.filter(e=>e.user_id).length]}),(0,t.jsxs)(N.Text,{children:["Service Account Keys: ",eS.keys.filter(e=>!e.user_id).length]}),(0,t.jsxs)(N.Text,{className:"text-gray-500",children:["Total: ",eS.keys.length]})]})]}),(0,t.jsx)(el.default,{objectPermission:tC.object_permission,variant:"card",accessToken:o}),(0,t.jsx)(S.Card,{children:(0,t.jsx)(Y,{globalGuardrailNames:e0,teamGuardrails:Array.isArray(tC.metadata?.guardrails)?tC.metadata.guardrails:[],optedOutGlobalGuardrails:Array.isArray(tC.metadata?.opted_out_global_guardrails)?tC.metadata.opted_out_global_guardrails:[],killSwitchOn:tk,variant:"inline"})}),(0,t.jsxs)(S.Card,{children:[(0,t.jsx)(N.Text,{className:"font-semibold text-gray-900 mb-3",children:"Policies"}),tC.policies&&tC.policies.length>0?(0,t.jsx)("div",{className:"space-y-4",children:tC.policies.map((e,l)=>(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)(T.Badge,{color:"purple",children:e}),e3&&(0,t.jsx)(N.Text,{className:"text-xs text-gray-400",children:"Loading guardrails..."})]}),!e3&&e2[e]&&e2[e].length>0&&(0,t.jsxs)("div",{className:"ml-4 pl-3 border-l-2 border-gray-200",children:[(0,t.jsx)(N.Text,{className:"text-xs text-gray-500 mb-1",children:"Resolved Guardrails:"}),(0,t.jsx)("div",{className:"flex flex-wrap gap-1",children:e2[e].map((e,l)=>(0,t.jsx)(T.Badge,{color:"blue",size:"xs",children:e},l))})]})]},l))}):(0,t.jsx)(N.Text,{className:"text-gray-500",children:"No policies configured"})]}),(0,t.jsx)(Q.default,{loggingConfigs:tC.metadata?.logging||[],disabledCallbacks:[],variant:"card"})]})},{key:eF,label:eL[eF],children:(0,t.jsx)(eI,{teamId:e})},{key:eP,label:eL[eP],children:(0,t.jsx)(e5,{teamId:e,teamAlias:tC.team_alias,organization:td})},{key:eO,label:eL[eO],children:(0,t.jsx)(eV,{teamData:eS,canEditTeam:tj,handleMemberDelete:e=>{e9(e),tt(!0)},setSelectedEditMember:eG,setIsEditMemberModalVisible:eU,setIsAddMemberModalVisible:eM})},{key:eD,label:eL[eD],children:(0,t.jsx)(ej,{teamId:e,accessToken:o,canEditTeam:tj})},{key:ez,label:eL[ez],children:(0,t.jsxs)(S.Card,{className:"overflow-y-auto max-h-[65vh]",children:[(0,t.jsxs)("div",{className:"flex justify-between items-center mb-4",children:[(0,t.jsx)(k.Title,{children:"Team Settings"}),tj&&!eK&&(0,t.jsx)(M.Button,{icon:(0,t.jsx)(g.EditOutlined,{className:"h-4 w-4"}),onClick:()=>{tn(tC.litellm_model_table?.model_aliases??{}),e$(!0)},children:"Edit Settings"})]}),eK&&eX?(0,t.jsx)("div",{className:"p-4",children:"Loading..."}):eK?(0,t.jsxs)(I.Form,{form:eB,onFinish:tN,onValuesChange:e=>{if("disable_global_guardrails"in e){let t=!0===e.disable_global_guardrails,l=(eB.getFieldValue("guardrails")||[]).filter(e=>!e0.has(e));eB.setFieldValue("guardrails",t?l:[...Array.from(e0),...l])}},initialValues:{...tC,team_alias:tC.team_alias,models:tC.models,tpm_limit:tC.tpm_limit,rpm_limit:tC.rpm_limit,object_permission_search_tools:tC.object_permission?.search_tools||[],modelLimits:Array.from(new Set([...Object.keys(tC.metadata?.model_tpm_limit??{}),...Object.keys(tC.metadata?.model_rpm_limit??{})])).map(e=>({model:e,tpm:tC.metadata?.model_tpm_limit?.[e],rpm:tC.metadata?.model_rpm_limit?.[e]})),max_budget:tC.max_budget,soft_budget:tC.soft_budget,budget_duration:tC.budget_duration,team_member_tpm_limit:tC.team_member_budget_table?.tpm_limit,team_member_rpm_limit:tC.team_member_budget_table?.rpm_limit,team_member_budget:tC.team_member_budget_table?.max_budget,team_member_budget_duration:tC.team_member_budget_table?.budget_duration,guardrails:tA,policies:tC.policies||[],disable_global_guardrails:tC.metadata?.disable_global_guardrails||!1,soft_budget_alerting_emails:Array.isArray(tC.metadata?.soft_budget_alerting_emails)?tC.metadata.soft_budget_alerting_emails.join(", "):"",metadata:tC.metadata?JSON.stringify((({logging:e,secret_manager_settings:t,soft_budget_alerting_emails:l,model_tpm_limit:a,model_rpm_limit:s,allowed_passthrough_routes:i,...r})=>r)(tC.metadata),null,2):"",logging_settings:tC.metadata?.logging||[],secret_manager_settings:tC.metadata?.secret_manager_settings?JSON.stringify(tC.metadata.secret_manager_settings,null,2):"",organization_id:tC.organization_id,vector_stores:tC.object_permission?.vector_stores||[],mcp_servers:tC.object_permission?.mcp_servers||[],mcp_access_groups:tC.object_permission?.mcp_access_groups||[],mcp_servers_and_groups:{servers:tC.object_permission?.mcp_servers||[],accessGroups:tC.object_permission?.mcp_access_groups||[],toolsets:tC.object_permission?.mcp_toolsets||[]},mcp_tool_permissions:tC.object_permission?.mcp_tool_permissions||{},agents_and_groups:{agents:tC.object_permission?.agents||[],accessGroups:tC.object_permission?.agent_access_groups||[]},access_group_ids:tC.access_group_ids||[],default_team_member_models:tC.default_team_member_models||[],allowed_passthrough_routes:tC.metadata?.allowed_passthrough_routes||[]},layout:"vertical",children:[(0,t.jsx)(I.Form.Item,{label:"Team Name",name:"team_alias",rules:[{required:!0,message:"Please input a team name"}],children:(0,t.jsx)(A.Input,{type:""})}),(0,t.jsx)(I.Form.Item,{label:"Models",name:"models",rules:[{required:!0,message:"Please select at least one model"}],children:(0,t.jsx)(ee.ModelSelect,{value:eB.getFieldValue("models")||[],onChange:e=>eB.setFieldValue("models",e),teamID:e,organizationID:eS?.team_info?.organization_id||void 0,options:{includeSpecialOptions:!0,includeUserModels:!eS?.team_info?.organization_id,showAllProxyModelsOverride:(0,u.isProxyAdminRole)(tc)&&!eS?.team_info?.organization_id},context:"team",dataTestId:"models-select"})}),(0,t.jsx)(I.Form.Item,{label:(0,t.jsxs)("span",{children:["Model Aliases"," ",(0,t.jsx)(B.Tooltip,{title:"Map a custom alias to an underlying model. Team members can call the alias in API requests instead of the real model name.",children:(0,t.jsx)(p.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),children:(0,t.jsx)(K.default,{accessToken:o||"",initialModelAliases:tr,onAliasUpdate:tn,showExampleConfig:!1})}),(0,t.jsx)(I.Form.Item,{label:"Max Budget (USD)",name:"max_budget",children:(0,t.jsx)(ea.default,{step:.01,precision:2,style:{width:"100%"}})}),(0,t.jsx)(I.Form.Item,{label:"Soft Budget (USD)",name:"soft_budget",children:(0,t.jsx)(ea.default,{step:.01,precision:2,style:{width:"100%"}})}),(0,t.jsx)(I.Form.Item,{label:"Soft Budget Alerting Emails",name:"soft_budget_alerting_emails",tooltip:"Comma-separated email addresses to receive alerts when the soft budget is reached",children:(0,t.jsx)(A.Input,{placeholder:"example1@test.com, example2@test.com"})}),(0,t.jsxs)(y.Accordion,{className:"mt-4 mb-4",children:[(0,t.jsx)(v.AccordionHeader,{children:(0,t.jsx)("b",{children:"Team Member Settings"})}),(0,t.jsxs)(f.AccordionBody,{children:[(0,t.jsx)(N.Text,{className:"text-xs text-gray-500 mb-4",children:"Optional defaults applied when members join this team. All fields can be overridden per member."}),(0,t.jsx)(I.Form.Item,{label:(0,t.jsxs)("span",{children:["Default Model Access"," ",(0,t.jsx)(B.Tooltip,{title:"Optional. If set, new members can only access these models by default. Must be a subset of the team's models above. Leave empty to give all members access to all team models.",children:(0,t.jsx)(p.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:"default_team_member_models",children:(0,t.jsx)(I.Form.Item,{noStyle:!0,shouldUpdate:(e,t)=>e.models!==t.models,children:({getFieldValue:e})=>{let l=e("models")||tC.models||[];return(0,t.jsx)(P.Select,{mode:"multiple",placeholder:"Leave empty — all team models accessible to every member",value:eB.getFieldValue("default_team_member_models")||[],onChange:e=>eB.setFieldValue("default_team_member_models",e),options:l.map(e=>({label:e,value:e}))})}})}),(0,t.jsx)(I.Form.Item,{label:"Default Budget (USD)",name:"team_member_budget",tooltip:"Default spend budget for each member in this team.",children:(0,t.jsx)(ea.default,{step:.01,precision:2,style:{width:"100%"}})}),(0,t.jsx)(I.Form.Item,{label:"Default Budget Duration",name:"team_member_budget_duration",children:(0,t.jsx)(q,{onChange:e=>eB.setFieldValue("team_member_budget_duration",e),value:eB.getFieldValue("team_member_budget_duration")})}),(0,t.jsx)(I.Form.Item,{label:"Default Key Duration (eg: 1d, 1mo)",name:"team_member_key_duration",tooltip:"Set a limit to the duration of a team member's key. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days), 1mo (month)",children:(0,t.jsx)(C.TextInput,{placeholder:"e.g., 30d"})}),(0,t.jsx)(I.Form.Item,{label:"Default TPM Limit",name:"team_member_tpm_limit",tooltip:"Default tokens per minute limit for each member. Can be overridden per member.",children:(0,t.jsx)(ea.default,{step:1,style:{width:"100%"},placeholder:"e.g., 1000"})}),(0,t.jsx)(I.Form.Item,{label:"Default RPM Limit",name:"team_member_rpm_limit",tooltip:"Default requests per minute limit for each member. Can be overridden per member.",children:(0,t.jsx)(ea.default,{step:1,style:{width:"100%"},placeholder:"e.g., 100"})})]})]}),(0,t.jsx)(I.Form.Item,{label:"Reset Budget",name:"budget_duration",children:(0,t.jsxs)(P.Select,{placeholder:"n/a",children:[(0,t.jsx)(P.Select.Option,{value:"24h",children:"daily"}),(0,t.jsx)(P.Select.Option,{value:"7d",children:"weekly"}),(0,t.jsx)(P.Select.Option,{value:"30d",children:"monthly"})]})}),(0,t.jsx)(I.Form.Item,{label:"Tokens per minute Limit (TPM)",name:"tpm_limit",children:(0,t.jsx)(ea.default,{step:1,style:{width:"100%"}})}),(0,t.jsx)(I.Form.Item,{label:"Requests per minute Limit (RPM)",name:"rpm_limit",children:(0,t.jsx)(ea.default,{step:1,style:{width:"100%"}})}),(0,t.jsx)(I.Form.Item,{label:"Model-Specific Rate Limits",tooltip:"Set per-model TPM/RPM limits that apply across the whole team.",children:(0,t.jsx)(I.Form.List,{name:"modelLimits",children:(e,{add:l,remove:a})=>(0,t.jsxs)(t.Fragment,{children:[e.map(({key:e,name:l,...s})=>(0,t.jsxs)(O.Space,{style:{display:"flex",marginBottom:8},align:"baseline",children:[(0,t.jsx)(I.Form.Item,{...s,name:[l,"model"],rules:[{required:!0,message:"Missing model"},{validator:(e,t)=>t&&(eB.getFieldValue("modelLimits")??[]).filter(e=>e?.model===t).length>1?Promise.reject(Error("Duplicate model")):Promise.resolve()}],style:{minWidth:240},children:(0,t.jsx)(P.Select,{showSearch:!0,placeholder:"Select model",allowClear:!0,options:tb.map(e=>({value:e,label:e}))})}),(0,t.jsx)(I.Form.Item,{...s,name:[l,"tpm"],rules:[{validator:async(e,t)=>{let a=(eB.getFieldValue("modelLimits")??[])[l]??{};return a.model&&null==t&&null==a.rpm?Promise.reject(Error("Set at least one of TPM or RPM")):Promise.resolve()}}],children:(0,t.jsx)(F.InputNumber,{placeholder:"TPM Limit",min:0})}),(0,t.jsx)(I.Form.Item,{...s,name:[l,"rpm"],children:(0,t.jsx)(F.InputNumber,{placeholder:"RPM Limit",min:0})}),(0,t.jsx)(_.MinusCircleOutlined,{onClick:()=>a(l),style:{color:"#ef4444"}})]},e)),(0,t.jsx)(I.Form.Item,{children:(0,t.jsx)(M.Button,{type:"dashed",onClick:()=>l(),block:!0,icon:(0,t.jsx)(x.PlusOutlined,{}),children:"Add Model Limit"})})]})})}),(0,t.jsx)(I.Form.Item,{label:"Router Settings",children:(0,t.jsx)(en.default,{ref:to,accessToken:o||"",value:tC.router_settings?{router_settings:tC.router_settings}:void 0})}),(0,t.jsx)(I.Form.Item,{label:(0,t.jsxs)("span",{children:["Guardrails"," ",(0,t.jsx)(B.Tooltip,{title:"Select which guardrails apply to this team. Global guardrails are enabled by default — uncheck to opt out. Other guardrails are opt-in.",children:(0,t.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/guardrails/quick_start",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,t.jsx)(p.InfoCircleOutlined,{style:{marginLeft:"4px"}})})})]}),name:"guardrails",children:(0,t.jsx)(P.Select,{mode:"multiple",placeholder:"Select guardrails",optionLabelProp:"label",tagRender:({label:e,value:l,closable:a,onClose:s})=>{let i=e0.has(l);return(0,t.jsxs)(L.Tag,{color:"blue",closable:a,onClose:s,onMouseDown:tz,style:{marginInlineEnd:4},children:[i&&(0,t.jsx)(h.GlobalOutlined,{style:{marginInlineEnd:4},"aria-label":"Global guardrail"}),e]})},children:tP.length>0&&tO.length>0?(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(P.Select.OptGroup,{label:(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(h.GlobalOutlined,{style:{marginInlineEnd:4}}),"Global"]}),children:tP.map(e=>tD(e,!!tx))}),(0,t.jsx)(P.Select.OptGroup,{label:"Other",children:tO.map(e=>tD(e,!1))})]}):[...tP.map(e=>tD(e,!!tx)),...tO.map(e=>tD(e,!1))]})}),(0,t.jsx)(I.Form.Item,{label:(0,t.jsxs)("span",{children:["Disable all global guardrails"," ",(0,t.jsx)(B.Tooltip,{title:"Kill switch: bypass every global guardrail for this team, including any added in the future. For per-guardrail opt-out instead, use the Guardrails dropdown above.",children:(0,t.jsx)(p.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:"disable_global_guardrails",valuePropName:"checked",children:(0,t.jsx)(D.Switch,{checkedChildren:"Yes",unCheckedChildren:"No"})}),(0,t.jsx)(I.Form.Item,{label:(0,t.jsxs)("span",{children:["Policies"," ",(0,t.jsx)(B.Tooltip,{title:"Apply policies to this team to control guardrails and other settings",children:(0,t.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/guardrails/guardrail_policies",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,t.jsx)(p.InfoCircleOutlined,{style:{marginLeft:"4px"}})})})]}),name:"policies",children:(0,t.jsx)(P.Select,{mode:"tags",placeholder:"Select or enter policies",options:e1.map(e=>({value:e,label:e}))})}),(0,t.jsx)(I.Form.Item,{label:(0,t.jsxs)("span",{children:["Access Groups"," ",(0,t.jsx)(B.Tooltip,{title:"Assign access groups to this team. Access groups control which models, MCP servers, and agents this team can use",children:(0,t.jsx)(p.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:"access_group_ids",children:(0,t.jsx)(G.default,{placeholder:"Select access groups (optional)"})}),(0,t.jsx)(I.Form.Item,{label:"Vector Stores",name:"vector_stores","aria-label":"Vector Stores",children:(0,t.jsx)(es.default,{onChange:e=>eB.setFieldValue("vector_stores",e),value:eB.getFieldValue("vector_stores"),accessToken:o||"",placeholder:"Select vector stores"})}),(0,t.jsx)(I.Form.Item,{label:"Allowed Pass Through Routes",name:"allowed_passthrough_routes",children:(0,t.jsx)(B.Tooltip,{title:eh?em?"":"Only proxy admins can set allowed pass through routes":"Premium feature - Upgrade to set allowed pass through routes",placement:"top",children:(0,t.jsx)(H.default,{onChange:e=>eB.setFieldValue("allowed_passthrough_routes",e),value:eB.getFieldValue("allowed_passthrough_routes"),accessToken:o||"",placeholder:"Select pass through routes",disabled:!eh||!em})})}),(0,t.jsx)(I.Form.Item,{label:"MCP Servers / Access Groups",name:"mcp_servers_and_groups",children:(0,t.jsx)(Z.default,{onChange:e=>eB.setFieldValue("mcp_servers_and_groups",e),value:eB.getFieldValue("mcp_servers_and_groups"),accessToken:o||"",placeholder:"Select MCP servers or access groups (optional)",allowAllProxyMcpServers:em})}),(0,t.jsx)(I.Form.Item,{name:"mcp_tool_permissions",initialValue:{},hidden:!0,children:(0,t.jsx)(A.Input,{type:"hidden"})}),(0,t.jsx)(I.Form.Item,{noStyle:!0,shouldUpdate:(e,t)=>e.mcp_servers_and_groups!==t.mcp_servers_and_groups||e.mcp_tool_permissions!==t.mcp_tool_permissions,children:()=>(0,t.jsx)("div",{className:"mb-6",children:(0,t.jsx)(X.default,{accessToken:o||"",selectedServers:eB.getFieldValue("mcp_servers_and_groups")?.servers||[],toolPermissions:eB.getFieldValue("mcp_tool_permissions")||{},onChange:e=>eB.setFieldsValue({mcp_tool_permissions:e})})})}),(0,t.jsx)(I.Form.Item,{label:"Agents / Access Groups",name:"agents_and_groups",children:(0,t.jsx)($.default,{onChange:e=>eB.setFieldValue("agents_and_groups",e),value:eB.getFieldValue("agents_and_groups"),accessToken:o||"",placeholder:"Select agents or access groups (optional)"})}),(0,t.jsxs)(y.Accordion,{className:"mt-4 mb-4",children:[(0,t.jsx)(v.AccordionHeader,{children:(0,t.jsx)("b",{children:"Search Tool Settings"})}),(0,t.jsx)(f.AccordionBody,{children:(0,t.jsx)(I.Form.Item,{label:"Allowed Search Tools",name:"object_permission_search_tools",tooltip:"Select which search tools this team can access. Leave empty to allow all search tools.",children:(0,t.jsx)(ei,{onChange:e=>eB.setFieldValue("object_permission_search_tools",e),value:eB.getFieldValue("object_permission_search_tools"),accessToken:o||"",placeholder:"Select search tools (optional, empty = all allowed)"})})})]}),(0,t.jsx)(I.Form.Item,{label:"Organization",name:"organization_id",children:(0,t.jsx)(P.Select,{allowClear:!0,placeholder:"Select an organization",showSearch:!0,optionFilterProp:"label",options:tg.map(e=>({value:e.organization_id,label:e.organization_alias||e.organization_id}))})}),(0,t.jsx)(I.Form.Item,{label:"Logging Settings",name:"logging_settings",children:(0,t.jsx)(er.default,{value:eB.getFieldValue("logging_settings"),onChange:e=>eB.setFieldValue("logging_settings",e)})}),(0,t.jsx)(I.Form.Item,{label:"Secret Manager Settings",name:"secret_manager_settings",help:eh?"Enter secret manager configuration as a JSON object.":"Premium feature - Upgrade to manage secret manager settings.",rules:[{validator:async(e,t)=>{if(!t)return Promise.resolve();try{return JSON.parse(t),Promise.resolve()}catch(e){return Promise.reject(Error("Please enter valid JSON"))}}}],children:(0,t.jsx)(A.Input.TextArea,{rows:6,placeholder:'{"namespace": "admin", "mount": "secret", "path_prefix": "litellm"}',disabled:!eh})}),(0,t.jsx)(I.Form.Item,{label:"Metadata",name:"metadata",children:(0,t.jsx)(A.Input.TextArea,{rows:10})}),(0,t.jsx)("div",{className:"sticky z-10 bg-white p-4 pr-0 border-t border-gray-200 -bottom-6 -inset-x-6",children:(0,t.jsxs)("div",{className:"flex justify-end items-center gap-2",children:[(0,t.jsx)(M.Button,{onClick:()=>e$(!1),disabled:ts,children:"Cancel"}),(0,t.jsx)(M.Button,{icon:(0,t.jsx)(b.SaveOutlined,{className:"h-4 w-4"}),type:"primary",htmlType:"submit",loading:ts,children:"Save Changes"})]})})]}):(0,t.jsxs)("div",{className:"space-y-4",children:[(0,t.jsxs)("div",{children:[(0,t.jsx)(N.Text,{className:"font-medium",children:"Team Name"}),(0,t.jsx)("div",{children:tC.team_alias})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(N.Text,{className:"font-medium",children:"Team ID"}),(0,t.jsx)("div",{className:"font-mono",children:tC.team_id})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(N.Text,{className:"font-medium",children:"Created At"}),(0,t.jsx)("div",{children:new Date(tC.created_at).toLocaleString()})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(N.Text,{className:"font-medium",children:"Models"}),(0,t.jsx)("div",{className:"flex flex-wrap gap-2 mt-1",children:tC.models.map((e,l)=>(0,t.jsx)(T.Badge,{color:"red",children:e},l))})]}),tC.default_team_member_models&&tC.default_team_member_models.length>0&&(0,t.jsxs)("div",{children:[(0,t.jsx)(N.Text,{className:"font-medium",children:"Default Member Models"}),(0,t.jsx)("div",{className:"flex flex-wrap gap-2 mt-1",children:tC.default_team_member_models.map((e,l)=>(0,t.jsx)(T.Badge,{color:"blue",children:e},l))})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(N.Text,{className:"font-medium",children:"Model Aliases"}),0===(ey=Object.entries(tC.litellm_model_table?.model_aliases??{})).length?(0,t.jsx)("div",{className:"text-gray-400",children:"No model aliases configured"}):(0,t.jsx)("div",{className:"mt-1 space-y-1",children:ey.map(([e,l])=>(0,t.jsxs)("div",{className:"text-sm",children:[(0,t.jsx)("span",{className:"font-mono",children:e}),(0,t.jsx)("span",{className:"text-gray-400",children:" -> "}),(0,t.jsx)("span",{className:"font-mono",children:l})]},e))})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(N.Text,{className:"font-medium",children:"Rate Limits"}),(0,t.jsxs)("div",{children:["TPM: ",tC.tpm_limit||"Unlimited"]}),(0,t.jsxs)("div",{children:["RPM: ",tC.rpm_limit||"Unlimited"]}),(ef=tC.metadata?.model_tpm_limit??{},ev=tC.metadata?.model_rpm_limit??{},0===(eT=Array.from(new Set([...Object.keys(ef),...Object.keys(ev)]))).length?null:(0,t.jsxs)("div",{className:"mt-2",children:[(0,t.jsx)(N.Text,{className:"text-gray-500",children:"Per-model limits:"}),eT.map(e=>(0,t.jsxs)("div",{className:"text-xs ml-2",children:[e,": TPM ",ef[e]??"—",", RPM ",ev[e]??"—"]},e))]}))]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(N.Text,{className:"font-medium",children:"Team Budget"}),(0,t.jsxs)("div",{children:["Max Budget:"," ",null!==tC.max_budget?`$${(0,m.formatNumberWithCommas)(tC.max_budget,4)}`:"No Limit"]}),(0,t.jsxs)("div",{children:["Soft Budget:"," ",null!==tC.soft_budget&&void 0!==tC.soft_budget?`$${(0,m.formatNumberWithCommas)(tC.soft_budget,4)}`:"No Limit"]}),(0,t.jsxs)("div",{children:["Budget Reset: ",tC.budget_duration||"Never"]}),tC.metadata?.soft_budget_alerting_emails&&Array.isArray(tC.metadata.soft_budget_alerting_emails)&&tC.metadata.soft_budget_alerting_emails.length>0&&(0,t.jsxs)("div",{children:["Soft Budget Alerting Emails: ",tC.metadata.soft_budget_alerting_emails.join(", ")]})]}),(0,t.jsxs)("div",{children:[(0,t.jsxs)(N.Text,{className:"font-medium",children:["Team Member Settings"," ",(0,t.jsx)(B.Tooltip,{title:"These are limits on individual team members",children:(0,t.jsx)(p.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),(0,t.jsxs)("div",{children:["Max Budget: ",tC.team_member_budget_table?.max_budget||"No Limit"]}),(0,t.jsxs)("div",{children:["Budget Duration: ",tC.team_member_budget_table?.budget_duration||"No Limit"]}),(0,t.jsxs)("div",{children:["Key Duration: ",tC.metadata?.team_member_key_duration||"No Limit"]}),(0,t.jsxs)("div",{children:["TPM Limit: ",tC.team_member_budget_table?.tpm_limit||"No Limit"]}),(0,t.jsxs)("div",{children:["RPM Limit: ",tC.team_member_budget_table?.rpm_limit||"No Limit"]})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(N.Text,{className:"font-medium",children:"Router Settings"}),tC.router_settings&&Object.values(tC.router_settings).some(e=>null!=e&&""!==e&&!(Array.isArray(e)&&0===e.length))?(0,t.jsxs)("div",{className:"mt-1 space-y-1",children:[tC.router_settings.routing_strategy&&(0,t.jsxs)("div",{children:["Routing Strategy: ",(0,t.jsx)(T.Badge,{color:"blue",children:tC.router_settings.routing_strategy})]}),null!=tC.router_settings.num_retries&&(0,t.jsxs)("div",{children:["Number of Retries: ",tC.router_settings.num_retries]}),null!=tC.router_settings.allowed_fails&&(0,t.jsxs)("div",{children:["Allowed Failures: ",tC.router_settings.allowed_fails]}),null!=tC.router_settings.cooldown_time&&(0,t.jsxs)("div",{children:["Cooldown Time: ",tC.router_settings.cooldown_time,"s"]}),null!=tC.router_settings.timeout&&(0,t.jsxs)("div",{children:["Timeout: ",tC.router_settings.timeout,"s"]}),null!=tC.router_settings.retry_after&&(0,t.jsxs)("div",{children:["Retry After: ",tC.router_settings.retry_after,"s"]}),tC.router_settings.fallbacks&&Array.isArray(tC.router_settings.fallbacks)&&tC.router_settings.fallbacks.length>0&&(0,t.jsxs)("div",{children:["Fallbacks: ",tC.router_settings.fallbacks.length," configured"]}),tC.router_settings.enable_tag_filtering&&(0,t.jsx)("div",{children:"Tag Filtering: Enabled"})]}):(0,t.jsx)("div",{className:"text-gray-400",children:"No router settings configured"})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(N.Text,{className:"font-medium",children:"Organization ID"}),(0,t.jsx)("div",{children:tC.organization_id})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(N.Text,{className:"font-medium",children:"Status"}),(0,t.jsx)(T.Badge,{color:tC.blocked?"red":"green",children:tC.blocked?"Blocked":"Active"})]}),(0,t.jsx)(el.default,{objectPermission:tC.object_permission,variant:"inline",className:"pt-4 border-t border-gray-200",accessToken:o}),(0,t.jsx)(Y,{globalGuardrailNames:e0,teamGuardrails:Array.isArray(tC.metadata?.guardrails)?tC.metadata.guardrails:[],optedOutGlobalGuardrails:Array.isArray(tC.metadata?.opted_out_global_guardrails)?tC.metadata.opted_out_global_guardrails:[],killSwitchOn:tk,variant:"inline",className:"pt-4 border-t border-gray-200"}),(0,t.jsx)(Q.default,{loggingConfigs:tC.metadata?.logging||[],disabledCallbacks:[],variant:"inline",className:"pt-4 border-t border-gray-200"}),tC.metadata?.secret_manager_settings&&(0,t.jsxs)("div",{className:"pt-4 border-t border-gray-200",children:[(0,t.jsx)(N.Text,{className:"font-medium",children:"Secret Manager Settings"}),(0,t.jsx)("pre",{className:"mt-2 bg-gray-50 p-3 rounded-sm text-xs overflow-x-auto",children:JSON.stringify(tC.metadata.secret_manager_settings,null,2)})]})]})]})}].filter(e=>ty.includes(e.key))}),(0,t.jsx)(eo.default,{visible:eR,onCancel:()=>eU(!1),onSubmit:tS,initialData:eE,mode:"edit",config:{title:"Edit Member",showEmail:!0,showUserId:!0,roleOptions:[{label:"Admin",value:"admin"},{label:"User",value:"user"}],additionalFields:[{name:"max_budget_in_team",label:(0,t.jsxs)("span",{children:["Team Member Budget (USD)"," ",(0,t.jsx)(B.Tooltip,{title:"Maximum amount in USD this member can spend within this team. This is separate from any global user budget limits",children:(0,t.jsx)(p.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),type:"numerical",step:.01,min:0,placeholder:"Budget limit for this member within this team"},{name:"budget_duration",label:(0,t.jsxs)("span",{children:["Budget Reset Period"," ",(0,t.jsx)(B.Tooltip,{title:"How often this member's budget resets within the team. Leave unset and the budget never resets.",children:(0,t.jsx)(p.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),type:"budget-duration"},{name:"tpm_limit",label:(0,t.jsxs)("span",{children:["Team Member TPM Limit"," ",(0,t.jsx)(B.Tooltip,{title:"Maximum tokens per minute this member can use within this team. This is separate from any global user TPM limit",children:(0,t.jsx)(p.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),type:"numerical",step:1,min:0,placeholder:"Tokens per minute limit for this member in this team"},{name:"rpm_limit",label:(0,t.jsxs)("span",{children:["Team Member RPM Limit"," ",(0,t.jsx)(B.Tooltip,{title:"Maximum requests per minute this member can make within this team. This is separate from any global user RPM limit",children:(0,t.jsx)(p.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),type:"numerical",step:1,min:0,placeholder:"Requests per minute limit for this member in this team"},{name:"allowed_models",label:(0,t.jsxs)("span",{children:["Allowed Models"," ",(0,t.jsx)(B.Tooltip,{title:"Models this member can access within this team. Leave empty to inherit all team models.",children:(0,t.jsx)(p.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),type:"multi-select",options:(tC.models||[]).map(e=>({label:e,value:e})),placeholder:"Leave empty to inherit all team models"}]}}),(0,t.jsx)(i.default,{isVisible:ek,onCancel:()=>eM(!1),onSubmit:tT,accessToken:o,teamId:e}),(0,t.jsx)(W.default,{isOpen:te,title:"Delete Team Member",alertMessage:"Removing team members will also delete any keys created by or created for this member.",message:"Are you sure you want to remove this member from the team? This action cannot be undone.",resourceInformationTitle:"Team Member Information",resourceInformation:[{label:"User ID",value:e7?.user_id,code:!0},{label:"Email",value:e7?.user_email},{label:"Role",value:e7?.role}],onCancel:()=>{tt(!1),e9(null)},onOk:tw,confirmLoading:tl})]})}],56567)}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/054z32giulaw7.js b/litellm/proxy/_experimental/out/_next/static/chunks/054z32giulaw7.js deleted file mode 100644 index e5097101fb1..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/054z32giulaw7.js +++ /dev/null @@ -1 +0,0 @@ -(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,799062,e=>{"use strict";var a=e.i(843476),t=e.i(271645),l=e.i(197647),s=e.i(653824),i=e.i(881073),n=e.i(404206),r=e.i(723731),o=e.i(560445),d=e.i(207082),c=e.i(135214),u=e.i(332102);e.i(707701);var m=e.i(807235),g=e.i(494862);e.i(622826);var x=e.i(200208),h=e.i(399536),p=e.i(964471);function b({value:e}){return e?(0,a.jsx)("span",{className:"block max-w-60 truncate",title:e,children:e}):(0,a.jsx)("span",{className:"text-muted-foreground",children:"-"})}let j=[{id:"deleted_at",desc:!0}];function f(){return(0,a.jsxs)("div",{className:"flex flex-col items-center gap-1 py-6",children:[(0,a.jsx)("div",{className:"mb-1 flex size-10 items-center justify-center rounded-lg bg-muted",children:(0,a.jsx)(u.Inbox,{className:"size-5 text-muted-foreground"})}),(0,a.jsx)("div",{className:"text-sm font-medium text-foreground",children:"No deleted keys found"}),(0,a.jsx)("div",{className:"text-sm text-muted-foreground",children:"Keys deleted from this proxy will show up here."})]})}function y({keys:e,totalCount:l,isLoading:s,pagination:i,onPaginationChange:n}){let[r,o]=(0,t.useState)(j),d=(0,t.useMemo)(()=>[{id:"token",accessorKey:"token",meta:{title:"Key ID"},header:"Key ID",size:150,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(h.IdCell,{value:e.original.token,variant:"plain"})},{id:"key_alias",accessorKey:"key_alias",meta:{title:"Key Alias"},header:"Key Alias",size:150,enableSorting:!1,cell:({row:e})=>{let t=e.original.key_alias;return t?(0,a.jsx)("span",{className:"block max-w-60 truncate font-mono text-xs",title:t,children:t}):(0,a.jsx)("span",{className:"text-muted-foreground",children:"-"})}},{id:"team_alias",accessorKey:"team_alias",meta:{title:"Team Alias"},header:"Team Alias",size:120,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(b,{value:e.original.team_alias})},{id:"spend",accessorKey:"spend",meta:{title:"Spend (USD)",numeric:!0},header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Spend (USD)"}),size:100,enableSorting:!0,cell:({row:e})=>(0,a.jsx)(p.MoneyCell,{value:e.original.spend,decimals:4})},{id:"max_budget",accessorKey:"max_budget",meta:{title:"Budget (USD)",numeric:!0},header:"Budget (USD)",size:110,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(p.MoneyCell,{value:e.original.max_budget,decimals:0,emptyText:"Unlimited",showZero:!0})},{id:"user_email",accessorKey:"user_email",meta:{title:"User Email"},header:"User Email",size:160,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(b,{value:e.original.user_email})},{id:"user_id",accessorKey:"user_id",meta:{title:"User ID"},header:"User ID",size:120,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(h.IdCell,{value:e.original.user_id,variant:"plain"})},{id:"created_at",accessorKey:"created_at",meta:{title:"Created At"},header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Created At"}),size:120,enableSorting:!0,cell:({row:e})=>(0,a.jsx)(x.DateCell,{value:e.original.created_at,precision:"date"})},{id:"created_by",accessorKey:"created_by",meta:{title:"Created By"},header:"Created By",size:120,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(b,{value:e.original.created_by})},{id:"deleted_at",accessorKey:"deleted_at",meta:{title:"Deleted At"},header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Deleted At"}),size:120,enableSorting:!0,cell:({row:e})=>(0,a.jsx)(x.DateCell,{value:e.original.deleted_at,precision:"date"})},{id:"deleted_by",accessorKey:"deleted_by",meta:{title:"Deleted By"},header:"Deleted By",size:120,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(b,{value:e.original.deleted_by})}],[]);return(0,a.jsx)(m.DataTable,{data:e,columns:d,getRowId:(e,a)=>e.token||String(a),sortingMode:"client",sorting:r,onSortingChange:o,paginationMode:"server",pagination:i,onPaginationChange:n,rowCount:l,isLoading:s,loadingMessage:"Loading deleted keys…",noDataMessage:(0,a.jsx)(f,{}),size:"compact"})}function _(){let{premiumUser:e}=(0,c.default)(),[l,s]=(0,t.useState)({pageIndex:0,pageSize:50}),{data:i,isLoading:n}=(0,d.useDeletedKeys)(l.pageIndex+1,l.pageSize);return(0,a.jsxs)("div",{className:"flex flex-col gap-4",children:[!e&&(0,a.jsx)(o.Alert,{type:"info",banner:!0,showIcon:!0,message:"Coming soon to Enterprise",description:"Deleted key auditing is graduating from beta into our Enterprise audit & compliance suite."}),(0,a.jsx)(y,{keys:i?.keys||[],totalCount:i?.total_count||0,isLoading:n,pagination:l,onPaginationChange:s})]})}var v=e.i(785242),S=e.i(547227);let C=[{id:"deleted_at",desc:!0}];function T(){return(0,a.jsxs)("div",{className:"flex flex-col items-center gap-1 py-6",children:[(0,a.jsx)("div",{className:"mb-1 flex size-10 items-center justify-center rounded-lg bg-muted",children:(0,a.jsx)(u.Inbox,{className:"size-5 text-muted-foreground"})}),(0,a.jsx)("div",{className:"text-sm font-medium text-foreground",children:"No deleted teams found"}),(0,a.jsx)("div",{className:"text-sm text-muted-foreground",children:"Teams deleted from this proxy will show up here."})]})}function N({teams:e,isLoading:l}){let[s,i]=(0,t.useState)(C),n=(0,t.useMemo)(()=>[{id:"team_alias",accessorKey:"team_alias",meta:{title:"Team Name"},header:"Team Name",size:150,enableSorting:!1,cell:({row:e})=>{let t=e.original.team_alias;return t?(0,a.jsx)("span",{className:"block max-w-60 truncate font-medium",title:t,children:t}):(0,a.jsx)("span",{className:"text-muted-foreground",children:"-"})}},{id:"team_id",accessorKey:"team_id",meta:{title:"Team ID"},header:"Team ID",size:150,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(h.IdCell,{value:e.original.team_id,variant:"plain"})},{id:"created_at",accessorKey:"created_at",meta:{title:"Created"},header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Created"}),size:120,enableSorting:!0,cell:({row:e})=>(0,a.jsx)(x.DateCell,{value:e.original.created_at,precision:"date"})},{id:"spend",accessorKey:"spend",meta:{title:"Spend (USD)",numeric:!0},header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Spend (USD)"}),size:100,enableSorting:!0,cell:({row:e})=>(0,a.jsx)(p.MoneyCell,{value:e.original.spend,decimals:4})},{id:"max_budget",accessorKey:"max_budget",meta:{title:"Budget (USD)",numeric:!0},header:"Budget (USD)",size:110,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(p.MoneyCell,{value:e.original.max_budget,decimals:0,emptyText:"Unlimited",showZero:!0})},{id:"models",accessorKey:"models",meta:{title:"Models",skeleton:"chips"},header:"Models",size:200,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(S.ModelsCell,{models:e.original.models})},{id:"organization_id",accessorKey:"organization_id",meta:{title:"Organization"},header:"Organization",size:150,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(h.IdCell,{value:e.original.organization_id,variant:"plain"})},{id:"deleted_at",accessorKey:"deleted_at",meta:{title:"Deleted At"},header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Deleted At"}),size:120,enableSorting:!0,cell:({row:e})=>(0,a.jsx)(x.DateCell,{value:e.original.deleted_at,precision:"date"})},{id:"deleted_by",accessorKey:"deleted_by",meta:{title:"Deleted By"},header:"Deleted By",size:120,enableSorting:!1,cell:({row:e})=>{let t=e.original.deleted_by;return t?(0,a.jsx)("span",{className:"block max-w-60 truncate",title:t,children:t}):(0,a.jsx)("span",{className:"text-muted-foreground",children:"-"})}}],[]);return(0,a.jsx)(m.DataTable,{data:e,columns:n,getRowId:(e,a)=>e.team_id||String(a),sortingMode:"client",sorting:s,onSortingChange:i,isLoading:l,loadingMessage:"Loading deleted teams…",noDataMessage:(0,a.jsx)(T,{}),size:"compact"})}function k(){let{premiumUser:e}=(0,c.default)(),{data:t,isLoading:l}=(0,v.useDeletedTeams)(1,100);return(0,a.jsxs)("div",{className:"flex flex-col gap-4",children:[!e&&(0,a.jsx)(o.Alert,{type:"info",banner:!0,showIcon:!0,message:"Coming soon to Enterprise",description:"Deleted team auditing is graduating from beta into our Enterprise audit & compliance suite."}),(0,a.jsx)(N,{teams:t||[],isLoading:l})]})}var D=e.i(266027),M=e.i(619273),L=e.i(555987),w=e.i(602869),I=e.i(176516),z=e.i(981080),F=e.i(531649),K=e.i(793479),P=e.i(967489),O=e.i(997422),A=e.i(112179),E=e.i(304911);let Y={LiteLLM_VerificationToken:"Keys",LiteLLM_TeamTable:"Teams",LiteLLM_UserTable:"Users",LiteLLM_OrganizationTable:"Organizations",LiteLLM_ProxyModelTable:"Models"},H={created:"success",updated:"info",deleted:"error",rotated:"warning"},q=[{label:"Created",value:"created"},{label:"Updated",value:"updated"},{label:"Deleted",value:"deleted"},{label:"Rotated",value:"rotated"}],R=[{label:"Keys",value:"LiteLLM_VerificationToken"},{label:"Teams",value:"LiteLLM_TeamTable"},{label:"Users",value:"LiteLLM_UserTable"},{label:"Organizations",value:"LiteLLM_OrganizationTable"},{label:"Models",value:"LiteLLM_ProxyModelTable"}],U={object_id:"Object ID",changed_by:"Changed By",team_id:"Team ID",key_hash:"Key Hash",action:"Action",table_name:"Table"},B=(e,a)=>{let t=String(a);return"action"===e?q.find(e=>e.value===t)?.label??t:"table_name"===e?Y[t]??t:t};function V({filtered:e}){return(0,a.jsxs)("div",{className:"flex flex-col items-center gap-1 py-6",children:[(0,a.jsx)("div",{className:"mb-1 flex size-10 items-center justify-center rounded-lg bg-muted",children:(0,a.jsx)(I.ScrollText,{className:"size-5 text-muted-foreground"})}),(0,a.jsx)("div",{className:"text-sm font-medium text-foreground",children:e?"No matching audit logs":"No audit logs yet"}),(0,a.jsx)("div",{className:"max-w-xs text-center text-sm text-muted-foreground",children:e?"No audit log entries match your filters.":"Administrative changes to keys, teams, users, and models will appear here."})]})}function $({data:e,rowCount:l,isLoading:s,isRefreshing:i,pagination:n,onPaginationChange:r,columnFilters:o,onColumnFiltersChange:d,onRefresh:c,onViewLog:u}){let[g,p]=(0,t.useState)(!1),b=(0,t.useMemo)(()=>(({onViewLog:e})=>[{id:"updated_at",accessorKey:"updated_at",header:"Timestamp",size:200,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(x.DateCell,{value:e.original.updated_at})},{id:"action",accessorKey:"action",header:"Action",size:110,enableSorting:!1,cell:({row:e})=>{let t;return(0,a.jsx)(A.StatusBadge,{tone:H[e.original.action]??"neutral",label:(t=e.original.action)?t.charAt(0).toUpperCase()+t.slice(1):t})}},{id:"table_name",accessorKey:"table_name",header:"Table",size:130,enableSorting:!1,cell:({row:e})=>(0,a.jsx)("span",{className:"text-sm",children:Y[e.original.table_name]??e.original.table_name})},{id:"object_id",accessorKey:"object_id",header:"Object ID",minSize:220,enableSorting:!1,cell:({row:t})=>(0,a.jsx)(O.IdentityCell,{title:t.original.object_id,titleClassName:"font-mono text-xs font-normal text-primary",className:"max-w-72",onClick:()=>e(t.original)})},{id:"changed_by",accessorKey:"changed_by",header:"Changed By",size:200,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(E.default,{userId:e.original.changed_by})},{id:"changed_by_api_key",accessorKey:"changed_by_api_key",header:"API Key (Hash)",size:160,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(h.IdCell,{value:e.original.changed_by_api_key,variant:"plain"})}])({onViewLog:u}),[u]);return(0,a.jsx)(m.DataTable,{data:e,columns:b,getRowId:e=>e.id,paginationMode:"server",pagination:n,onPaginationChange:r,rowCount:l,filterMode:"server",columnFilters:o,onColumnFiltersChange:d,isLoading:s,loadingMessage:"Loading audit logs…",noDataMessage:(0,a.jsx)(V,{filtered:o.length>0}),size:"compact",toolbar:e=>(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(F.DataTableToolbar,{table:e,onRefresh:c,isRefreshing:i,onOpenFilters:()=>p(!0),filterLabels:U,formatFilterValue:B,showViewOptions:!1}),(0,a.jsx)(z.DataTableFilterDrawer,{table:e,open:g,onOpenChange:p,title:"Filters",description:"Narrow down audit log entries",children:({get:e,set:t})=>(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(z.DataTableFilterField,{label:"Object ID",children:(0,a.jsx)(K.Input,{value:e("object_id")??"",onChange:e=>t("object_id",e.target.value),placeholder:"Enter object ID…"})}),(0,a.jsx)(z.DataTableFilterField,{label:"Changed By",children:(0,a.jsx)(K.Input,{value:e("changed_by")??"",onChange:e=>t("changed_by",e.target.value),placeholder:"Enter user ID…"})}),(0,a.jsx)(z.DataTableFilterField,{label:"Team ID",children:(0,a.jsx)(K.Input,{value:e("team_id")??"",onChange:e=>t("team_id",e.target.value),placeholder:"Enter team ID…"})}),(0,a.jsx)(z.DataTableFilterField,{label:"Key Hash",children:(0,a.jsx)(K.Input,{value:e("key_hash")??"",onChange:e=>t("key_hash",e.target.value),placeholder:"Enter key hash…"})}),(0,a.jsx)(z.DataTableFilterField,{label:"Action",children:(0,a.jsxs)(P.Select,{value:e("action")??"all",onValueChange:e=>t("action","all"===e?void 0:e),children:[(0,a.jsx)(P.SelectTrigger,{className:"w-full",children:(0,a.jsx)(P.SelectValue,{placeholder:"All Actions"})}),(0,a.jsxs)(P.SelectContent,{children:[(0,a.jsx)(P.SelectItem,{value:"all",children:"All Actions"}),q.map(e=>(0,a.jsx)(P.SelectItem,{value:e.value,children:e.label},e.value))]})]})}),(0,a.jsx)(z.DataTableFilterField,{label:"Table",children:(0,a.jsxs)(P.Select,{value:e("table_name")??"all",onValueChange:e=>t("table_name","all"===e?void 0:e),children:[(0,a.jsx)(P.SelectTrigger,{className:"w-full",children:(0,a.jsx)(P.SelectValue,{placeholder:"All Tables"})}),(0,a.jsxs)(P.SelectContent,{children:[(0,a.jsx)(P.SelectItem,{value:"all",children:"All Tables"}),R.map(e=>(0,a.jsx)(P.SelectItem,{value:e.value,children:e.label},e.value))]})]})})]})})]})})}var Q=e.i(608856),J=e.i(262218),W=e.i(898586),G=e.i(149192),Z=e.i(166406),X=e.i(492030),ee=e.i(166540);let{Text:ea}=W.Typography,et={LiteLLM_VerificationToken:"Keys",LiteLLM_TeamTable:"Teams",LiteLLM_UserTable:"Users",LiteLLM_OrganizationTable:"Organizations",LiteLLM_ProxyModelTable:"Models"},el={created:"green",updated:"blue",deleted:"red",rotated:"orange"};function es({label:e,value:l}){let[s,i]=(0,t.useState)(!1),n=(0,t.useCallback)(async()=>{try{let e=JSON.stringify(l,null,2);if(navigator.clipboard&&window.isSecureContext)await navigator.clipboard.writeText(e);else{let a=document.createElement("textarea");a.value=e,a.style.position="fixed",a.style.opacity="0",document.body.appendChild(a),a.focus(),a.select(),document.execCommand("copy"),document.body.removeChild(a)}i(!0),setTimeout(()=>i(!1),2e3)}catch(e){console.error("Copy failed:",e)}},[l]);return(0,a.jsxs)("div",{className:"bg-white rounded-sm border overflow-hidden",children:[(0,a.jsxs)("div",{className:"flex justify-between items-center px-3 py-2 border-b bg-gray-50",children:[(0,a.jsx)("span",{className:"text-xs font-semibold text-gray-600",children:e}),(0,a.jsx)("button",{onClick:n,className:"p-1 hover:bg-gray-200 rounded-sm text-gray-500 hover:text-gray-700 transition-colors",title:"Copy JSON",children:s?(0,a.jsx)(X.CheckOutlined,{className:"text-green-600"}):(0,a.jsx)(Z.CopyOutlined,{})})]}),(0,a.jsx)("pre",{className:"p-3 bg-white text-xs font-mono overflow-auto max-h-96 whitespace-pre-wrap break-all m-0",children:JSON.stringify(l,null,2)})]})}function ei({label:e,value:t}){return(0,a.jsxs)("div",{className:"flex items-start gap-2 py-1.5",children:[(0,a.jsx)("span",{className:"text-xs text-gray-500 w-36 shrink-0",children:e}),(0,a.jsx)("span",{className:"text-xs text-gray-900 break-all",children:t})]})}function en({log:e}){let{action:t,table_name:l,before_value:s,updated_values:i}=e,n="LiteLLM_VerificationToken"===l,r="updated"===t||"rotated"===t,o=s,d=i;if(r&&s&&i){let e={},a={};new Set([...Object.keys(s),...Object.keys(i)]).forEach(t=>{JSON.stringify(s[t])!==JSON.stringify(i[t])&&(t in s&&(e[t]=s[t]),t in i&&(a[t]=i[t]))}),Object.keys(s).forEach(t=>{t in i||t in e||(e[t]=s[t],a[t]=void 0)}),Object.keys(i).forEach(t=>{t in s||t in a||(a[t]=i[t],e[t]=void 0)}),o=Object.keys(e).length>0?e:{note:"No differing fields detected"},d=Object.keys(a).length>0?a:{note:"No differing fields detected"}}let c=(e,t)=>{if(!t||0===Object.keys(t).length)return(0,a.jsxs)("div",{className:"bg-white rounded-sm border overflow-hidden",children:[(0,a.jsx)("div",{className:"flex items-center px-3 py-2 border-b bg-gray-50",children:(0,a.jsx)("span",{className:"text-xs font-semibold text-gray-600",children:e})}),(0,a.jsx)("p",{className:"px-3 py-3 text-xs text-gray-400 italic m-0",children:"N/A"})]});if(n&&r){let l=["token","spend","max_budget"];if(Object.keys(t).every(e=>l.includes(e))&&!("note"in t))return(0,a.jsxs)("div",{className:"bg-white rounded-sm border overflow-hidden",children:[(0,a.jsx)("div",{className:"flex items-center px-3 py-2 border-b bg-gray-50",children:(0,a.jsx)("span",{className:"text-xs font-semibold text-gray-600",children:e})}),(0,a.jsxs)("div",{className:"px-3 py-3 space-y-1 text-xs",children:[void 0!==t.token&&(0,a.jsxs)("p",{children:[(0,a.jsx)("span",{className:"text-gray-500",children:"Token:"})," ",t.token??"N/A"]}),void 0!==t.spend&&(0,a.jsxs)("p",{children:[(0,a.jsx)("span",{className:"text-gray-500",children:"Spend:"})," $",Number(t.spend).toFixed(6)]}),void 0!==t.max_budget&&(0,a.jsxs)("p",{children:[(0,a.jsx)("span",{className:"text-gray-500",children:"Max Budget:"})," $",Number(t.max_budget).toFixed(6)]})]})]})}return(0,a.jsx)(es,{label:e,value:t})};return(0,a.jsxs)("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-4 mt-4",children:[c("Before",o),c("After",d)]})}function er({open:e,onClose:t,log:l}){if(!l)return null;let s=et[l.table_name]??l.table_name,i=el[l.action]??"default";return(0,a.jsxs)(Q.Drawer,{placement:"right",width:"60%",open:e,onClose:t,closable:!1,mask:!0,maskClosable:!0,styles:{body:{padding:0,display:"flex",flexDirection:"column"},header:{display:"none"}},children:[(0,a.jsxs)("div",{className:"flex items-center justify-between px-6 py-4 border-b bg-white shrink-0",children:[(0,a.jsxs)("div",{className:"flex items-center gap-3",children:[(0,a.jsx)(J.Tag,{color:i,className:"capitalize m-0",children:l.action}),(0,a.jsx)("span",{className:"text-sm text-gray-500",children:ee.default.utc(l.updated_at).local().format("MMM D, YYYY HH:mm:ss")})]}),(0,a.jsx)("button",{onClick:t,className:"w-8 h-8 flex items-center justify-center rounded-sm hover:bg-gray-100 text-gray-500","aria-label":"Close",children:(0,a.jsx)(G.CloseOutlined,{})})]}),(0,a.jsxs)("div",{className:"px-6 py-5",children:[(0,a.jsxs)("div",{className:"bg-gray-50 border rounded-lg p-4 mb-5",children:[(0,a.jsx)("p",{className:"text-xs font-semibold text-gray-700 mb-2 uppercase tracking-wide",children:"Details"}),(0,a.jsx)(ei,{label:"Table",value:s}),(0,a.jsx)(ei,{label:"Object ID",value:(0,a.jsx)(ea,{copyable:!0,className:"font-mono text-xs",children:l.object_id})}),(0,a.jsx)(ei,{label:"Changed By",value:(0,a.jsx)(E.default,{userId:l.changed_by})}),(0,a.jsx)(ei,{label:"API Key (Hash)",value:l.changed_by_api_key?(0,a.jsx)(ea,{copyable:!0,className:"font-mono text-xs break-all",children:l.changed_by_api_key}):"—"})]}),(0,a.jsx)(en,{log:l})]})]})}function eo({userID:e,userRole:l,token:s,accessToken:i,isActive:n,premiumUser:r}){let[o,d]=(0,t.useState)({pageIndex:0,pageSize:50}),[c,u]=(0,t.useState)([]),[m,g]=(0,t.useState)(null),[x,h]=(0,t.useState)(!1),p=e=>{let a=c.find(a=>a.id===e);return"string"==typeof a?.value&&a.value.trim()?a.value.trim():void 0},b=!!i&&!!s&&!!l&&!!e&&n&&r,j=(0,D.useQuery)({queryKey:["audit_logs",o.pageIndex,o.pageSize,c],queryFn:async()=>i?(0,w.uiAuditLogsCall)({accessToken:i,page:o.pageIndex+1,page_size:o.pageSize,params:{object_id:p("object_id"),changed_by:p("changed_by"),object_key_hash:p("key_hash"),object_team_id:p("team_id"),action:p("action"),table_name:p("table_name"),sort_by:"updated_at",sort_order:"desc"}}):{audit_logs:[],total:0,page:1,page_size:o.pageSize,total_pages:0},enabled:b,placeholderData:M.keepPreviousData}),f=(0,t.useCallback)(e=>{u(e),d(e=>({...e,pageIndex:0}))},[]),y=(0,t.useCallback)(e=>{g(e),h(!0)},[]);return r?(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)("div",{className:"flex items-center justify-between mb-4",children:(0,a.jsx)("h1",{className:"text-xl font-semibold",children:"Audit Logs"})}),(0,a.jsx)($,{data:j.data?.audit_logs??[],rowCount:j.data?.total??0,isLoading:j.isLoading,isRefreshing:j.isFetching,pagination:o,onPaginationChange:d,columnFilters:c,onColumnFiltersChange:f,onRefresh:()=>j.refetch(),onViewLog:y}),(0,a.jsx)(er,{open:x,onClose:()=>h(!1),log:m})]}):(0,a.jsxs)("div",{style:{textAlign:"center",marginTop:"20px"},children:[(0,a.jsx)("h1",{style:{display:"block",marginBottom:"10px"},children:"✨ Enterprise Feature."}),(0,a.jsx)("p",{style:{display:"block",marginBottom:"10px"},children:"This is a LiteLLM Enterprise feature, and requires a valid key to use."}),(0,a.jsx)("p",{style:{display:"block",marginBottom:"20px",fontStyle:"italic"},children:"Here's a preview of what Audit Logs offer:"}),(0,a.jsx)("img",{src:(0,L.resolveLogoSrc)("/ui/assets/audit-logs-preview.png"),alt:"Audit Logs Preview",style:{maxWidth:"100%",maxHeight:"700px",borderRadius:"8px",boxShadow:"0 4px 8px rgba(0,0,0,0.1)",margin:"0 auto"},onError:e=>{e.target.style.display="none"}})]})}var ed=e.i(548151),ec=e.i(708347),eu=e.i(20147),em=e.i(97859);let eg=async(e,a)=>{if(!e)return[];try{let t=[],l=1,s=!0;for(;s;){let i=await (0,w.teamListCall)(e,a||null,null);t=[...t,...i],l({start_date:(0,ee.default)(e).utc().format("YYYY-MM-DD HH:mm:ss"),end_date:t?(0,ee.default)(a).utc().format("YYYY-MM-DD HH:mm:ss"):(0,ee.default)(l).utc().format("YYYY-MM-DD HH:mm:ss")}),eM=[{id:"startTime",desc:!0}],eL=(e,a)=>{let t=e.find(e=>e.id===a);if("string"!=typeof t?.value)return;let l=t.value.trim();return""===l?void 0:l};e.i(3565);var ew=e.i(502626);let eI=(0,e.i(475254).default)("calendar-days",[["path",{d:"M8 2v4",key:"1cmpym"}],["path",{d:"M16 2v4",key:"4m81vk"}],["rect",{width:"18",height:"18",x:"3",y:"4",rx:"2",key:"1hopcy"}],["path",{d:"M3 10h18",key:"8toen8"}],["path",{d:"M8 14h.01",key:"6423bh"}],["path",{d:"M12 14h.01",key:"1etili"}],["path",{d:"M16 14h.01",key:"1gbofw"}],["path",{d:"M8 18h.01",key:"lrp35t"}],["path",{d:"M12 18h.01",key:"mhygvu"}],["path",{d:"M16 18h.01",key:"kzsmim"}]]);var ez=e.i(519455),eF=e.i(337822),eK=e.i(699375);function eP({startTime:e,onStartTimeChange:l,endTime:s,onEndTimeChange:i,isCustomDate:n,onIsCustomDateChange:r,selectedTimeInterval:o,onSelectedTimeIntervalChange:d,isLiveTail:c,onIsLiveTailChange:u,onResetToFirstPage:m,onResetFilters:g}){let[x,h]=(0,t.useState)(!1),p=em.QUICK_SELECT_OPTIONS.find(e=>e.value===o.value&&e.unit===o.unit),b=n?((e,a,t)=>{if(e)return`${(0,ee.default)(a).format("MMM D, h:mm A")} - ${(0,ee.default)(t).format("MMM D, h:mm A")}`;let l=(0,ee.default)(),s=(0,ee.default)(a),i=l.diff(s,"minutes");if(i>=0&&i<2)return"Last 1 Minute";if(i>=2&&i<16)return"Last 15 Minutes";if(i>=16&&i<61)return"Last Hour";let n=l.diff(s,"hours");return n>=1&&n<5?"Last 4 Hours":n>=5&&n<25?"Last 24 Hours":n>=25&&n<169?"Last 7 Days":`${s.format("MMM D")} - ${l.format("MMM D")}`})(n,e,s):p?.label;return(0,a.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,a.jsxs)(eF.Popover,{open:x,onOpenChange:h,children:[(0,a.jsx)(eF.PopoverTrigger,{render:(0,a.jsxs)(ez.Button,{variant:"outline",size:"sm",className:"gap-2",children:[(0,a.jsx)(eI,{className:"size-4"}),b]})}),(0,a.jsx)(eF.PopoverContent,{align:"start",className:"w-64 p-2",children:(0,a.jsxs)("div",{className:"space-y-1",children:[em.QUICK_SELECT_OPTIONS.map(e=>(0,a.jsx)(ez.Button,{variant:"ghost",className:"w-full justify-start font-normal",onClick:()=>{m(),i((0,ee.default)().format("YYYY-MM-DDTHH:mm")),l((0,ee.default)().subtract(e.value,e.unit).format("YYYY-MM-DDTHH:mm")),d({value:e.value,unit:e.unit}),r(!1),h(!1)},children:e.label},e.label)),(0,a.jsx)("div",{className:"my-2 border-t"}),(0,a.jsx)(ez.Button,{variant:"ghost",className:"w-full justify-start font-normal",onClick:()=>r(!n),children:"Custom Range"})]})})]}),n&&(0,a.jsxs)("div",{className:"flex items-center gap-2",children:[(0,a.jsx)(K.Input,{type:"datetime-local",className:"w-auto",value:e,onChange:e=>{l(e.target.value),m()}}),(0,a.jsx)("span",{className:"text-sm text-muted-foreground",children:"to"}),(0,a.jsx)(K.Input,{type:"datetime-local",className:"w-auto",value:s,onChange:e=>{i(e.target.value),m()}})]}),(0,a.jsxs)("div",{className:"flex items-center gap-2",children:[(0,a.jsx)("span",{className:"text-sm font-medium",children:"Live Tail"}),(0,a.jsx)(eK.Switch,{checked:c,onCheckedChange:u,"aria-label":"Live Tail"})]}),(0,a.jsx)(ez.Button,{variant:"outline",size:"sm",onClick:g,children:"Reset Filters"})]})}function eO({onStop:e}){return(0,a.jsxs)("div",{className:"mb-4 flex items-center justify-between rounded-md border border-green-200 bg-green-50 px-4 py-2",children:[(0,a.jsx)("span",{className:"text-sm text-green-700",children:"Auto-refreshing every 15 seconds"}),(0,a.jsx)("button",{type:"button",onClick:e,className:"text-sm text-green-600 hover:text-green-800",children:"Stop"})]})}var eA=e.i(768371);let eE=e=>{let a=e.links.next;if(!a)return;let t=new URLSearchParams(a.slice(a.indexOf("?")+1)).get("page");return null===t?void 0:Number(t)};var eY=e.i(621482);let eH=(0,e.i(243652).createQueryKeys)("infiniteKeyAliases");var eq=e.i(625901),eR=e.i(744582),eU=e.i(552546),eB=e.i(131792);let eV=e=>""===e?void 0:e;function e$({value:e,onChange:l,teams:s}){let i=(0,t.useMemo)(()=>s.map(e=>({label:e.team_alias||e.team_id,value:e.team_id,sublabel:e.team_id})),[s]);return(0,a.jsx)(z.DataTableFilterField,{label:"Team ID",children:(0,a.jsx)(eU.SearchSelect,{options:i,value:e,onValueChange:e=>l(eV(e)),placeholder:"Search or select a team",emptyText:"No teams found"})})}function eQ({value:e,onChange:l,teamId:s}){let[i,n]=(0,t.useState)(""),{data:r,fetchNextPage:o,hasNextPage:d,isFetchingNextPage:u,isLoading:m}=((e=50,a,t)=>{let{accessToken:l}=(0,c.default)();return(0,eY.useInfiniteQuery)({queryKey:eH.list({filters:{size:e,...a&&{search:a},...t&&{team_id:t}}}),queryFn:async({pageParam:s})=>await (0,w.keyAliasesCall)(l,s,e,a,t),initialPageParam:1,getNextPageParam:e=>{if(e.current_page{let e=new Set;return(r?.pages??[]).flatMap(a=>a.aliases.flatMap(a=>!a||e.has(a)?[]:(e.add(a),[{label:a,value:a}])))},[r]);return(0,a.jsx)(z.DataTableFilterField,{label:"Key Alias",children:(0,a.jsx)(eR.PaginatedSearchSelect,{options:g,value:e,onValueChange:e=>l(eV(e)),onSearchChange:n,onLoadMore:()=>void o(),hasNextPage:d,isLoading:m,isFetchingNextPage:u,placeholder:"Search a key alias",emptyText:"No key aliases found"})})}function eJ({value:e,onChange:l}){let[s,i]=(0,t.useState)(""),{data:n,fetchNextPage:r,hasNextPage:o,isFetchingNextPage:d,isLoading:c}=(0,eq.useInfiniteModelInfo)(50,eV(s)),u=(0,t.useMemo)(()=>{let e=new Set;return(n?.pages??[]).flatMap(a=>a.data.flatMap(a=>{let t=a.model_info?.id??"",l=a.model_name??"";return!t||e.has(t)?[]:(e.add(t),[{label:l||t,value:t,sublabel:`Model ID: ${t}`}])}))},[n]);return(0,a.jsx)(z.DataTableFilterField,{label:"Model",children:(0,a.jsx)(eR.PaginatedSearchSelect,{options:u,value:e,onValueChange:e=>l(eV(e)),onSearchChange:i,onLoadMore:()=>void r(),hasNextPage:o,isLoading:c,isFetchingNextPage:d,placeholder:"Search a model",emptyText:"No models found"})})}function eW({value:e,onChange:l,logsWindow:s}){let[i,n]=(0,t.useState)(""),{data:r,fetchNextPage:o,hasNextPage:d,isFetchingNextPage:u,isLoading:m}=((e,a=50,t)=>{let{accessToken:l}=(0,c.default)(),s={"filter[startTime][gte]":e.start_date,"filter[startTime][lte]":e.end_date,page_size:a,...void 0!==t&&""!==t?{q:t}:{}};return eA.$api.useInfiniteQuery("get","/management/v1/spend_logs/end_users",{params:{query:s}},{pageParamName:"page",initialPageParam:1,getNextPageParam:eE,enabled:!!l})})(s,50,eV(i)),g=(0,t.useMemo)(()=>{let e=new Set;return(r?.pages??[]).flatMap(a=>a.data.flatMap(a=>!a||e.has(a)?[]:(e.add(a),[{label:a,value:a}])))},[r]);return(0,a.jsx)(z.DataTableFilterField,{label:"End User",children:(0,a.jsx)(eR.PaginatedSearchSelect,{options:g,value:e,onValueChange:e=>l(eV(e)),onSearchChange:n,onLoadMore:()=>void o(),hasNextPage:d,isLoading:m,isFetchingNextPage:u,placeholder:"Search an end user",emptyText:"No end users in this time range"})})}function eG({value:e,onChange:l}){let[s,i]=(0,t.useState)(""),n=(0,t.useMemo)(()=>{let e=s.trim(),a=e.toLowerCase(),t=em.ERROR_CODE_OPTIONS.filter(e=>e.label.toLowerCase().includes(a));return""===e||em.ERROR_CODE_OPTIONS.some(a=>a.value===e)?t:[...t,{label:`Use custom code: ${e}`,value:e}]},[s]),r=(0,t.useMemo)(()=>""===e?null:em.ERROR_CODE_OPTIONS.find(a=>a.value===e)??{label:e,value:e},[e]),o=(0,t.useMemo)(()=>null===r||n.some(e=>e.value===r.value)?n:[r,...n],[n,r]);return(0,a.jsx)(z.DataTableFilterField,{label:"Error Code",children:(0,a.jsxs)(eB.Combobox,{items:o,value:r,onValueChange:e=>l(eV(e?.value??"")),onInputValueChange:i,isItemEqualToValue:(e,a)=>e.value===a.value,itemToStringLabel:e=>e.label,filter:null,children:[(0,a.jsx)(eB.ComboboxInput,{placeholder:"Select or type an error code",showClear:""!==e,className:"w-full"}),(0,a.jsxs)(eB.ComboboxContent,{children:[(0,a.jsx)(eB.ComboboxEmpty,{children:"No error codes found"}),(0,a.jsx)(eB.ComboboxList,{"data-testid":"error-code-filter-list",children:e=>(0,a.jsx)(eB.ComboboxItem,{value:e,children:e.label},e.value)})]})]})})}function eZ({get:e,set:t,teams:l,logsWindow:s}){let i=a=>{let t;return"string"==typeof(t=e(a))?t:""},n=e=>a=>t(e,a);return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(e$,{value:i(ep),onChange:n(ep),teams:l}),(0,a.jsx)(z.DataTableFilterField,{label:"Status",children:(0,a.jsxs)(P.Select,{value:""===i(eb)?"all":i(eb),onValueChange:e=>t(eb,null===e||"all"===e?void 0:e),children:[(0,a.jsx)(P.SelectTrigger,{className:"w-full",children:(0,a.jsx)(P.SelectValue,{placeholder:"All Statuses"})}),(0,a.jsxs)(P.SelectContent,{children:[(0,a.jsx)(P.SelectItem,{value:"all",children:"All Statuses"}),(0,a.jsx)(P.SelectItem,{value:"success",children:"Success"}),(0,a.jsx)(P.SelectItem,{value:"failure",children:"Failure"})]})]})}),(0,a.jsx)(eQ,{value:i(ej),onChange:n(ej),teamId:i(ep)}),(0,a.jsx)(eW,{value:i(ef),onChange:n(ef),logsWindow:s}),(0,a.jsx)(eG,{value:i(ey),onChange:n(ey)}),(0,a.jsx)(z.DataTableFilterField,{label:"Error Message",children:(0,a.jsx)(K.Input,{value:i(e_),onChange:e=>t(e_,eV(e.target.value)),placeholder:"Enter error message…"})}),(0,a.jsx)(z.DataTableFilterField,{label:"Key Hash",children:(0,a.jsx)(K.Input,{value:i(ev),onChange:e=>t(ev,eV(e.target.value)),placeholder:"Enter key hash…"})}),(0,a.jsx)(z.DataTableFilterField,{label:"Session ID",children:(0,a.jsx)(K.Input,{value:i(eS),onChange:e=>t(eS,eV(e.target.value)),placeholder:"Enter session ID…"})}),(0,a.jsx)(eJ,{value:i(eC),onChange:n(eC)}),(0,a.jsx)(z.DataTableFilterField,{label:"Public model / search tool",children:(0,a.jsx)(K.Input,{value:i(eT),onChange:e=>t(eT,eV(e.target.value)),placeholder:"Enter public model or search tool…"})})]})}var eX=e.i(581070),e0=e.i(500330),e1=e.i(916925);let e2=({size:e=12})=>(0,a.jsx)("svg",{width:e,height:e,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"shrink-0 text-gray-400",children:(0,a.jsx)("path",{d:"M12 3l1.912 5.813a2 2 0 0 0 1.275 1.275L21 12l-5.813 1.912a2 2 0 0 0-1.275 1.275L12 21l-1.912-5.813a2 2 0 0 0-1.275-1.275L3 12l5.813-1.912a2 2 0 0 0 1.275-1.275L12 3z"})}),e5=({size:e=10})=>(0,a.jsx)("svg",{width:e,height:e,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"shrink-0",children:(0,a.jsx)("path",{d:"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"})}),e4=({size:e=12})=>(0,a.jsxs)("svg",{width:e,height:e,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"shrink-0",children:[(0,a.jsx)("path",{d:"M12 8V4H8"}),(0,a.jsx)("rect",{width:"16",height:"12",x:"4",y:"8",rx:"2"}),(0,a.jsx)("path",{d:"M2 14h2"}),(0,a.jsx)("path",{d:"M20 14h2"}),(0,a.jsx)("path",{d:"M15 13v2"}),(0,a.jsx)("path",{d:"M9 13v2"})]}),e6=({count:e})=>(0,a.jsxs)("span",{className:"inline-flex items-center gap-1 px-2 py-0.5 bg-blue-50 text-blue-700 border border-blue-200 rounded-full text-[11px] font-medium whitespace-nowrap",children:[(0,a.jsx)(e2,{}),null!=e?e:"LLM"]}),e7=({count:e})=>(0,a.jsxs)("span",{className:"inline-flex items-center gap-1 px-2 py-0.5 bg-amber-50 text-amber-700 border border-amber-200 rounded-full text-[11px] font-medium whitespace-nowrap",children:[(0,a.jsx)(e5,{}),null!=e?e:"MCP"]}),e3=({count:e})=>(0,a.jsxs)("span",{className:"inline-flex items-center gap-1 px-2 py-0.5 bg-violet-50 text-violet-700 border border-violet-200 rounded-full text-[11px] font-medium whitespace-nowrap",children:[(0,a.jsx)(e4,{}),null!=e?e:"Agent"]}),e8=(e,a)=>{let t=e?.[a];return"string"==typeof t&&""!==t?t:void 0};function e9({value:e}){let t=e??"-";return(0,a.jsx)(eX.CellTooltip,{content:t,trigger:(0,a.jsx)("span",{className:"max-w-[15ch] truncate block",children:t})})}function ae({filtered:e}){return(0,a.jsxs)("div",{className:"flex flex-col items-center gap-1 py-6",children:[(0,a.jsx)("div",{className:"mb-1 flex size-10 items-center justify-center rounded-lg bg-muted",children:(0,a.jsx)(I.ScrollText,{className:"size-5 text-muted-foreground"})}),(0,a.jsx)("div",{className:"text-sm font-medium text-foreground",children:e?"No matching requests":"No requests yet"}),(0,a.jsx)("div",{className:"max-w-xs text-center text-sm text-muted-foreground",children:e?"No requests match your filters for this time range.":"Requests proxied through LiteLLM will appear here."})]})}function aa({data:e,rowCount:l,isLoading:s,isRefreshing:i,pagination:n,onPaginationChange:r,sorting:o,onSortingChange:d,columnFilters:c,onColumnFiltersChange:u,searchValue:b,onSearchChange:j,onRefresh:f,onRowClick:y,onKeyHashClick:_,onSessionClick:v,teams:S,logsWindow:C,toolbarChildren:T}){let[N,k]=(0,t.useState)(!1),D=(0,t.useMemo)(()=>(({onKeyHashClick:e,onSessionClick:t})=>[{id:"startTime",accessorKey:"startTime",header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Time",variant:"dropdown-tristate"}),size:200,enableSorting:!0,cell:({row:e})=>(0,a.jsx)(x.DateCell,{value:e.original.startTime})},{id:"type",header:"Type",size:90,enableSorting:!1,meta:{skeleton:"badge"},cell:({row:e})=>{let t=e.original,l=t.session_total_count||1,s=em.MCP_CALL_TYPES.includes(t.call_type),i=em.AGENT_CALL_TYPES.includes(t.call_type),n=t.session_llm_count??(s||i?0:l),r=t.session_agent_count??(i?l:0),o=t.session_mcp_count??(s?l:0);if(s)return(0,a.jsx)(e7,{});if(i&&l<=1)return(0,a.jsx)(e3,{});if(l<=1)return(0,a.jsx)(e6,{});let d=(0,a.jsxs)("span",{className:"inline-flex items-center gap-1 px-2 py-0.5 bg-blue-50 text-blue-700 border border-blue-200 rounded-full text-[11px] font-medium whitespace-nowrap",children:[(0,a.jsx)(e2,{}),(0,a.jsx)("span",{children:l}),r>0&&(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)("span",{className:"text-blue-300",children:"·"}),(0,a.jsx)(e4,{size:10})]}),o>0&&(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)("span",{className:"text-blue-300",children:"·"}),(0,a.jsx)(e5,{})]})]}),c=[n>0&&`${n} LLM`,r>0&&`${r} Agent`,o>0&&`${o} MCP`].filter(Boolean);return(0,a.jsx)(eX.CellTooltip,{content:c.join(" • "),trigger:d})}},{id:"status",header:"Status",size:100,enableSorting:!1,meta:{skeleton:"badge"},cell:({row:e})=>{let t="failure"!==(e8(e.original.metadata,"status")??"Success").toLowerCase();return(0,a.jsx)(A.StatusBadge,{tone:t?"success":"error",label:t?"Success":"Failure"})}},{id:"session_id",accessorKey:"session_id",header:"Session ID",size:120,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(h.IdCell,{value:e.original.session_id,onClick:t})},{id:"request_id",accessorKey:"request_id",header:"Request ID",enableSorting:!1,cell:({row:e})=>(0,a.jsx)(h.IdCell,{value:e.original.request_id,variant:"plain"})},{id:"spend",accessorKey:"spend",header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Cost",variant:"dropdown-tristate"}),size:110,enableSorting:!0,meta:{numeric:!0,skeleton:"twoLine"},cell:({row:e})=>{let t=e.original,l=t.mcp_tool_call_count||0,s=t.mcp_tool_call_spend||0,i=(t.session_total_count||1)>1,n=i&&null!=t.session_total_spend?t.session_total_spend:t.spend,r=(0,a.jsx)("span",{children:(0,a.jsx)(p.MoneyCell,{value:n,decimals:6})});return(0,a.jsxs)("div",{className:"flex flex-col items-end",children:[n?(0,a.jsx)(eX.CellTooltip,{content:`$${String(n)}`,trigger:r}):r,i&&(0,a.jsx)("span",{className:"text-[10px] text-gray-400",children:"session total"}),l>0&&s>0&&(0,a.jsxs)("span",{className:"text-[10px] text-amber-600",children:["incl. ",(0,e0.getSpendString)(s)," from ",l," MCP"]})]})}},{id:"request_duration_ms",accessorKey:"request_duration_ms",header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Duration (s)",variant:"dropdown-tristate"}),enableSorting:!0,meta:{numeric:!0},cell:({row:e})=>{let t=e.original.request_duration_ms;return null==t?(0,a.jsx)("span",{children:"-"}):(0,a.jsx)(eX.CellTooltip,{content:`${t}ms`,trigger:(0,a.jsx)("span",{className:"max-w-[15ch] truncate inline-block",children:(t/1e3).toFixed(2)})})}},{id:"ttft_ms",accessorKey:"completionStartTime",header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"TTFT (s)",variant:"dropdown-tristate"}),enableSorting:!0,meta:{numeric:!0},cell:({row:e})=>{let t=e.original,l=t.completionStartTime;if(!l||l===t.endTime)return(0,a.jsx)("span",{children:"-"});let s=new Date(l).getTime()-new Date(t.startTime).getTime();return s<=0?(0,a.jsx)("span",{children:"-"}):(0,a.jsx)(eX.CellTooltip,{content:`${s}ms`,trigger:(0,a.jsx)("span",{className:"max-w-[15ch] truncate inline-block",children:(s/1e3).toFixed(2)})})}},{id:"team_alias",header:"Team Name",size:150,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(e9,{value:e8(e.original.metadata,"user_api_key_team_alias")})},{id:"key_hash",header:"Key Hash",size:110,enableSorting:!1,cell:({row:t})=>(0,a.jsx)(h.IdCell,{value:e8(t.original.metadata,"user_api_key"),variant:"plain",onClick:e})},{id:"key_alias",header:"Key Alias",size:150,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(e9,{value:e8(e.original.metadata,"user_api_key_alias")})},{id:"model",accessorKey:"model",header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Model",variant:"dropdown-tristate"}),size:200,enableSorting:!0,cell:({row:e})=>{let t=e.original,l=t.custom_llm_provider,s=t.model??"";return(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[l&&(0,a.jsx)("img",{src:(e=>{let a=e?.mcp_tool_call_metadata;if("object"!=typeof a||null===a)return;let t=a.mcp_server_logo_url;return"string"==typeof t&&""!==t?t:void 0})(t.metadata)??(l?(0,e1.getProviderLogoAndName)(l).logo:""),alt:"",className:"w-4 h-4",onError:e=>{e.currentTarget.style.display="none"}}),(0,a.jsx)(eX.CellTooltip,{content:s,trigger:(0,a.jsx)("span",{className:"max-w-[15ch] truncate block",children:s})})]})}},{id:"total_tokens",accessorKey:"total_tokens",header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Tokens",variant:"dropdown-tristate"}),size:140,enableSorting:!0,meta:{numeric:!0},cell:({row:e})=>{let t=e.original;return(0,a.jsxs)("span",{className:"text-sm",children:[String(t.total_tokens||"0"),(0,a.jsxs)("span",{className:"text-gray-400 text-xs ml-1",children:["(",String(t.prompt_tokens||"0"),"+",String(t.completion_tokens||"0"),")"]})]})}},{id:"user",accessorKey:"user",header:"Internal User",size:150,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(e9,{value:e.original.user})},{id:"end_user",accessorKey:"end_user",header:"End User",size:140,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(e9,{value:e.original.end_user})},{id:"request_tags",accessorKey:"request_tags",header:"Tags",size:150,enableSorting:!1,meta:{skeleton:"chips"},cell:({row:e})=>{let t=e.original.request_tags;if(!t||0===Object.keys(t).length)return"-";let l=Object.entries(t),[s,i]=l[0],n=l.length-1;return(0,a.jsx)("div",{className:"flex flex-wrap gap-1",children:(0,a.jsx)(eX.CellTooltip,{content:(0,a.jsx)("div",{className:"flex flex-col gap-1",children:l.map(([e,t])=>(0,a.jsxs)("span",{children:[e,": ",String(t)]},e))}),trigger:(0,a.jsxs)("span",{className:"px-2 py-1 bg-gray-100 rounded-full text-xs",children:[s,": ",String(i),n>0&&` +${n}`]})})})}}])({onKeyHashClick:_,onSessionClick:v}),[_,v]),M=c.length>0||""!==b;return(0,a.jsx)(m.DataTable,{data:e,columns:D,getRowId:e=>e.request_id,sortingMode:"server",sorting:o,onSortingChange:d,paginationMode:"server",pagination:n,onPaginationChange:r,rowCount:l,filterMode:"server",columnFilters:c,onColumnFiltersChange:u,isLoading:s,loadingMessage:"Loading request logs…",noDataMessage:(0,a.jsx)(ae,{filtered:M}),size:"compact",onRowClick:y,toolbar:e=>(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(F.DataTableToolbar,{table:e,searchValue:b,onSearchChange:j,searchPlaceholder:"Search by Request ID",onRefresh:f,isRefreshing:i,onOpenFilters:()=>k(!0),filterLabels:ek,showViewOptions:!1,children:T}),(0,a.jsx)(z.DataTableFilterDrawer,{table:e,open:N,onOpenChange:k,title:"Filters",description:"Narrow down request logs",children:({get:e,set:t})=>(0,a.jsx)(eZ,{get:e,set:t,teams:S,logsWindow:C})})]})})}let at={value:24,unit:"hours"};function al({accessToken:e,token:l,userRole:s,userID:i,isActive:n}){let[r,o]=(0,t.useState)({pageIndex:0,pageSize:50}),[d,c]=(0,t.useState)(eM),[u,m]=(0,t.useState)([]),[g,x]=(0,t.useState)((0,ee.default)().subtract(24,"hours").format("YYYY-MM-DDTHH:mm")),[h,p]=(0,t.useState)((0,ee.default)().format("YYYY-MM-DDTHH:mm")),[b,j]=(0,t.useState)(!1),[f,y]=(0,t.useState)(at),[_,v]=(0,t.useState)(null),[S,C]=(0,t.useState)(null),[T,N]=(0,t.useState)(!1),[k,L]=(0,t.useState)(null),[I,z]=(0,t.useState)(()=>{let e=sessionStorage.getItem("isLiveTail");return null===e||JSON.parse(e)});(0,t.useEffect)(()=>{sessionStorage.setItem("isLiveTail",JSON.stringify(I))},[I]);let F=ec.internalUserRoles.includes(s),{logsQuery:K,filteredLogs:P,allTeams:O}=function({accessToken:e,token:a,userRole:t,userID:l,columnFilters:s,filterByCurrentUser:i,activeTab:n,isLiveTail:r,startTime:o,endTime:d,pagination:c,isCustomDate:u,sorting:m}){let g,x=c.pageSize||ex.defaultPageSize,h=m[0]??eM[0],p=Object.hasOwn(eh,h.id)?h.id:"startTime",b=h.desc?"desc":"asc",j={queryKey:["logs","table",c.pageIndex,x,o,d,u,s,i?l:null,p,b],queryFn:async()=>{if(!e||!a||!t||!l)return{data:[],total:0,page:1,page_size:x,total_pages:0};let n=eD(o,d,u),r=eL(s,"user_id");return await (0,w.uiSpendLogsCall)({accessToken:e,start_date:n.start_date,end_date:n.end_date,page:c.pageIndex+1,page_size:x,params:{api_key:eL(s,ev),team_id:eL(s,ep),request_id:eL(s,eN),session_id:eL(s,eS),user_id:r??(i?l??void 0:void 0),end_user:eL(s,ef),status_filter:eL(s,eb),model_id:eL(s,eC),model:eL(s,eT),key_alias:eL(s,ej),error_code:eL(s,ey),error_message:eL(s,e_),sort_by:p,sort_order:b}})},enabled:!!e&&!!a&&!!t&&!!l&&"request logs"===n,refetchInterval:(g=c.pageIndex,!!r&&0===g&&15e3),placeholderData:M.keepPreviousData,refetchIntervalInBackground:!1},f=(0,D.useQuery)(j),y=f.data??{data:[],total:0,page:1,page_size:x,total_pages:0},{data:_}=(0,D.useQuery)({queryKey:["allTeamsForLogFilters",e],queryFn:async()=>e&&await eg(e)||[],enabled:!!e});return{logsQuery:f,filteredLogs:y,allTeams:_}}({accessToken:e,token:l,userRole:s,userID:i,columnFilters:u,filterByCurrentUser:F,activeTab:n?"request logs":"inactive",isLiveTail:I,startTime:g,endTime:h,pagination:r,isCustomDate:b,sorting:d}),A=(Math.floor((K.dataUpdatedAt||Date.parse(h))/6e4)+1)*6e4,E=(0,t.useMemo)(()=>eD(g,h,b,A),[g,h,b,A]),{data:Y}=(0,D.useQuery)({queryKey:["requestLogsKeyInfo",_,e],queryFn:async()=>null===_?null:{...(await (0,w.keyInfoV1Call)(e,_)).info,token:_,api_key:_},enabled:null!==_}),H=(0,t.useMemo)(()=>{let e=P.data,a=e.reduce((e,a)=>(a.session_id&&(e[a.session_id]||(e[a.session_id]={llm:0,agent:0,mcp:0}),em.MCP_CALL_TYPES.includes(a.call_type)?e[a.session_id].mcp+=1:em.AGENT_CALL_TYPES.includes(a.call_type)?e[a.session_id].agent+=1:e[a.session_id].llm+=1),e),{}),t=new Map;for(let a of e){if(!a.session_id||1>=(a.session_total_count||1))continue;let e=em.MCP_CALL_TYPES.includes(a.call_type),l=t.get(a.session_id);l&&(!l.isMcp||e)||t.set(a.session_id,{requestId:a.request_id,isMcp:e})}return e.map(e=>{let t=e.session_id?a[e.session_id]:void 0;return{...e,session_llm_count:t?.llm??void 0,session_mcp_count:t?.mcp??void 0,session_agent_count:t?.agent??void 0}}).filter(e=>!e.session_id||1>=(e.session_total_count||1)||t.get(e.session_id)?.requestId===e.request_id)},[P.data]),q=(0,t.useMemo)(()=>{let e=u.find(e=>e.id===eN);return"string"==typeof e?.value?e.value:""},[u]),R=(0,t.useCallback)(e=>{m(a=>{let t=a.filter(e=>e.id!==eN);return""===e?t:[...t,{id:eN,value:e}]}),o(e=>({...e,pageIndex:0}))},[]),U=(0,t.useCallback)(e=>{c(e),o(e=>({...e,pageIndex:0}))},[]),B=(0,t.useCallback)(e=>{m(e),o(e=>({...e,pageIndex:0}))},[]),V=(0,t.useCallback)(()=>{o(e=>({...e,pageIndex:0}))},[]),$=(0,t.useCallback)(()=>{m([]),x((0,ee.default)().subtract(24,"hours").format("YYYY-MM-DDTHH:mm")),p((0,ee.default)().format("YYYY-MM-DDTHH:mm")),j(!1),y(at),V()},[V]),Q=(0,t.useCallback)(e=>{L(void 0!==e.session_id&&(e.session_total_count||1)>1?e.session_id??null:null),C(e),N(!0)},[]),J=(0,t.useCallback)(e=>{if(!e)return;let a=H.find(a=>a.session_id===e)??null;L(e),C(a),N(!0)},[H]),W=(0,t.useCallback)(e=>{v(e)},[]);return Y&&_&&Y.api_key===_?(0,a.jsx)(eu.default,{keyId:_,keyData:Y,teams:O??[],onClose:()=>v(null),backButtonText:"Back to Logs"}):(0,a.jsxs)(ed.AutoRouterModelGroupsProvider,{children:[(0,a.jsx)("div",{className:"flex items-center justify-between mb-4",children:(0,a.jsx)("h1",{className:"text-xl font-semibold",children:"Request Logs"})}),I&&0===r.pageIndex&&(0,a.jsx)(eO,{onStop:()=>z(!1)}),(0,a.jsx)(aa,{data:H,rowCount:P.total,isLoading:K.isLoading,isRefreshing:K.isFetching,pagination:r,onPaginationChange:o,sorting:d,onSortingChange:U,columnFilters:u,onColumnFiltersChange:B,searchValue:q,onSearchChange:R,onRefresh:()=>void K.refetch(),onRowClick:Q,onKeyHashClick:W,onSessionClick:J,teams:O??[],logsWindow:E,toolbarChildren:(0,a.jsx)(eP,{startTime:g,onStartTimeChange:x,endTime:h,onEndTimeChange:p,isCustomDate:b,onIsCustomDateChange:j,selectedTimeInterval:f,onSelectedTimeIntervalChange:y,isLiveTail:I,onIsLiveTailChange:z,onResetToFirstPage:V,onResetFilters:$})}),(0,a.jsx)(ew.LogDetailsDrawer,{open:T,onClose:()=>{N(!1),L(null)},logEntry:S,sessionId:k,accessToken:e,allLogs:H,onSelectLog:C,startTime:(0,ee.default)(g).utc().format("YYYY-MM-DD HH:mm:ss")})]})}var as=e.i(482725),ai=e.i(56456);function an({size:e,fontSize:t}){let l=(0,a.jsx)(ai.LoadingOutlined,{style:t?{fontSize:t}:void 0,spin:!0});return(0,a.jsx)(as.Spin,{indicator:l,size:e})}function ar({accessToken:e,token:o,userRole:d,userID:c,premiumUser:u}){let[m,g]=(0,t.useState)("request logs");return e&&o&&d&&c?(0,a.jsx)("div",{className:"w-full p-6 overflow-x-hidden box-border",children:(0,a.jsxs)(s.TabGroup,{defaultIndex:0,onIndexChange:e=>g(0===e?"request logs":"audit logs"),children:[(0,a.jsxs)(i.TabList,{children:[(0,a.jsx)(l.Tab,{children:"Request Logs"}),(0,a.jsx)(l.Tab,{children:"Audit Logs"}),(0,a.jsx)(l.Tab,{children:"Deleted Keys"}),(0,a.jsx)(l.Tab,{children:"Deleted Teams"})]}),(0,a.jsxs)(r.TabPanels,{children:[(0,a.jsx)(n.TabPanel,{children:(0,a.jsx)(al,{accessToken:e,token:o,userRole:d,userID:c,isActive:"request logs"===m})}),(0,a.jsx)(n.TabPanel,{children:(0,a.jsx)(eo,{userID:c,userRole:d,token:o,accessToken:e,isActive:"audit logs"===m,premiumUser:u})}),(0,a.jsx)(n.TabPanel,{children:(0,a.jsx)(_,{})}),(0,a.jsx)(n.TabPanel,{children:(0,a.jsx)(k,{})})]})]})}):(0,a.jsx)("div",{className:"flex items-center justify-center h-64",children:(0,a.jsx)(an,{size:"large"})})}e.s(["default",0,function(){let{accessToken:e,userRole:t,userId:l,token:s,premiumUser:i}=(0,c.default)();return(0,a.jsx)(ar,{userID:l,userRole:t,token:s,accessToken:e,premiumUser:i})}],799062)}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/058j9m4b8p4wx.js b/litellm/proxy/_experimental/out/_next/static/chunks/058j9m4b8p4wx.js deleted file mode 100644 index 361fcf6e3e1..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/058j9m4b8p4wx.js +++ /dev/null @@ -1 +0,0 @@ -(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,677572,370359,405934,e=>{"use strict";var t,r,n,a=e.i(843476);e.s([],559657),e.i(559657),e.i(247167);var i=e.i(271645),s=e.i(951437),l=e.i(146376),o=e.i(667865),u=e.i(552245),c=e.i(53687),d=e.i(733332);let f=i.createContext(void 0);function h(){let e=i.useContext(f);if(void 0===e)throw Error((0,d.default)(64));return e}let p=((t={}).activationDirection="data-activation-direction",t.orientation="data-orientation",t),m={tabActivationDirection:e=>({[p.activationDirection]:e})};var g=e.i(675606),x=e.i(56434);let v=i.forwardRef(function(e,t){let{className:r,defaultValue:n=0,onValueChange:d,orientation:h="horizontal",render:p,value:v,style:y,..._}=e,w=void 0!==e.defaultValue,S=i.useRef([]),[C,N]=i.useState(()=>new Map),[T,A]=(0,s.useControlled)({controlled:v,default:n,name:"Tabs",state:"value"}),E=void 0!==v,[j,O]=i.useState(()=>new Map),R=i.useRef(void 0),k=i.useCallback(e=>{if(void 0===e)return null;for(let[t,r]of j.entries())if(null!=r&&e===(r.value??r.index))return t;return null},[j]),[I,M]=i.useState(()=>({previousValue:T,tabActivationDirection:"none"})),{previousValue:L,tabActivationDirection:P}=I,U=P,H=!1;L!==T&&(U=b(L,T,h,j),H=null!=L&&null!=T&&null==k(T));let D=H?L:T,z=L!==D||P!==U;(0,l.useIsoLayoutEffect)(()=>{z&&M({previousValue:D,tabActivationDirection:U})},[D,z,U]);let W=(0,o.useStableCallback)((e,t)=>{t.activationDirection=b(T,e,h,j),d?.(e,t),t.isCanceled||A(e)}),B=(0,o.useStableCallback)((e,t)=>{d?.(e,(0,g.createChangeEventDetails)(t,void 0,void 0,{activationDirection:"none"}))}),V=(0,o.useStableCallback)((e,t)=>{N(r=>{if(r.get(e)===t)return r;let n=new Map(r);return n.set(e,t),n})}),F=(0,o.useStableCallback)((e,t)=>{N(r=>{if(!r.has(e)||r.get(e)!==t)return r;let n=new Map(r);return n.delete(e),n})}),K=i.useCallback(e=>C.get(e),[C]),$=i.useCallback(e=>{for(let t of j.values())if(e===t?.value)return t?.id},[j]),Y=i.useMemo(()=>({getTabElementBySelectedValue:k,getTabIdByPanelValue:$,getTabPanelIdByValue:K,onValueChange:W,orientation:h,registerMountedTabPanel:V,setTabMap:O,unregisterMountedTabPanel:F,tabActivationDirection:U,value:T}),[k,$,K,W,h,V,O,F,U,T]),G=i.useMemo(()=>{for(let e of j.values())if(null!=e&&e.value===T)return e},[j,T]),J=i.useMemo(()=>{for(let e of j.values())if(null!=e&&!e.disabled)return e.value},[j]),X=i.useRef(!w),q=i.useRef(n),Z=i.useRef(w),Q=i.useRef(!1);(0,l.useIsoLayoutEffect)(()=>{if(E)return;function e(e,t){A(e),M(t=>t.previousValue===e&&"none"===t.tabActivationDirection?t:{previousValue:e,tabActivationDirection:"none"}),B(e,t),X.current=!1}if(0===j.size){Q.current&&null!==T&&!R.current?.isConnected&&e(null,x.REASONS.missing);return}Q.current=!0,R.current=j.keys().next().value;let t=G?.disabled,r=null==G&&null!==T;if(t||T!==q.current||(Z.current=!1),Z.current&&t&&T===q.current)return;let n=X.current;if(t||r){let r=J??null;if(T===r){X.current=!1;return}let a=x.REASONS.missing;n?a=x.REASONS.initial:t&&(a=x.REASONS.disabled),e(r,a);return}n&&null!=G&&(B(T,x.REASONS.initial),X.current=!1)},[J,E,B,G,A,j,T]);let ee={orientation:h,tabActivationDirection:U},et=(0,u.useRenderElement)("div",e,{state:ee,ref:t,props:_,stateAttributesMapping:m});return(0,a.jsx)(f.Provider,{value:Y,children:(0,a.jsx)(c.CompositeList,{elementsRef:S,children:et})})});function b(e,t,r,n){if(null==e||null==t)return"none";let a=null,i=null;for(let[r,s]of n.entries()){if(null==s)continue;let n=s.value??s.index;if(e===n&&(a=r),t===n&&(i=r),null!=a&&null!=i)break}if(null==a||null==i)return a!==i&&("number"==typeof e||"string"==typeof e)&&typeof e==typeof t?"horizontal"===r?t>e?"right":"left":t>e?"down":"up":"none";let s=a.getBoundingClientRect(),l=i.getBoundingClientRect();if("horizontal"===r){if(l.lefts.left)return"right"}else{if(l.tops.top)return"down"}return"none"}var y=e.i(108868),_=e.i(788015),w=e.i(540886);let S="data-composite-item-active";e.s(["ACTIVE_COMPOSITE_ITEM",0,S],370359);var C=e.i(395530);let N=i.createContext(void 0);function T(){let e=i.useContext(N);if(void 0===e)throw Error((0,d.default)(65));return e}var A=e.i(647554);let E=i.forwardRef(function(e,t){let{className:r,disabled:n=!1,render:a,value:s,id:o,nativeButton:c=!0,style:d,...f}=e,{value:p,getTabPanelIdByValue:v,orientation:b,tabActivationDirection:N}=h(),{activateOnFocus:E,highlightedTabIndex:j,onTabActivation:O,registerTabResizeObserverElement:R,setHighlightedTabIndex:k,tabsListElement:I}=T(),M=(0,_.useBaseUiId)(o),L=i.useMemo(()=>({disabled:n,id:M,value:s}),[n,M,s]),{compositeProps:P,compositeRef:U,index:H}=(0,C.useCompositeItem)({metadata:L}),D=s===p,z=i.useRef(!1),W=i.useRef(null);(0,l.useIsoLayoutEffect)(()=>{let e=W.current;if(e)return R(e)},[R]),(0,l.useIsoLayoutEffect)(()=>{if(z.current){z.current=!1;return}if(D&&H>-1&&j!==H){if(null!=I){let e=(0,A.activeElement)((0,y.ownerDocument)(I));if(e&&(0,A.contains)(I,e))return}n||k(H)}},[D,H,j,k,n,I]);let{getButtonProps:B,buttonRef:V}=(0,w.useButton)({disabled:n,native:c,focusableWhenDisabled:!0}),F=v(s),K=i.useRef(!1),$=i.useRef(!1);return(0,u.useRenderElement)("button",e,{state:{disabled:n,active:D,orientation:b,tabActivationDirection:N},ref:[t,V,U,W],props:[P,{role:"tab","aria-controls":F,"aria-selected":D,id:M,onClick:function(e){D||n||O(s,(0,g.createChangeEventDetails)(x.REASONS.none,e.nativeEvent,void 0,{activationDirection:"none"}))},onFocus:function(e){D||(H>-1&&!n&&k(H),!n&&E&&(!K.current||K.current&&$.current)&&O(s,(0,g.createChangeEventDetails)(x.REASONS.none,e.nativeEvent,void 0,{activationDirection:"none"})))},onPointerDown:function(e){D||n||(K.current=!0,e.button&&0!==e.button||($.current=!0,(0,y.ownerDocument)(e.currentTarget).addEventListener("pointerup",function(){K.current=!1,$.current=!1},{once:!0})))},[S]:D?"":void 0,onKeyDownCapture(){z.current=!0}},f,B],stateAttributesMapping:m})});var j=e.i(73364),O=e.i(802239),R=e.i(956789);function k(){return R.NOOP}function I(){return!1}function M(){return!0}let L=((r={}).activeTabLeft="--active-tab-left",r.activeTabRight="--active-tab-right",r.activeTabTop="--active-tab-top",r.activeTabBottom="--active-tab-bottom",r.activeTabWidth="--active-tab-width",r.activeTabHeight="--active-tab-height",r);var P=e.i(172410);let U={...m,activeTabPosition:()=>null,activeTabSize:()=>null},H=i.forwardRef(function(e,t){let{className:r,render:n,renderBeforeHydration:s=!1,style:l,...o}=e,{nonce:c}=(0,P.useCSPContext)(),{getTabElementBySelectedValue:d,orientation:f,tabActivationDirection:p,value:m}=h(),{tabsListElement:g,registerIndicatorUpdateListener:x}=T(),v=(0,O.useSyncExternalStore)(k,I,M),b=function(){let[,e]=i.useState({});return i.useCallback(()=>{e({})},[])}();i.useEffect(()=>x(b),[x,b]);let y=0,_=0,w=0,S=0,C=0,N=0,A=!1;if(null!=m&&null!=g){let e=d(m);if(null!=e){A=!0;let{width:t,height:r}=(0,j.getCssDimensions)(e),{width:n,height:a}=(0,j.getCssDimensions)(g),i=e.getBoundingClientRect(),s=g.getBoundingClientRect(),l=n>0?s.width/n:1,o=a>0?s.height/a:1;if(Math.abs(l)>Number.EPSILON&&Math.abs(o)>Number.EPSILON){let e=i.left-s.left,t=i.top-s.top;y=e/l+g.scrollLeft-g.clientLeft,w=t/o+g.scrollTop-g.clientTop}else y=e.offsetLeft,w=e.offsetTop;C=t,N=r,_=g.scrollWidth-y-C,S=g.scrollHeight-w-N}}let E=A?{left:y,right:_,top:w,bottom:S}:null,R=A?{width:C,height:N}:null,H=A?{[L.activeTabLeft]:`${y}px`,[L.activeTabRight]:`${_}px`,[L.activeTabTop]:`${w}px`,[L.activeTabBottom]:`${S}px`,[L.activeTabWidth]:`${C}px`,[L.activeTabHeight]:`${N}px`}:void 0,D=A&&C>0&&N>0,z=(0,u.useRenderElement)("span",e,{state:{orientation:f,activeTabPosition:E,activeTabSize:R,tabActivationDirection:p},ref:t,props:[{role:"presentation",style:H,hidden:!D},o,{suppressHydrationWarning:!0}],stateAttributesMapping:U});return null==m?null:(0,a.jsxs)(i.Fragment,{children:[z,v&&s&&(0,a.jsx)("script",{nonce:c,dangerouslySetInnerHTML:{__html:'!function(){const t=document.currentScript.previousElementSibling;if(!t)return;const e=t.closest(\'[role="tablist"]\');if(!e)return;const i=e.querySelector("[data-active]");if(!i)return;if(0===i.offsetWidth||0===e.offsetWidth)return;let o=0,n=0,h=0,l=0,r=0,f=0;function s(t){const e=getComputedStyle(t);let i=parseFloat(e.width)||0,o=parseFloat(e.height)||0;return(Math.round(i)!==t.offsetWidth||Math.round(o)!==t.offsetHeight)&&(i=t.offsetWidth,o=t.offsetHeight),{width:i,height:o}}if(null!=i&&null!=e){const{width:t,height:c}=s(i),{width:u,height:d}=s(e),a=i.getBoundingClientRect(),g=e.getBoundingClientRect(),p=u>0?g.width/u:1,b=d>0?g.height/d:1;if(Math.abs(p)>Number.EPSILON&&Math.abs(b)>Number.EPSILON){const t=a.left-g.left,i=a.top-g.top;o=t/p+e.scrollLeft-e.clientLeft,h=i/b+e.scrollTop-e.clientTop}else o=i.offsetLeft,h=i.offsetTop;r=t,f=c,n=e.scrollWidth-o-r,l=e.scrollHeight-h-f}function c(e,i){t.style.setProperty(`--active-tab-${e}`,`${i}px`)}c("left",o),c("right",n),c("top",h),c("bottom",l),c("width",r),c("height",f),r>0&&f>0&&t.removeAttribute("hidden")}();'},suppressHydrationWarning:!0})]})});var D=e.i(144394),z=e.i(209407),W=e.i(137584),B=e.i(223910),V=e.i(673553);let F=((n={}).index="data-index",n.activationDirection="data-activation-direction",n.orientation="data-orientation",n.hidden="data-hidden",n[n.startingStyle=z.TransitionStatusDataAttributes.startingStyle]="startingStyle",n[n.endingStyle=z.TransitionStatusDataAttributes.endingStyle]="endingStyle",n),K={...m,...z.transitionStatusMapping},$=i.forwardRef(function(e,t){let{className:r,value:n,render:a,keepMounted:s=!1,style:o,...c}=e,{value:d,getTabIdByPanelValue:f,orientation:p,tabActivationDirection:m,registerMountedTabPanel:g,unregisterMountedTabPanel:x}=h(),v=(0,_.useBaseUiId)(),b=i.useMemo(()=>({id:v,value:n}),[v,n]),{ref:y,index:w}=(0,V.useCompositeListItem)({metadata:b}),S=n===d,{mounted:C,transitionStatus:N,setMounted:T}=(0,B.useTransitionStatus)(S),A=!C,E=f(n),j=i.useRef(null),O=(0,u.useRenderElement)("div",e,{state:{hidden:A,orientation:p,tabActivationDirection:m,transitionStatus:N},ref:[t,y,j],props:[{"aria-labelledby":E,hidden:A,id:v,role:"tabpanel",tabIndex:S?0:-1,inert:(0,D.inertValue)(!S),[F.index]:w},c],stateAttributesMapping:K});return((0,W.useOpenChangeComplete)({open:S,ref:j,onComplete(){S||T(!1)}}),(0,l.useIsoLayoutEffect)(()=>{if((!A||s)&&null!=v)return g(n,v),()=>{x(n,v)}},[A,s,n,v,g,x]),s||C)?O:null});var Y=e.i(590803),G=e.i(828918),J=e.i(673327),X=e.i(621082);let q=[];var Z=e.i(838452),Q=e.i(872855);function ee(e){let{render:t,className:r,style:n,refs:s=R.EMPTY_ARRAY,props:d=R.EMPTY_ARRAY,state:f=R.EMPTY_OBJECT,stateAttributesMapping:h,highlightedIndex:p,onHighlightedIndexChange:m,orientation:g,grid:x,loopFocus:v,onLoop:b,enableHomeAndEndKeys:y,onMapChange:_,stopEventPropagation:w=!0,rootRef:C,disabledIndices:N,modifierKeys:T,highlightItemOnHover:E=!1,tag:j="div",...O}=e,{props:k,highlightedIndex:I,onHighlightedIndexChange:M,elementsRef:L,onMapChange:P,relayKeyboardEvent:U}=function(e){let{loopFocus:t=!0,orientation:r="both",grid:n,onLoop:a,direction:s,highlightedIndex:u,onHighlightedIndexChange:c,rootRef:d,enableHomeAndEndKeys:f=!1,stopEventPropagation:h=!1,disabledIndices:p,modifierKeys:m=q}=e,[g,x]=i.useState(0),v=null!=n,b=i.useRef(null),y=(0,G.useMergedRefs)(b,d),_=i.useRef([]),w=i.useRef(!1),C=u??g,N=(0,o.useStableCallback)((e,t=!1)=>{if((c??x)(e),t){let t=_.current[e];(0,J.scrollIntoViewIfNeeded)(b.current,t,s,r)}}),T=(0,o.useStableCallback)(e=>{if(0===e.size||w.current)return;w.current=!0;let t=Array.from(e.keys()),n=t.find(e=>e?.hasAttribute(S))??null,a=n?t.indexOf(n):-1;if(-1!==a)N(a);else if((0,X.isListIndexDisabled)(t,C,p)){let e=(0,X.findNonDisabledListIndex)(t,{disabledIndices:p});(0,X.isIndexOutOfListBounds)(t,e)||N(e)}(0,J.scrollIntoViewIfNeeded)(b.current,n,s,r)});(0,l.useIsoLayoutEffect)(()=>{if(null==p||null!=u||!w.current)return;let e=_.current;if((0,X.isListIndexDisabled)(e,C,p)){let t=(0,X.findNonDisabledListIndex)(e,{disabledIndices:p});(0,X.isIndexOutOfListBounds)(e,t)||N(t)}},[p,u,C,_,N]);let E=(0,o.useStableCallback)((e,t,r)=>a?a(e,t,r,_):r),j=(0,o.useStableCallback)(e=>{let i=f?J.COMPOSITE_KEYS:J.ARROW_KEYS;if(!i.has(e.key)||function(e,t){for(let r of J.MODIFIER_KEYS.values())if(!t.includes(r)&&e.getModifierState(r))return!0;return!1}(e,m)||!b.current)return;let l="rtl"===s,o=l?J.ARROW_LEFT:J.ARROW_RIGHT,u={horizontal:o,vertical:J.ARROW_DOWN,both:o}[r],c=l?J.ARROW_RIGHT:J.ARROW_LEFT,d={horizontal:c,vertical:J.ARROW_UP,both:c}[r],g=(0,A.getTarget)(e.nativeEvent);if(null!=g&&(0,J.isNativeInput)(g)&&!(0,Y.isElementDisabled)(g)){let t=g.selectionStart,r=g.selectionEnd,n=g.value??"";if(null==t||e.shiftKey||t!==r||e.key!==d&&t0)return}let x=C,y=(0,X.getMinListIndex)(_,p),w=(0,X.getMaxListIndex)(_,p);null!=n&&(x=n({disabledIndices:p,elementsRef:_,event:e,highlightedIndex:C,loopFocus:t,maxIndex:w,minIndex:y,onLoop:E,orientation:r,rtl:l}));let S={horizontal:[o],vertical:[J.ARROW_DOWN],both:[o,J.ARROW_DOWN]}[r],T={horizontal:[c],vertical:[J.ARROW_UP],both:[c,J.ARROW_UP]}[r],j=v?i:({horizontal:f?J.HORIZONTAL_KEYS_WITH_EXTRA_KEYS:J.HORIZONTAL_KEYS,vertical:f?J.VERTICAL_KEYS_WITH_EXTRA_KEYS:J.VERTICAL_KEYS,both:i})[r];f&&(e.key===J.HOME?x=y:e.key===J.END&&(x=w)),x===C&&(S.includes(e.key)||T.includes(e.key))&&(t&&x===w&&S.includes(e.key)?(x=y,a&&(x=a(e,C,x,_))):t&&x===y&&T.includes(e.key)?(x=w,a&&(x=a(e,C,x,_))):x=(0,X.findNonDisabledListIndex)(_.current,{startingIndex:x,decrement:T.includes(e.key),disabledIndices:p})),x===C||(0,X.isIndexOutOfListBounds)(_.current,x)||(h&&e.stopPropagation(),j.has(e.key)&&e.preventDefault(),N(x,!0),queueMicrotask(()=>{_.current[x]?.focus()}))});return{props:{ref:y,onFocus(e){let t=b.current,r=(0,A.getTarget)(e.nativeEvent);t&&null!=r&&(0,J.isNativeInput)(r)&&r.setSelectionRange(0,r.value.length??0)},onKeyDown:j},highlightedIndex:C,onHighlightedIndexChange:N,elementsRef:_,disabledIndices:p,onMapChange:T,relayKeyboardEvent:j}}({grid:x,loopFocus:v,onLoop:b,orientation:g,highlightedIndex:p,onHighlightedIndexChange:m,rootRef:C,stopEventPropagation:w,enableHomeAndEndKeys:y,direction:(0,Q.useDirection)(),disabledIndices:N,modifierKeys:T}),H=(0,u.useRenderElement)(j,e,{state:f,ref:s,props:[k,...d,O],stateAttributesMapping:h}),D=i.useMemo(()=>({highlightedIndex:I,onHighlightedIndexChange:M,highlightItemOnHover:E,relayKeyboardEvent:U}),[I,M,E,U]);return(0,a.jsx)(Z.CompositeRootContext.Provider,{value:D,children:(0,a.jsx)(c.CompositeList,{elementsRef:L,onMapChange:e=>{_?.(e),P(e)},children:H})})}e.s(["CompositeRoot",0,ee],405934);let et=i.forwardRef(function(e,t){let{activateOnFocus:r=!1,className:n,loopFocus:s=!0,render:u,style:c,...d}=e,{onValueChange:f,orientation:p,value:g,setTabMap:x,tabActivationDirection:v}=h(),[b,y]=i.useState(0),[_,w]=i.useState(null),S=i.useRef(new Set),C=i.useRef(new Set),T=i.useRef(null);(0,l.useIsoLayoutEffect)(()=>{if("u"{S.current.forEach(e=>{e()})});return T.current=e,_&&e.observe(_),C.current.forEach(t=>{e.observe(t)}),()=>{e.disconnect(),T.current=null}},[_]);let A=(0,o.useStableCallback)(e=>(S.current.add(e),()=>{S.current.delete(e)})),E=(0,o.useStableCallback)(e=>(C.current.add(e),T.current?.observe(e),()=>{C.current.delete(e),T.current?.unobserve(e)})),j=(0,o.useStableCallback)((e,t)=>{e!==g&&f(e,t)}),O=i.useMemo(()=>({activateOnFocus:r,highlightedTabIndex:b,registerIndicatorUpdateListener:A,registerTabResizeObserverElement:E,onTabActivation:j,setHighlightedTabIndex:y,tabsListElement:_}),[r,b,A,E,j,y,_]);return(0,a.jsx)(N.Provider,{value:O,children:(0,a.jsx)(ee,{render:u,className:n,style:c,state:{orientation:p,tabActivationDirection:v},refs:[t,w],props:[{"aria-orientation":"vertical"===p?"vertical":void 0,role:"tablist"},d],stateAttributesMapping:m,highlightedIndex:b,enableHomeAndEndKeys:!0,loopFocus:s,orientation:p,onHighlightedIndexChange:y,onMapChange:x,disabledIndices:R.EMPTY_ARRAY})})});e.s(["Indicator",0,H,"List",0,et,"Panel",0,$,"Root",0,v,"Tab",0,E],69281);var er=e.i(69281),er=er,en=e.i(115504);let ea=(0,en.cva)({base:"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-9 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",variants:{variant:{default:"bg-muted",line:"gap-1 bg-transparent"}},defaultVariants:{variant:"default"}});e.s(["Tabs",0,function({className:e,orientation:t="horizontal",...r}){return(0,a.jsx)(er.Root,{"data-slot":"tabs","data-orientation":t,className:(0,en.cn)("group/tabs flex gap-2 data-horizontal:flex-col",e),...r})},"TabsContent",0,function({className:e,...t}){return(0,a.jsx)(er.Panel,{"data-slot":"tabs-content",className:(0,en.cn)("flex-1 text-sm outline-none",e),...t})},"TabsList",0,function({className:e,variant:t="default",...r}){return(0,a.jsx)(er.List,{"data-slot":"tabs-list","data-variant":t,className:(0,en.cn)(ea({variant:t}),e),...r})},"TabsTrigger",0,function({className:e,...t}){return(0,a.jsx)(er.Tab,{"data-slot":"tabs-trigger",className:(0,en.cn)("relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4","group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent","data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground","after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",e),...t})}],677572)},531278,e=>{"use strict";let t=(0,e.i(475254).default)("loader-circle",[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]]);e.s(["Loader2",0,t],531278)},195116,e=>{"use strict";let t=(0,e.i(475254).default)("wrench",[["path",{d:"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z",key:"cbrjhi"}]]);e.s(["Wrench",0,t],195116)},180127,e=>{"use strict";let t=(0,e.i(475254).default)("arrow-left",[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]]);e.s(["default",0,t])},871689,e=>{"use strict";var t=e.i(180127);e.s(["ArrowLeft",()=>t.default])},434166,e=>{"use strict";e.s(["getSecureItem",0,function(e){try{let t=window.sessionStorage.getItem(e);if(null===t)return null;return decodeURIComponent(atob(t).split("").map(e=>"%"+e.charCodeAt(0).toString(16).padStart(2,"0")).join(""))}catch{return null}},"setSecureItem",0,function(e,t){window.sessionStorage.setItem(e,btoa(encodeURIComponent(t).replace(/%([0-9A-F]{2})/g,(e,t)=>String.fromCharCode(parseInt(t,16)))))}])},292335,122520,165615,779129,280024,e=>{"use strict";let t={NONE:"none",API_KEY:"api_key",BEARER_TOKEN:"bearer_token",TOKEN:"token",BASIC:"basic",OAUTH2:"oauth2",OAUTH2_TOKEN_EXCHANGE:"oauth2_token_exchange",AWS_SIGV4:"aws_sigv4",TRUE_PASSTHROUGH:"true_passthrough",OAUTH_DELEGATE:"oauth_delegate"},r=e=>e===t.TRUE_PASSTHROUGH||e===t.OAUTH_DELEGATE,n={INTERACTIVE:"interactive",M2M:"m2m"},a=e=>{let t=e.credentials??{};return JSON.stringify({url:"string"==typeof e.url?e.url:null,spec_path:"string"==typeof e.spec_path?e.spec_path:null,auth_type:e.auth_type??null,oauth_flow_type:e.oauth_flow_type??null,client_id:t.client_id??null,client_secret:t.client_secret??null,scopes:t.scopes??null,upstream_resource:t.upstream_resource??null,issuer:e.issuer??null,authorization_url:e.authorization_url??null,token_url:e.token_url??null,registration_url:e.registration_url??null})},i=["client_id","client_secret"],s=["upstream_resource"],l=["access_token","refresh_token","expires_in","scope"],o=(e,t)=>{if(!e)return;let r=Object.fromEntries(t.filter(t=>"string"==typeof e[t]&&""!==e[t]).map(t=>[t,e[t]]));return Object.keys(r).length>0?r:void 0},u="client_credentials",c={SSE:"sse",HTTP:"http",STDIO:"stdio",OPENAPI:"openapi"};e.s(["ADMIN_CONFIG_CREDENTIAL_KEYS",0,s,"AUTH_TYPE",0,t,"CLEARED_ON_INVALIDATION",0,["credentials"],"MCP_OAUTH2_FLOW_INTERACTIVE",0,"authorization_code","MCP_OAUTH2_FLOW_M2M",0,u,"OAUTH_FLOW",0,n,"TRANSPORT",0,c,"credentialAuthClass",0,e=>e===t.TRUE_PASSTHROUGH||e===t.OAUTH_DELEGATE?"client_forwarded":e??null,"gatewayMintsClientFor",0,e=>e.auth_type===t.TRUE_PASSTHROUGH||e.auth_type===t.OAUTH_DELEGATE&&!e.dcr_bridge,"getMcpOAuthMode",0,function(e){return e.auth_type===t.OAUTH2_TOKEN_EXCHANGE?"token_exchange":e.auth_type!==t.OAUTH2?null:e.oauth2_flow===u?"m2m":e.delegate_auth_to_upstream?"passthrough":"authorization_code"},"getOAuthAuthorizationIdentity",0,a,"handleAuth",0,e=>null==e?t.NONE:e,"handleTransport",0,(e,t)=>null==e?c.SSE:t&&e!==c.STDIO?c.OPENAPI:e,"isClientForwardedTokenMode",0,r,"isHeldOAuthTokenStale",0,(e,t)=>void 0!==t&&a(e)!==t,"isUnsupportedOnGatewayConnect",0,e=>r(e)||e===t.OAUTH2_TOKEN_EXCHANGE,"oauth2FlowToFormValue",0,function(e){return e===u?n.M2M:e?n.INTERACTIVE:void 0},"preservedAdminCredentials",0,e=>o(e,[...i,...s]),"preservedDeclaredAppCredentials",0,e=>o(e,i),"withoutMintedTokenCredentials",0,e=>{if(!e)return;let t=Object.fromEntries(Object.entries(e).filter(([e])=>!l.includes(e)));return Object.keys(t).length>0?t:void 0}],292335);var d=e.i(271645),f=e.i(602869),h=e.i(727749);function p(e){if(e instanceof Error)return e.message;if(e&&"object"==typeof e){let t=e.detail;return"string"==typeof t?t:Array.isArray(t)?t.map(e=>e&&"object"==typeof e?"string"==typeof e.msg?e.msg:JSON.stringify(e):String(e)).join("; "):t&&"object"==typeof t&&"string"==typeof t.error?t.error:"string"==typeof e.message?e.message:JSON.stringify(e)}return String(e)}e.s(["extractErrorMessage",0,p],122520);let m=e=>{let t=new Uint8Array(e),r="";return t.forEach(e=>r+=String.fromCharCode(e)),btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")},g=()=>{let e=new Uint8Array(32);return window.crypto.getRandomValues(e),m(e.buffer)},x=async e=>{let t=new TextEncoder().encode(e);return m(await window.crypto.subtle.digest("SHA-256",t))};e.s(["generateCodeChallenge",0,x,"generateCodeVerifier",0,g],165615);var v=e.i(434166);let b=()=>{{let e=window.location.pathname||"",t=e.indexOf("/ui"),r=t>=0?e.slice(0,t+3).replace(/\/+$/,""):"";return`${window.location.origin}${r}/mcp/oauth/callback`}},y=(...e)=>{e.forEach(e=>{try{window.sessionStorage.removeItem(e)}catch(e){}})};e.s(["TOOLS_OAUTH_UI_STATE_KEY",0,"litellm-mcp-oauth-tools-state","buildCallbackUrl",0,b,"clearStorage",0,y],779129);let _="litellm-user-mcp-oauth-flow-state",w="litellm-user-mcp-oauth-result",S=(e,t)=>{(0,v.setSecureItem)(e,t)},C=e=>(0,v.getSecureItem)(e);e.s(["useUserMcpOAuthFlow",0,({accessToken:e,serverId:t,serverAlias:r,scopes:n,clientId:a,onSuccess:i})=>{let[s,l]=(0,d.useState)("idle"),[o,u]=(0,d.useState)(null),c=(0,d.useRef)(!1),m=(0,d.useCallback)(async()=>{try{let i;l("authorizing"),u(null);let s=a??void 0;if(!s)try{let n=await (0,f.registerMcpOAuthClient)(e,t,{client_name:r||t,grant_types:["authorization_code","refresh_token"],response_types:["code"],token_endpoint_auth_method:"none"});s=n?.client_id,i=n?.client_secret}catch(e){}let o=g(),c=await x(o),d=crypto.randomUUID(),h=b(),p=n?.filter(e=>e.trim()).join(" "),m=(0,f.buildMcpOAuthAuthorizeUrl)({serverId:t,clientId:s,redirectUri:h,state:d,codeChallenge:c,scope:p}),v={state:d,codeVerifier:o,serverId:t,redirectUri:h,clientId:s,clientSecret:i,scopes:n};S(_,JSON.stringify(v));let y=new URL(window.location.href);y.searchParams.set("mcpOauthReturn","apps"),S("litellm-mcp-oauth-return-url",y.toString()),window.location.href=m}catch(t){let e=p(t);u(e),l("error"),h.default.error(e)}},[e,t,r,n,a]),v=(0,d.useCallback)(async()=>{if(c.current)return;let r=C(w);if(!r)return;let n=C(_);if(!n)return;try{let e=JSON.parse(n);if(e.serverId&&e.serverId!==t)return}catch(e){}c.current=!0,y(w);let a=null,s=null;try{a=JSON.parse(r);let e=C(_);s=e?JSON.parse(e):null}catch(e){u("Failed to resume OAuth flow. Please retry."),l("error"),c.current=!1,y(_);return}try{if(!s?.state||!s.codeVerifier||!s.serverId)throw Error("OAuth session state was lost. Please retry.");if(!a?.state||a.state!==s.state)throw Error("OAuth state mismatch. Please retry.");if(a.error)throw Error(a.error_description||a.error);if(!a.code)throw Error("Authorization code missing in callback.");l("exchanging");let t=await (0,f.exchangeMcpOAuthToken)({serverId:s.serverId,code:a.code,clientId:s.clientId,clientSecret:s.clientSecret,codeVerifier:s.codeVerifier,redirectUri:s.redirectUri,accessToken:e});await (0,f.storeMCPOAuthUserCredential)(e,s.serverId,{access_token:t.access_token,refresh_token:t.refresh_token,expires_in:t.expires_in,scopes:s.scopes}),l("success"),u(null),h.default.success("Connected successfully"),i()}catch(t){let e=p(t);u(e),l("error"),h.default.error(e)}finally{y(_),setTimeout(()=>{c.current=!1},1e3)}},[e,t,i]);return(0,d.useEffect)(()=>{v()},[v]),{startOAuthFlow:m,status:s,error:o}}],280024)},269638,e=>{"use strict";let t=(0,e.i(475254).default)("circle-check-big",[["path",{d:"M21.801 10A10 10 0 1 1 17 3.335",key:"yps3ct"}],["path",{d:"m9 11 3 3L22 4",key:"1pflzl"}]]);e.s(["CheckCircle",0,t],269638)},21040,e=>{"use strict";var t=e.i(843476),r=e.i(271645),n=e.i(266027),a=e.i(555436),i=e.i(871689),s=e.i(463059),l=e.i(195116),o=e.i(269638),u=e.i(531278),c=e.i(519455),d=e.i(793479),f=e.i(302747),h=e.i(677572),p=e.i(602869),m=e.i(292335),g=e.i(174553),x=e.i(888259),v=e.i(280024);let b=({server:e,accessToken:n,onConnect:a,variant:i="badge"})=>{let s=e.server_name??e.alias??e.server_id,{startOAuthFlow:l,status:o}=(0,v.useUserMcpOAuthFlow)({accessToken:n,serverId:e.server_id,serverAlias:s,onSuccess:(0,r.useCallback)(()=>a(e.server_id),[a,e.server_id])}),d="authorizing"===o||"exchanging"===o;return"button"===i?(0,t.jsxs)(c.Button,{onClick:l,disabled:d,className:"font-semibold h-[38px] min-w-[110px]",children:[d&&(0,t.jsx)(u.Loader2,{className:"h-4 w-4 animate-spin mr-1.5"}),d?"Connecting…":"Connect"]}):(0,t.jsx)("span",{onClick:e=>{e.stopPropagation(),d||l()},className:`text-[11px] font-semibold rounded-md px-2 py-0.5 shrink-0 whitespace-nowrap ${d?"text-muted-foreground bg-muted cursor-default":"text-primary-foreground bg-primary cursor-pointer hover:bg-primary/90"}`,children:d?"Connecting…":"Connect"})},y=["#1677ff","#52c41a","#fa8c16","#eb2f96","#722ed1","#13c2c2","#fa541c","#2f54eb","#a0d911","#faad14"];function _(e){let t=0;for(let r=0;r{let[S,C]=(0,r.useState)([]),[N,T]=(0,r.useState)(!0),[A,E]=(0,r.useState)(""),[j,O]=(0,r.useState)("all"),[R,k]=(0,r.useState)(new Set),[I,M]=(0,r.useState)(null),[L,P]=(0,r.useState)({}),[U,H]=(0,r.useState)(!1),[D,z]=(0,r.useState)(new Set),[W,B]=(0,r.useState)(new Set),V=(0,r.useRef)([]);(0,r.useEffect)(()=>{V.current=S},[S]);let F=(0,r.useRef)(v);(0,r.useEffect)(()=>{F.current=v},[v]);let K=(0,r.useRef)(y);(0,r.useEffect)(()=>{K.current=y},[y]);let $=e=>e.server_name??e.alias??e.server_id,Y=(0,r.useRef)(!1),G=(0,r.useCallback)(async t=>{try{let r=await (0,p.listMCPTools)(e,t.server_id);if(Y.current)return;let n=Array.isArray(r?.tools)?r.tools:[];P(e=>({...e,[$(t)]:n.length}))}catch{}},[e]),J=(0,r.useCallback)(async t=>{try{let r=await (0,p.getMCPOAuthUserCredentialStatus)(e,t.server_id);if(Y.current)return;r.has_credential&&!r.is_expired&&z(e=>new Set(e).add(t.server_id))}catch{}finally{Y.current||B(e=>{let r=new Set(e);return r.delete(t.server_id),r})}},[e]);(0,r.useEffect)(()=>(Y.current=!1,(0,p.fetchMCPServers)(e).then(async e=>{if(Y.current)return;let t=Array.isArray(e)?e:e?.data??[],r=t.filter(e=>e.auth_type===m.AUTH_TYPE.OAUTH2);for(let e of(C(t),B(new Set(r.map(e=>e.server_id))),T(!1),r.forEach(e=>J(e)),H(!0),Array.from({length:Math.ceil(t.length/5)},(e,r)=>t.slice(5*r,(r+1)*5)))){if(Y.current)return;await Promise.allSettled(e.map(e=>G(e)))}Y.current||H(!1)}).catch(()=>{Y.current||(C([]),T(!1))}),()=>{Y.current=!0}),[e,G,J]),(0,r.useEffect)(()=>{if(0===D.size)return;let e=V.current.filter(e=>D.has(e.server_id)&&!F.current.includes($(e))).map($);e.length>0&&K.current([...F.current,...e])},[D]);let X=async(t,r,n)=>{if(!r){y(v.filter(e=>e!==t)),n&&z(e=>{let t=new Set(e);return t.delete(n),t});return}k(e=>new Set(e).add(t));try{let r=n??t,a=await (0,p.listMCPTools)(e,r);if(a?.error)return void x.default.warning(`Could not load tools for ${t}`);F.current.includes(t)||y([...F.current,t])}catch{x.default.warning(`Could not load tools for ${t}`)}finally{k(e=>{let r=new Set(e);return r.delete(t),r})}},{data:q,isLoading:Z}=(0,n.useQuery)({queryKey:["mcp-apps-panel-detail-tools",I?.server_id],queryFn:()=>(0,p.listMCPTools)(e,I.server_id),enabled:!!I}),Q=Array.isArray(q?.tools)?q.tools:[],ee=S.filter(e=>{let t=$(e),r=!A.trim()||t.toLowerCase().includes(A.toLowerCase())||(e.description??"").toLowerCase().includes(A.toLowerCase()),n="all"===j||v.includes(t);return r&&n}),et=S.filter(e=>v.includes($(e))).length,er=Object.values(L).reduce((e,t)=>e+t,0);if(I){let r=$(I),n=v.includes(r),a=R.has(r),s=_(r);return(0,t.jsxs)("div",{className:"w-full",children:[(0,t.jsxs)(c.Button,{variant:"ghost",size:"sm",onClick:()=>M(null),className:"-ml-3 mb-5 gap-1.5 text-muted-foreground hover:text-foreground",children:[(0,t.jsx)(i.ArrowLeft,{className:"h-3 w-3"}),"Back"]}),(0,t.jsxs)("div",{className:"flex items-start gap-5 mb-7",children:[I.mcp_info?.logo_url?(0,t.jsx)(g.Logo,{src:I.mcp_info.logo_url,label:r,className:"w-16 h-16 rounded-2xl object-contain shrink-0 bg-muted/50"}):(0,t.jsx)("div",{className:"w-16 h-16 rounded-2xl flex items-center justify-center text-white font-bold text-[28px] shrink-0",style:{background:s},children:r.charAt(0).toUpperCase()}),(0,t.jsxs)("div",{className:"flex-1",children:[(0,t.jsx)("h2",{className:"m-0 mb-1 text-[22px] font-bold text-foreground",children:r}),(0,t.jsx)("p",{className:"m-0 text-sm text-muted-foreground",children:I.description??"MCP server"})]}),I.auth_type===m.AUTH_TYPE.OAUTH2?D.has(I.server_id)?(0,t.jsx)(c.Button,{variant:"destructive",onClick:async()=>{try{await (0,p.deleteMCPOAuthUserCredential)(e,I.server_id)}catch(e){}z(e=>{let t=new Set(e);return t.delete(I.server_id),t}),K.current(F.current.filter(e=>e!==r))},className:"font-semibold h-[38px] min-w-[110px]",children:"Disconnect"}):(0,t.jsx)(b,{server:I,accessToken:e,onConnect:e=>{z(t=>new Set(t).add(e))},variant:"button"}):(0,t.jsxs)(c.Button,{variant:n?"outline":"default",disabled:a,onClick:()=>X(r,!n,I.server_id),className:"font-semibold h-[38px] min-w-[110px]",children:[a&&(0,t.jsx)(u.Loader2,{className:"h-4 w-4 animate-spin mr-1.5"}),n?"Disconnect":"Connect"]})]}),(0,t.jsx)("h3",{className:"m-0 mb-3 text-[15px] font-semibold text-foreground",children:"Information"}),(0,t.jsx)("div",{className:"border rounded-lg overflow-hidden mb-7",children:[["Server ID",I.server_id],["Transport",(0,m.handleTransport)(I.transport,I.spec_path)],["Status",n?"Connected":"Not connected"]].filter(([,e])=>e).map(([e,r],n,a)=>(0,t.jsxs)("div",{className:`flex px-4 py-3 text-[13px] ${n(0,t.jsxs)("div",{className:"border rounded-lg px-3.5 py-2.5 bg-muted/30 flex flex-col gap-1.5",children:[(0,t.jsx)(f.Skeleton,{className:"h-3.5 w-1/3"}),(0,t.jsx)(f.Skeleton,{className:"h-3 w-2/3"})]},r))}):0===Q.length?(0,t.jsx)("div",{className:"text-muted-foreground text-[13px] py-2",children:"No tools available"}):(0,t.jsx)("div",{className:"flex flex-col gap-2",children:Q.map(e=>(0,t.jsxs)("div",{className:"border rounded-lg px-3.5 py-2.5 bg-muted/30",children:[(0,t.jsxs)("div",{className:`flex items-center gap-2 ${e.description?"mb-1":""}`,children:[(0,t.jsx)(l.Wrench,{className:"h-3.5 w-3.5 text-muted-foreground"}),(0,t.jsx)("span",{className:"text-[13px] font-semibold text-foreground font-mono",children:e.name})]}),e.description&&(0,t.jsx)("p",{className:"m-0 text-xs text-muted-foreground pl-[21px]",children:e.description})]},e.name))})]})}return(0,t.jsxs)("div",{className:"w-full",children:[(0,t.jsxs)("div",{className:"flex items-center justify-between mb-5 gap-4 flex-wrap",children:[(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"flex items-center gap-2 mb-1",children:[(0,t.jsx)("h2",{className:"m-0 text-lg font-semibold text-foreground",children:"MCP Servers"}),!w&&(0,t.jsx)("span",{className:"text-[10px] font-semibold text-primary bg-primary/10 rounded px-1.5 py-0.5 uppercase tracking-wider",children:"Beta"})]}),w?(0,t.jsx)("p",{className:"m-0 text-[13px] text-muted-foreground",children:"Click a server to see its tools and connect"}):(0,t.jsxs)("div",{className:"flex items-center gap-3",children:[(0,t.jsx)("p",{className:"m-0 text-[13px] text-muted-foreground",children:"Browse tools, authenticate once, use in chat"}),U?(0,t.jsxs)("span",{className:"flex items-center gap-1.5 text-xs text-muted-foreground",children:[(0,t.jsx)(u.Loader2,{className:"h-3 w-3 animate-spin"}),"Loading tools..."]}):er>0?(0,t.jsxs)("span",{className:"flex items-center gap-1 text-xs text-muted-foreground",children:[(0,t.jsx)(l.Wrench,{className:"h-3 w-3"}),er," tool",1!==er?"s":""," available"]}):null]})]}),(0,t.jsxs)("div",{className:"relative w-[220px]",children:[(0,t.jsx)(a.Search,{className:"absolute left-3 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground"}),(0,t.jsx)(d.Input,{placeholder:"Search servers...",value:A,onChange:e=>E(e.target.value),className:"pl-9 text-[13px] h-9"})]})]}),(0,t.jsx)(h.Tabs,{value:j,onValueChange:e=>O(e),className:"mb-4",children:(0,t.jsxs)(h.TabsList,{variant:"line",className:"border-b rounded-none w-full justify-start h-auto p-0",children:[(0,t.jsx)(h.TabsTrigger,{value:"all",className:"rounded-none px-4 py-2 text-[13px]",children:"All"}),(0,t.jsxs)(h.TabsTrigger,{value:"connected",className:"rounded-none px-4 py-2 text-[13px]",children:["Connected",et>0?` (${et})`:""]})]})}),N?(0,t.jsx)("div",{className:"grid grid-cols-2 border rounded-lg overflow-hidden",children:Array.from({length:6},(e,r)=>(0,t.jsxs)("div",{className:`flex items-center gap-3 p-4 ${r%2==0?"border-r":""} ${r<4?"border-b":""}`,children:[(0,t.jsx)(f.Skeleton,{className:"w-[38px] h-[38px] rounded-xl shrink-0"}),(0,t.jsxs)("div",{className:"flex-1 min-w-0 flex flex-col gap-1.5",children:[(0,t.jsx)(f.Skeleton,{className:"h-3.5 w-2/3"}),(0,t.jsx)(f.Skeleton,{className:"h-3 w-1/2"})]})]},r))}):0===ee.length?(0,t.jsx)("div",{className:"text-center text-muted-foreground text-[13px] py-12 px-3",children:0===S.length?"No MCP servers configured. Add servers in Tools -> MCP Servers.":"connected"===j?"No servers connected yet.":"No servers match your search."}):(0,t.jsx)("div",{className:"grid grid-cols-2 border rounded-lg overflow-hidden",children:ee.map((r,n)=>{var a;let i=$(r),u=_(i),c=L[i],d=!!w&&(0,m.isUnsupportedOnGatewayConnect)(r.auth_type);return(0,t.jsxs)("div",{onClick:()=>M(r),className:`flex items-center gap-3 p-4 bg-card cursor-pointer transition-colors hover:bg-accent/30 min-w-0 ${n%2==0?"border-r":""} ${Math.floor(n/2)0?(0,t.jsxs)("span",{className:"shrink-0 flex items-center gap-1 text-muted-foreground",children:["· ",(0,t.jsx)(l.Wrench,{className:"h-2.5 w-2.5"})," ",c]}):null:U?(0,t.jsx)(f.Skeleton,{className:"w-7 h-3 shrink-0"}):null]})]}),(a=r,w&&(0,m.isUnsupportedOnGatewayConnect)(a.auth_type)?(0,t.jsx)("span",{className:"text-[11px] text-muted-foreground shrink-0 whitespace-nowrap",children:"Not supported on this connection"}):a.auth_type===m.AUTH_TYPE.OAUTH2?D.has(a.server_id)?(0,t.jsx)(o.CheckCircle,{className:"h-3.5 w-3.5 text-emerald-600 shrink-0"}):W.has(a.server_id)?(0,t.jsx)(f.Skeleton,{className:"h-6 w-16 shrink-0 rounded-md"}):(0,t.jsx)(b,{server:a,accessToken:e,onConnect:e=>z(t=>new Set(t).add(e)),variant:"badge"}):v.includes($(a))?(0,t.jsx)("span",{className:"w-[7px] h-[7px] rounded-full bg-emerald-600 dark:bg-emerald-400 shrink-0"}):null),(0,t.jsx)(s.ChevronRight,{className:"h-3 w-3 text-muted-foreground/40 shrink-0"})]},r.server_id)})})]})}])},248536,e=>{"use strict";var t=e.i(843476),r=e.i(271645),n=e.i(618566),a=e.i(405033),i=e.i(21040),s=e.i(269638),l=e.i(602869);let o=({flowHandle:e,clientOrigin:r})=>{let n=`${(0,l.getProxyBaseUrl)()}/authorize/complete`,a=r??"the application";return(0,t.jsx)("div",{className:"mb-6 rounded-lg border border-primary/30 bg-primary/5 px-5 py-4",children:(0,t.jsxs)("div",{className:"flex items-start justify-between gap-4 flex-wrap",children:[(0,t.jsxs)("div",{className:"flex items-start gap-3 min-w-0",children:[(0,t.jsx)(s.CheckCircle,{className:"h-5 w-5 text-primary shrink-0 mt-0.5"}),(0,t.jsxs)("div",{className:"min-w-0",children:[(0,t.jsxs)("p",{className:"text-sm font-semibold text-foreground",children:["Connect your MCP servers to ",a]}),(0,t.jsxs)("p",{className:"text-[13px] text-muted-foreground mt-0.5",children:["Authorize the servers you want to use below, then click Finish connecting to return to ",a,"."]})]})]}),(0,t.jsxs)("form",{method:"POST",action:n,className:"shrink-0",children:[(0,t.jsx)("input",{type:"hidden",name:"flow",value:e}),(0,t.jsx)("button",{type:"submit",className:"h-[38px] rounded-md bg-primary px-4 text-sm font-semibold text-primary-foreground hover:bg-primary/90",children:"Finish connecting"})]})]})})};function u(){let{accessToken:e,selectedMCPServers:s,setSelectedMCPServers:l}=(0,a.useChatShell)(),u=(0,n.useRouter)(),c=(0,n.useSearchParams)(),d=c.get("mcpOauthReturn"),f=c.get("connect_flow"),h=c.get("connect_client");return(0,r.useEffect)(()=>{if(d){let e=new URL(window.location.href);e.searchParams.delete("mcpOauthReturn"),u.replace(e.pathname+e.search)}},[d,u]),(0,t.jsxs)("div",{className:"flex-1 min-h-0 overflow-auto w-full py-8 px-8",children:[f&&(0,t.jsx)(o,{flowHandle:f,clientOrigin:h}),(0,t.jsx)(i.default,{accessToken:e,selectedServers:s,onChange:l,connectMode:!!f})]})}e.s(["default",0,function(){return(0,t.jsx)(r.Suspense,{children:(0,t.jsx)(u,{})})}],248536)}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/05id71gg6oywc.js b/litellm/proxy/_experimental/out/_next/static/chunks/05id71gg6oywc.js deleted file mode 100644 index 9c60665515a..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/05id71gg6oywc.js +++ /dev/null @@ -1 +0,0 @@ -(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,309426,e=>{"use strict";var t=e.i(290571),l=e.i(444755),a=e.i(673706),s=e.i(271645),i=e.i(46757);let r=(0,a.makeClassName)("Col"),n=s.default.forwardRef((e,a)=>{let n,o,d,c,{numColSpan:u=1,numColSpanSm:m,numColSpanMd:g,numColSpanLg:p,children:h,className:x}=e,y=(0,t.__rest)(e,["numColSpan","numColSpanSm","numColSpanMd","numColSpanLg","children","className"]),f=(e,t)=>e&&Object.keys(t).includes(String(e))?t[e]:"";return s.default.createElement("div",Object.assign({ref:a,className:(0,l.tremorTwMerge)(r("root"),(n=f(u,i.colSpan),o=f(m,i.colSpanSm),d=f(g,i.colSpanMd),c=f(p,i.colSpanLg),(0,l.tremorTwMerge)(n,o,d,c)),x)},y),h)});n.displayName="Col",e.s(["Col",0,n],309426)},510674,e=>{"use strict";var t=e.i(266027),l=e.i(243652),a=e.i(602869),s=e.i(431703),i=e.i(135214),r=e.i(708347);let n=(0,l.createQueryKeys)("projects"),o=[...r.all_admin_roles,...r.internalUserRoles],d=async e=>{let t=(0,a.getProxyBaseUrl)(),l=`${t}/project/list`,i=await fetch(l,{method:"GET",headers:{[(0,a.getGlobalLitellmHeaderName)()]:`Bearer ${e}`,"Content-Type":"application/json"}});if(!i.ok){let e=await i.json(),t=(0,s.deriveErrorMessage)(e);throw(0,a.handleError)(t),Error(t)}return i.json()};e.s(["projectKeys",0,n,"useProjects",0,()=>{let{accessToken:e,userRole:l}=(0,i.default)();return(0,t.useQuery)({queryKey:n.list({}),queryFn:async()=>d(e),enabled:!!e&&o.includes(l)})}])},557662,e=>{"use strict";let t={src:e.i(196361).default,width:823,height:807,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAABCUlEQVR42k2Ov0sCYRjH3/f09c68NOuiLg+NvMM6qIaIhgKDlgbBBiH7ARbkFAr+wEFwUBzUSRwUN3+Am4Kbg4sIIg4iOugiguDm5F/gvaLiF57l83yf5/sFYC0VQdJJnS0bObUkSCgnwa4ggPD94O57KkYX46vw/HVftECMNzKRJ2JL8A+KekelfO6s1Y3utl6hNayWahmlznIfhQbv6V4oGP6aOrvtCIFRnH1LKQmkBC7G7BtehmZWzY0NRxEAEo7Dhz/MvrT3P6BvCk5irDVJQURhAx5aKpzh7Pkm7+2BZ1p4YeT0MYIy5Dx6/P+UrvAXFml0TyqjeVtUytsrGX6rac6ew+YNXwKfsTPy4XOyEQAAAABJRU5ErkJggg=="},l={src:e.i(614148).default,width:600,height:450,blurWidth:0,blurHeight:0},a={src:e.i(858236).default,width:200,height:200,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAqklEQVR42mXOzQqCQBQF4NG8PVKLalNRrxW01EgUghZm4Tu0aV3gopBCaDDoh8LMyubaCzQzLj2c1fk2h/zwyZvjI8NTmtMXHj94yfBG5JqE0WrsDWyv70eLGOkbrwJotG316lDVeJvd2pouE3YW4M4nAEBkADTD1eOvBNPUFUUpQAMwplbK7gJsa6hWVG58bXTam0OAmAjYBT63kWk4Myeke2TiJynulvsHOZp6y2XrD90AAAAASUVORK5CYII="},s={src:e.i(508296).default,width:180,height:180,blurWidth:8,blurHeight:8,blurDataURL:"data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/wAARCAAIAAgDAREAAhEBAxEB/9sAQwAKBwcIBwYKCAgICwoKCw4YEA4NDQ4dFRYRGCMfJSQiHyIhJis3LyYpNCkhIjBBMTQ5Oz4+PiUuRElDPEg3PT47/9sAQwEKCwsODQ4cEBAcOygiKDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDvXivVvITItyZVvSwKFmDRbjjPYDB/SutONn6fiaaH/9k="},i={src:e.i(324755).default,width:48,height:48,blurWidth:0,blurHeight:0},r={src:e.i(475151).default,width:14,height:16,blurWidth:0,blurHeight:0},n={src:e.i(274286).default,width:256,height:256,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA/ElEQVR42m3Pz0vCUADA8ed7a3uBjb0sx5C1Ri1oIDJSKEdJgyEFtbwFgScpsGNR1CmyS0Ho0ZOggv+A4F3B30f9HwRRwYMICupNPPg5fa9fANazLQtCtC0jdGbSdFSmN6+Ry/tIHd/+w52jOwyRCDRZL+UJ6eREdXauR0dq8HtiBl7mD25r+MtyLfBO+Fpm19UXTsJ17LnPWhyf+rNvVWOiu38j+Zrgk/CNuHDYtfsiRUa1khYnpH9Y0viQtIGxf1oGfsVoFxzOYULR51f+p6lifo09wdjs0hvuvbKkAhDF7GkUFYpgnLhg8DPtOAghOfAGiWRs2KBz/dqKBVIdOzeF2+/ZAAAAAElFTkSuQmCC"},o={src:e.i(436494).default,width:200,height:200,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAnElEQVR42oXOsQqCUACF4ftWvUFbBFFzQSVRLQ5RmRDkUJFTIUFNEQgNDWFTNURjgyAiOgiiCF7FQRCcVFQQJ1H4t284B1gwzA0UAS/A2+N7pp8nmrne3yynJ6Cp3mBBoARZbfcqjVZ3tkSwlSw7QFHc5gitI+M1delM8LhafyhKNjCN4PPjsR2Fk8f59jDd7JnXHxp+Oh5zVsmrCGlHlzZm+jq8AAAAAElFTkSuQmCC"},d={src:e.i(204086).default,width:48,height:48,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAhElEQVR42oWPuw0DIRBEr6g7aIGQAAQSEiTk1EAHVILogIQAQUPOxoKT5cSfYDbYedqdOc7zfFzXhU9a3vHNfGkDhBB472GMQQgB1lo45/Z+A4wx9N6Rc8acE7VWpJRuYI0YI1prW6UUjDHAOb9fLEAIAaXUPi2lhNYalNJ3hp8h/9V8AqCAe6iqrOaAAAAAAElFTkSuQmCC"},c={src:e.i(531150).default,width:48,height:48,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA40lEQVR42lWOvesBcQDGz0/9VmUl/4lSyqaUic5gM0jKSBlISgaXwWCwGc7gFPKeIikUMXAldec9Wdx5uft+v3KFMzzL8/k89WAIIUweyLG6W9tRFNmKGUHw9wMfAvgfDBfWddWbv5aNE5Gtmr9LiBSNzgq3uQv7UKzY2tZ9uXvfl/4IxxOvyeZn/niyQkWJJkXTjB7ez2oJHk6cNpzo5ohUjeTHRICnSScEQPliGIKistef23FPYUNSo6CwJHGw6xgQggpJEBYZ12UQiY2njOnCPVSv528oCWDbNopMySIv5XkCtum3l9/HqzEAAAAASUVORK5CYII="},u=[{id:"arize",displayName:"Arize",logo:t.src,supports_key_team_logging:!0,dynamic_params:{arize_api_key:"password",arize_space_id:"password"},description:"Arize Logging Integration"},{id:"braintrust",displayName:"Braintrust",logo:a.src,supports_key_team_logging:!1,dynamic_params:{braintrust_api_key:"password",braintrust_project_name:"text"},description:"Braintrust Logging Integration"},{id:"custom_callback_api",displayName:"Custom Callback API",supports_key_team_logging:!0,dynamic_params:{custom_callback_api_url:"text",custom_callback_api_headers:"text"},description:"Custom Callback API Logging Integration"},{id:"galileo",displayName:"Galileo",logo:i.src,supports_key_team_logging:!1,dynamic_params:{GALILEO_API_KEY:"password",GALILEO_PROJECT_ID:"text",GALILEO_LOG_STREAM_ID:"text",GALILEO_BASE_URL:"text",GALILEO_USERNAME:"text",GALILEO_PASSWORD:"password"},description:"Galileo AI Observability Integration"},{id:"datadog",displayName:"Datadog",logo:s.src,supports_key_team_logging:!1,dynamic_params:{dd_api_key:"password",dd_site:"text"},description:"Datadog Logging Integration"},{id:"lago",displayName:"Lago",logo:r.src,supports_key_team_logging:!1,dynamic_params:{lago_api_url:"text",lago_api_key:"password"},description:"Lago Billing Logging Integration"},{id:"langfuse",displayName:"Langfuse",logo:n.src,supports_key_team_logging:!0,dynamic_params:{langfuse_public_key:"text",langfuse_secret_key:"password",langfuse_host:"text"},description:"Langfuse v2 Logging Integration"},{id:"langfuse_otel",displayName:"Langfuse OTEL",logo:n.src,supports_key_team_logging:!0,dynamic_params:{langfuse_public_key:"text",langfuse_secret_key:"password",langfuse_host:"text"},description:"Langfuse v3 OTEL Logging Integration"},{id:"langsmith",displayName:"LangSmith",logo:o.src,supports_key_team_logging:!0,dynamic_params:{langsmith_api_key:"password",langsmith_project:"text",langsmith_base_url:"text",langsmith_sampling_rate:"number"},description:"Langsmith Logging Integration"},{id:"openmeter",displayName:"OpenMeter",logo:d.src,supports_key_team_logging:!1,dynamic_params:{openmeter_api_key:"password",openmeter_base_url:"text"},description:"OpenMeter Logging Integration"},{id:"otel",displayName:"Open Telemetry",logo:c.src,supports_key_team_logging:!1,dynamic_params:{otel_endpoint:"text",otel_headers:"text"},description:"OpenTelemetry Logging Integration"},{id:"s3",displayName:"S3",logo:l.src,supports_key_team_logging:!1,dynamic_params:{s3_bucket_name:"text",aws_access_key_id:"password",aws_secret_access_key:"password",aws_region:"text"},description:"S3 Bucket (AWS) Logging Integration"},{id:"SQS",displayName:"SQS",logo:l.src,supports_key_team_logging:!1,dynamic_params:{sqs_queue_url:"text",aws_access_key_id:"password",aws_secret_access_key:"password",aws_region:"text"},description:"SQS Queue (AWS) Logging Integration"}],m=u.reduce((e,t)=>(e[t.displayName]=t,e),{}),g=u.reduce((e,t)=>(e[t.displayName]=t.id,e),{}),p=u.reduce((e,t)=>(e[t.id]=t.displayName,e),{});e.s(["callbackInfo",0,m,"callback_map",0,g,"mapDisplayToInternalNames",0,e=>e.map(e=>g[e]||e),"mapInternalToDisplayNames",0,e=>e.map(e=>p[e]||e),"reverse_callback_map",0,p],557662)},810757,477386,e=>{"use strict";var t=e.i(271645);let l=t.forwardRef(function(e,l){return t.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:l},e),t.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"}),t.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M15 12a3 3 0 11-6 0 3 3 0 016 0z"}))});e.s(["CogIcon",0,l],810757);let a=t.forwardRef(function(e,l){return t.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:l},e),t.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"}))});e.s(["BanIcon",0,a],477386)},207082,e=>{"use strict";var t=e.i(619273),l=e.i(266027),a=e.i(243652),s=e.i(602869),i=e.i(431703),r=e.i(135214);let n=(0,a.createQueryKeys)("keys"),o=async(e,t,l,a={})=>{try{let r=(0,s.getProxyBaseUrl)(),n=new URLSearchParams(Object.entries({team_id:a.teamID,project_id:a.projectID,agent_id:a.agentID,organization_id:a.organizationID,key_alias:a.selectedKeyAlias,key_hash:a.keyHash,user_id:a.userID,page:t,size:l,sort_by:a.sortBy,sort_order:a.sortOrder,expand:a.expand,status:a.status,return_full_object:"true",include_team_keys:"true",include_created_by_keys:"true",substring_matching:"true"}).filter(([,e])=>null!=e).map(([e,t])=>[e,String(t)])),o=`${r?`${r}/key/list`:"/key/list"}?${n}`,d=await fetch(o,{method:"GET",headers:{[(0,s.getGlobalLitellmHeaderName)()]:`Bearer ${e}`,"Content-Type":"application/json"}});if(!d.ok){let e=await d.json(),t=(0,i.deriveErrorMessage)(e);throw(0,s.handleError)(t),Error(t)}return await d.json()}catch(e){throw console.error("Failed to list keys:",e),e}},d=(0,a.createQueryKeys)("deletedKeys");e.s(["keyKeys",0,n,"useDeletedKeys",0,(e,a,s={})=>{let{accessToken:i}=(0,r.default)();return(0,l.useQuery)({queryKey:d.list({page:e,limit:a,...s}),queryFn:async()=>await o(i,e,a,{...s,status:"deleted"}),enabled:!!i,staleTime:3e4,placeholderData:t.keepPreviousData})},"useKeys",0,(e,a,s={},i=!0)=>{let{accessToken:d}=(0,r.default)();return(0,l.useQuery)({queryKey:n.list({page:e,limit:a,...s}),queryFn:async()=>await o(d,e,a,s),enabled:!!d&&i,staleTime:3e4,placeholderData:t.keepPreviousData})}])},552130,e=>{"use strict";var t=e.i(843476),l=e.i(271645),a=e.i(199133),s=e.i(602869);e.s(["default",0,({onChange:e,value:i,className:r,accessToken:n,placeholder:o="Select agents",disabled:d=!1})=>{let[c,u]=(0,l.useState)([]),[m,g]=(0,l.useState)([]),[p,h]=(0,l.useState)(!1);(0,l.useEffect)(()=>{(async()=>{if(n){h(!0);try{let e=await (0,s.getAgentsList)(n),t=e?.agents||[];u(t);let l=new Set;t.forEach(e=>{let t=e.agent_access_groups;t&&Array.isArray(t)&&t.forEach(e=>l.add(e))}),g(Array.from(l))}catch(e){console.error("Error fetching agents:",e)}finally{h(!1)}}})()},[n]);let x=[...m.map(e=>({label:e,value:`group:${e}`,isAccessGroup:!0,searchText:`${e} Access Group`})),...c.map(e=>({label:`${e.agent_name||e.agent_id}`,value:e.agent_id,isAccessGroup:!1,searchText:`${e.agent_name||e.agent_id} ${e.agent_id} Agent`}))],y=[...i?.agents||[],...(i?.accessGroups||[]).map(e=>`group:${e}`)];return(0,t.jsx)("div",{children:(0,t.jsx)(a.Select,{mode:"multiple",placeholder:o,onChange:t=>{e({agents:t.filter(e=>!e.startsWith("group:")),accessGroups:t.filter(e=>e.startsWith("group:")).map(e=>e.replace("group:",""))})},value:y,loading:p,className:r,allowClear:!0,showSearch:!0,style:{width:"100%"},disabled:d,filterOption:(e,t)=>(x.find(e=>e.value===t?.value)?.searchText||"").toLowerCase().includes(e.toLowerCase()),children:x.map(e=>(0,t.jsx)(a.Select.Option,{value:e.value,label:e.label,children:(0,t.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:"8px"},children:[(0,t.jsx)("span",{style:{display:"inline-block",width:8,height:8,borderRadius:"50%",background:e.isAccessGroup?"#52c41a":"#722ed1",flexShrink:0}}),(0,t.jsx)("span",{style:{flex:1},children:e.label}),(0,t.jsx)("span",{style:{color:e.isAccessGroup?"#52c41a":"#722ed1",fontSize:"12px",fontWeight:500,opacity:.8},children:e.isAccessGroup?"Access Group":"Agent"})]})},e.value))})})}])},9314,645526,e=>{"use strict";var t=e.i(843476),l=e.i(199133),a=e.i(981339);e.i(247167);var s=e.i(931067),i=e.i(271645);let r={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"};var n=e.i(9583),o=i.forwardRef(function(e,t){return i.createElement(n.default,(0,s.default)({},e,{ref:t,icon:r}))});e.s(["TeamOutlined",0,o],645526);var d=e.i(599724),c=e.i(263147);e.s(["default",0,({value:e,onChange:s,placeholder:i="Select access groups",disabled:r=!1,style:n,className:u,showLabel:m=!1,labelText:g="Access Group",allowClear:p=!0})=>{let{data:h,isLoading:x,isError:y}=(0,c.useAccessGroups)();if(x)return(0,t.jsxs)("div",{children:[m&&(0,t.jsxs)(d.Text,{className:"font-medium block mb-2 text-gray-700 flex items-center",children:[(0,t.jsx)(o,{className:"mr-2"})," ",g]}),(0,t.jsx)(a.Skeleton.Input,{active:!0,block:!0,style:{height:32,...n}})]});let f=(h??[]).map(e=>({label:(0,t.jsxs)("span",{children:[(0,t.jsx)("span",{className:"font-medium",children:e.access_group_name})," ",(0,t.jsxs)("span",{className:"text-gray-400 text-xs",children:["(",e.access_group_id,")"]})]}),value:e.access_group_id,selectedLabel:e.access_group_name,searchText:`${e.access_group_name} ${e.access_group_id}`}));return(0,t.jsxs)("div",{children:[m&&(0,t.jsxs)(d.Text,{className:"font-medium block mb-2 text-gray-700 flex items-center",children:[(0,t.jsx)(o,{className:"mr-2"})," ",g]}),(0,t.jsx)(l.Select,{mode:"multiple",value:e,placeholder:i,onChange:s,disabled:r,allowClear:p,showSearch:!0,style:{width:"100%",...n},className:`rounded-md ${u??""}`,notFoundContent:y?(0,t.jsx)("span",{className:"text-red-500",children:"Failed to load access groups"}):"No access groups found",filterOption:(e,t)=>(f.find(e=>e.value===t?.value)?.searchText??"").toLowerCase().includes(e.toLowerCase()),optionLabelProp:"selectedLabel",options:f.map(e=>({label:e.label,value:e.value,selectedLabel:e.selectedLabel}))})]})}],9314)},392110,e=>{"use strict";var t=e.i(843476),l=e.i(271645),a=e.i(199133),s=e.i(592968),i=e.i(312361),r=e.i(790848),n=e.i(536916),o=e.i(808613),d=e.i(827252),c=e.i(779241);let{Option:u}=a.Select;e.s(["default",0,({form:e,autoRotationEnabled:m,onAutoRotationChange:g,rotationInterval:p,onRotationIntervalChange:h,isCreateMode:x=!1,neverExpire:y=!1,onNeverExpireChange:f})=>{let b=p&&!["7d","30d","90d","180d","365d"].includes(p),[j,_]=(0,l.useState)(b),[v,A]=(0,l.useState)(b?p:"");return(0,t.jsxs)("div",{className:"space-y-6",children:[(0,t.jsxs)("div",{className:"space-y-4",children:[(0,t.jsx)("span",{className:"text-sm font-medium text-gray-700",children:"Key Expiry Settings"}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsxs)("label",{className:"text-sm font-medium text-gray-700 flex items-center space-x-1",children:[(0,t.jsx)("span",{children:"Expire Key"}),(0,t.jsx)(s.Tooltip,{title:"Set when this key should expire. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days). Leave empty to keep the current expiry unchanged.",children:(0,t.jsx)(d.InfoCircleOutlined,{className:"text-gray-400 cursor-help text-xs"})}),!x&&f&&(0,t.jsx)(n.Checkbox,{checked:y,onChange:t=>{let l=t.target.checked;f(l),l&&(e&&"function"==typeof e.setFieldValue?e.setFieldValue("duration",""):e&&"function"==typeof e.setFieldsValue&&e.setFieldsValue({duration:""}))},className:"ml-2 text-sm font-normal text-gray-600",children:"Never Expire"})]}),(0,t.jsx)(o.Form.Item,{name:"duration",noStyle:!0,initialValue:"",children:(0,t.jsx)(c.TextInput,{placeholder:x?"e.g., 30d or leave empty to never expire":"e.g., 30d",className:"w-full",disabled:!x&&y})})]})]}),(0,t.jsx)(i.Divider,{}),(0,t.jsxs)("div",{className:"space-y-4",children:[(0,t.jsx)("span",{className:"text-sm font-medium text-gray-700",children:"Auto-Rotation Settings"}),(0,t.jsxs)("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-4",children:[(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsxs)("label",{className:"text-sm font-medium text-gray-700 flex items-center space-x-1",children:[(0,t.jsx)("span",{children:"Enable Auto-Rotation"}),(0,t.jsx)(s.Tooltip,{title:"Key will automatically regenerate at the specified interval for enhanced security.",children:(0,t.jsx)(d.InfoCircleOutlined,{className:"text-gray-400 cursor-help text-xs"})})]}),(0,t.jsx)(r.Switch,{checked:m,onChange:g,size:"default",className:m?"":"bg-gray-400"})]}),m&&(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsxs)("label",{className:"text-sm font-medium text-gray-700 flex items-center space-x-1",children:[(0,t.jsx)("span",{children:"Rotation Interval"}),(0,t.jsx)(s.Tooltip,{title:"How often the key should be automatically rotated. Choose the interval that best fits your security requirements.",children:(0,t.jsx)(d.InfoCircleOutlined,{className:"text-gray-400 cursor-help text-xs"})})]}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsxs)(a.Select,{value:j?"custom":p,onChange:e=>{"custom"===e?_(!0):(_(!1),A(""),h(e))},className:"w-full",placeholder:"Select interval",children:[(0,t.jsx)(u,{value:"7d",children:"7 days"}),(0,t.jsx)(u,{value:"30d",children:"30 days"}),(0,t.jsx)(u,{value:"90d",children:"90 days"}),(0,t.jsx)(u,{value:"180d",children:"180 days"}),(0,t.jsx)(u,{value:"365d",children:"365 days"}),(0,t.jsx)(u,{value:"custom",children:"Custom interval"})]}),j&&(0,t.jsxs)("div",{className:"space-y-1",children:[(0,t.jsx)(c.TextInput,{value:v,onChange:e=>{let t=e.target.value;A(t),h(t)},placeholder:"e.g., 1s, 5m, 2h, 14d"}),(0,t.jsx)("div",{className:"text-xs text-gray-500",children:"Supported formats: seconds (s), minutes (m), hours (h), days (d)"})]})]})]})]}),m&&(0,t.jsx)("div",{className:"bg-blue-50 p-3 rounded-md text-sm text-blue-700",children:"When rotation occurs, you'll receive a notification with the new key. The old key will be deactivated after a brief grace period."})]})]})}])},844565,e=>{"use strict";var t=e.i(843476),l=e.i(271645),a=e.i(199133),s=e.i(602869);e.s(["default",0,({onChange:e,value:i,className:r,accessToken:n,placeholder:o="Select pass through routes",disabled:d=!1,teamId:c})=>{let[u,m]=(0,l.useState)([]),[g,p]=(0,l.useState)(!1);return(0,l.useEffect)(()=>{(async()=>{if(n){p(!0);try{let e=await (0,s.getPassThroughEndpointsCall)(n,c);if(e.endpoints){let t=e.endpoints.flatMap(e=>{let t=e.path,l=e.methods;return l&&l.length>0?l.map(e=>({label:`${e} ${t}`,value:t})):[{label:t,value:t}]});m(t)}}catch(e){console.error("Error fetching pass through routes:",e)}finally{p(!1)}}})()},[n,c]),(0,t.jsx)(a.Select,{mode:"tags",placeholder:o,onChange:e,value:i,loading:g,className:r,allowClear:!0,options:u,optionFilterProp:"label",showSearch:!0,style:{width:"100%"},disabled:d})}])},939510,e=>{"use strict";var t=e.i(843476),l=e.i(808613),a=e.i(199133),s=e.i(592968),i=e.i(827252);let{Option:r}=a.Select;e.s(["default",0,({type:e,name:n,showDetailedDescriptions:o=!0,className:d="",initialValue:c=null,form:u,onChange:m})=>{let g=e.toUpperCase(),p=e.toLowerCase(),h=`Select 'guaranteed_throughput' to prevent overallocating ${g} limit when the key belongs to a Team with specific ${g} limits.`;return(0,t.jsx)(l.Form.Item,{label:(0,t.jsxs)("span",{children:[g," Rate Limit Type"," ",(0,t.jsx)(s.Tooltip,{title:h,children:(0,t.jsx)(i.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:n,initialValue:c,className:d,children:(0,t.jsx)(a.Select,{defaultValue:o?"default":void 0,placeholder:"Select rate limit type",style:{width:"100%"},optionLabelProp:o?"label":void 0,onChange:e=>{u&&u.setFieldValue(n,e),m&&m(e)},children:o?(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(r,{value:"best_effort_throughput",label:"Default",children:(0,t.jsxs)("div",{style:{padding:"4px 0"},children:[(0,t.jsx)("div",{style:{fontWeight:500},children:"Default"}),(0,t.jsxs)("div",{style:{fontSize:"11px",color:"#6b7280",marginTop:"2px"},children:["Best effort throughput - no error if we're overallocating ",p," (Team/Key Limits checked at runtime)."]})]})}),(0,t.jsx)(r,{value:"guaranteed_throughput",label:"Guaranteed throughput",children:(0,t.jsxs)("div",{style:{padding:"4px 0"},children:[(0,t.jsx)("div",{style:{fontWeight:500},children:"Guaranteed throughput"}),(0,t.jsxs)("div",{style:{fontSize:"11px",color:"#6b7280",marginTop:"2px"},children:["Guaranteed throughput - raise an error if we're overallocating ",p," (also checks model-specific limits)"]})]})}),(0,t.jsx)(r,{value:"dynamic",label:"Dynamic",children:(0,t.jsxs)("div",{style:{padding:"4px 0"},children:[(0,t.jsx)("div",{style:{fontWeight:500},children:"Dynamic"}),(0,t.jsxs)("div",{style:{fontSize:"11px",color:"#6b7280",marginTop:"2px"},children:["If the key has a set ",g," (e.g. 2 ",g,") and there are no 429 errors, it can dynamically exceed the limit when the model being called is not erroring."]})]})})]}):(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(r,{value:"best_effort_throughput",children:"Best effort throughput"}),(0,t.jsx)(r,{value:"guaranteed_throughput",children:"Guaranteed throughput"}),(0,t.jsx)(r,{value:"dynamic",children:"Dynamic"})]})})})}])},363256,e=>{"use strict";var t=e.i(843476),l=e.i(199133);let{Text:a}=e.i(898586).Typography;e.s(["default",0,({organizations:e,value:s,onChange:i,disabled:r,loading:n,style:o})=>(0,t.jsx)(l.Select,{showSearch:!0,placeholder:"All Organizations",value:s,onChange:i,disabled:r,loading:n,allowClear:!0,style:{minWidth:280,...o},filterOption:(t,l)=>{if(!l)return!1;let a=e?.find(e=>e.organization_id===l.key);if(!a)return!1;let s=t.toLowerCase().trim(),i=(a.organization_alias||"").toLowerCase(),r=(a.organization_id||"").toLowerCase();return i.includes(s)||r.includes(s)},children:e?.map(e=>(0,t.jsxs)(l.Select.Option,{value:e.organization_id,children:[(0,t.jsx)("span",{className:"font-medium",children:e.organization_alias})," ",(0,t.jsxs)(a,{type:"secondary",children:["(",e.organization_id,")"]})]},e.organization_id))})])},128233,319312,833400,e=>{"use strict";var t=e.i(843476),l=e.i(464571),a=e.i(199133),s=e.i(592968),i=e.i(425063),r=e.i(107233),n=e.i(37727),o=e.i(271645);e.s(["BudgetFallbacksEditor",0,function({value:e,onChange:d,availableModels:c}){let[u,m]=(0,o.useState)(()=>{let t;return 0===(t=Object.keys(e)).length?[]:t.map((t,l)=>({id:String(l+1),primaryModel:t,fallbackModels:e[t]}))}),g=e=>{m(e),d(Object.fromEntries(e.filter(e=>null!==e.primaryModel&&e.fallbackModels.length>0).map(e=>[e.primaryModel,e.fallbackModels])))},p=()=>{g([...u,{id:Date.now().toString(),primaryModel:null,fallbackModels:[]}])},h=(e,t)=>{g(u.map(l=>l.id===e?{...l,...t}:l))},x=new Set(u.map(e=>e.primaryModel).filter(Boolean));return 0===u.length?(0,t.jsxs)("div",{children:[(0,t.jsx)("div",{className:"text-xs text-gray-500 mb-2",children:"When a model exceeds its per-model budget, requests automatically reroute to fallback models"}),(0,t.jsx)(l.Button,{size:"small",onClick:p,icon:(0,t.jsx)(r.Plus,{className:"w-3 h-3"}),children:"Add Budget Fallback"})]}):(0,t.jsxs)("div",{className:"space-y-4",children:[(0,t.jsx)("div",{className:"text-xs text-gray-500",children:"When a model exceeds its per-model budget, requests automatically reroute to fallback models"}),u.map(e=>{let l=c.filter(t=>t===e.primaryModel||!x.has(t)),r=c.filter(t=>t!==e.primaryModel);return(0,t.jsxs)("div",{className:"relative rounded-lg border border-gray-200 bg-gray-50 p-4",children:[(0,t.jsx)("button",{type:"button",onClick:()=>{var t;return t=e.id,void g(u.filter(e=>e.id!==t))},className:"absolute top-2 right-2 text-gray-400 hover:text-red-500 transition-colors p-1",children:(0,t.jsx)(n.X,{className:"w-4 h-4"})}),(0,t.jsxs)("div",{className:"mb-3",children:[(0,t.jsx)("label",{className:"block text-xs font-medium text-gray-600 mb-1",children:"Primary Model"}),(0,t.jsx)(a.Select,{className:"w-full",placeholder:"Select model",value:e.primaryModel,onChange:t=>{let l=e.fallbackModels.filter(e=>e!==t);h(e.id,{primaryModel:t,fallbackModels:l})},showSearch:!0,filterOption:(e,t)=>(t?.label??"").toLowerCase().includes(e.toLowerCase()),options:l.map(e=>({label:e,value:e})),getPopupContainer:e=>e.parentElement||document.body})]}),(0,t.jsx)("div",{className:"flex items-center justify-center -my-1 mb-2",children:(0,t.jsxs)("div",{className:"bg-amber-50 text-amber-600 px-3 py-0.5 rounded-full text-[10px] font-bold border border-amber-100 flex items-center gap-1",children:[(0,t.jsx)(i.ArrowDown,{className:"w-3 h-3"}),"IF BUDGET EXCEEDED, TRY"]})}),(0,t.jsxs)("div",{children:[(0,t.jsx)("label",{className:"block text-xs font-medium text-gray-600 mb-1",children:"Fallback Models"}),(0,t.jsx)(a.Select,{mode:"multiple",className:"w-full",placeholder:e.primaryModel?"Select fallback models":"Select a primary model first",value:e.fallbackModels,onChange:t=>h(e.id,{fallbackModels:t}),disabled:!e.primaryModel,showSearch:!0,filterOption:(e,t)=>(t?.label??"").toLowerCase().includes(e.toLowerCase()),options:r.map(e=>({label:e,value:e})),getPopupContainer:e=>e.parentElement||document.body,maxTagCount:"responsive",maxTagPlaceholder:e=>(0,t.jsx)(s.Tooltip,{styles:{root:{pointerEvents:"none"}},title:e.map(({value:e})=>e).join(", "),children:(0,t.jsxs)("span",{children:["+",e.length," more"]})})}),e.fallbackModels.length>1&&(0,t.jsx)("div",{className:"text-[10px] text-gray-400 mt-1 ml-1",children:"Tried in order; first model still within its own budget is used"})]})]},e.id)}),(0,t.jsx)(l.Button,{size:"small",onClick:p,icon:(0,t.jsx)(r.Plus,{className:"w-3 h-3"}),children:"Add Budget Fallback"})]})}],128233);var d=e.i(28651);let c=[{value:"1h",label:"Hourly",resetHint:"Resets every hour"},{value:"24h",label:"Daily",resetHint:"Resets daily at midnight UTC"},{value:"7d",label:"Weekly",resetHint:"Resets every Sunday at midnight UTC"},{value:"30d",label:"Monthly",resetHint:"Resets on the 1st of every month at midnight UTC"}];e.s(["BudgetWindowsEditor",0,function({value:e,onChange:s}){let i=(t,l,a)=>{s(e.map((e,s)=>s===t?{...e,[l]:a}:e))};return(0,t.jsxs)("div",{children:[e.map((r,n)=>{let o=c.find(e=>e.value===r.budget_duration)?.resetHint;return(0,t.jsxs)("div",{style:{marginBottom:12},children:[(0,t.jsxs)("div",{style:{display:"flex",gap:8,alignItems:"center"},children:[(0,t.jsx)(a.Select,{value:r.budget_duration,onChange:e=>i(n,"budget_duration",e),style:{width:130},options:c.map(e=>({value:e.value,label:e.label}))}),(0,t.jsx)(d.InputNumber,{step:.01,min:0,precision:2,value:r.max_budget??void 0,onChange:e=>i(n,"max_budget",e??null),placeholder:"Max spend ($)",style:{width:160},prefix:"$"}),(0,t.jsx)(l.Button,{type:"text",danger:!0,size:"small",onClick:()=>{s(e.filter((e,t)=>t!==n))},style:{padding:"0 4px"},children:"✕"})]}),o&&(0,t.jsxs)("div",{style:{fontSize:11,color:"#888",marginTop:3,marginLeft:2},children:["↻ ",o]})]},n)}),(0,t.jsx)(l.Button,{size:"small",onClick:t=>{t.preventDefault(),s([...e,{budget_duration:"24h",max_budget:null}])},children:"+ Add Budget Window"})]})}],319312);var u=e.i(311451);let m=0,g=()=>`tag-row-${m++}`;e.s(["TagRateLimitEditor",0,function({value:e,onChange:a}){let s=(t,l,s)=>{a(e.map((e,a)=>a===t?{...e,[l]:s}:e))};return(0,t.jsxs)("div",{children:[e.map((i,r)=>(0,t.jsxs)("div",{style:{display:"flex",gap:8,alignItems:"center",marginBottom:12},children:[(0,t.jsx)(u.Input,{value:i.tag,onChange:e=>s(r,"tag",e.target.value),placeholder:"Tag (e.g. cell-1)",style:{width:180}}),(0,t.jsx)(d.InputNumber,{min:0,value:i.rpm_limit??void 0,onChange:e=>s(r,"rpm_limit",e??null),placeholder:"RPM",style:{width:120}}),(0,t.jsx)(l.Button,{type:"text",danger:!0,size:"small",onClick:()=>{a(e.filter((e,t)=>t!==r))},style:{padding:"0 4px"},children:"✕"})]},i.id)),(0,t.jsx)(l.Button,{size:"small",onClick:t=>{t.preventDefault(),a([...e,{id:g(),tag:"",rpm_limit:null}])},children:"+ Add Tag Limit"})]})},"tagLimitsToRows",0,e=>{let t=(e=>{if(!e||"object"!=typeof e)return{};let t={};return Object.entries(e).forEach(([e,l])=>{"number"==typeof l&&(t[e]=l)}),t})(e);return Object.keys(t).map(e=>({id:g(),tag:e,rpm_limit:t[e]}))},"tagRowsToLimits",0,e=>{let t={};return e.forEach(({tag:e,rpm_limit:l})=>{let a=e.trim();a&&"number"==typeof l&&(t[a]=l)}),{tag_rpm_limit:t}}],833400)},390605,e=>{"use strict";var t=e.i(843476),l=e.i(271645),a=e.i(602869),s=e.i(599724),i=e.i(482725),r=e.i(91739),n=e.i(500727),o=e.i(531516),d=e.i(696609);e.s(["default",0,({accessToken:e,selectedServers:c,toolPermissions:u,onChange:m,disabled:g=!1})=>{let{data:p=[]}=(0,n.useMCPServers)(),[h,x]=(0,l.useState)({}),[y,f]=(0,l.useState)({}),[b,j]=(0,l.useState)({}),[_,v]=(0,l.useState)({}),A=(0,l.useRef)(u);(0,l.useEffect)(()=>{A.current=u},[u]);let w=(0,l.useMemo)(()=>0===c.length?[]:p.filter(e=>c.includes(e.server_id)),[p,c]),k=async(e,t)=>{f(t=>({...t,[e]:!0})),j(t=>({...t,[e]:""}));try{let l=await (0,a.listMCPTools)(t,e);if(l.error)j(t=>({...t,[e]:l.message||"Failed to fetch tools"})),x(t=>({...t,[e]:[]}));else{let t=l.tools||[];x(l=>({...l,[e]:t}));let a=A.current;if(!a[e]&&t.length>0){let l=t.filter(e=>"delete"!==(0,d.classifyToolOp)(e.name,e.description||"")).map(e=>e.name);m({...a,[e]:l})}}}catch(t){console.error(`Error fetching tools for server ${e}:`,t),j(t=>({...t,[e]:"Failed to fetch tools"})),x(t=>({...t,[e]:[]}))}finally{f(t=>({...t,[e]:!1}))}};(0,l.useEffect)(()=>{w.forEach(t=>{h[t.server_id]||y[t.server_id]||k(t.server_id,e)})},[w,e]);let N=(e,t)=>{m({...u,[e]:t})};return 0===c.length?null:(0,t.jsx)("div",{className:"space-y-4",children:w.map(e=>{let l=e.server_name||e.alias||e.server_id,a=h[e.server_id]||[],n=u[e.server_id]||[],d=y[e.server_id],c=b[e.server_id],p=_[e.server_id]??"crud";return(0,t.jsxs)("div",{className:"border rounded-lg bg-gray-50",children:[(0,t.jsxs)("div",{className:"flex items-center justify-between p-4 border-b bg-white rounded-t-lg",children:[(0,t.jsxs)("div",{children:[(0,t.jsx)(s.Text,{className:"font-semibold text-gray-900",children:l}),e.description&&(0,t.jsx)(s.Text,{className:"text-sm text-gray-500",children:e.description})]}),(0,t.jsxs)("div",{className:"flex items-center gap-3",children:[!g&&a.length>0&&(0,t.jsx)(r.Radio.Group,{value:p,onChange:t=>v(l=>({...l,[e.server_id]:t.target.value})),size:"small",optionType:"button",buttonStyle:"solid",options:[{label:"Risk Groups",value:"crud"},{label:"Flat List",value:"flat"}]}),!g&&(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)("button",{type:"button",className:"text-sm text-blue-600 hover:text-blue-700 font-medium",onClick:()=>{var t;let l;return l=h[t=e.server_id]||[],void m({...u,[t]:l.map(e=>e.name)})},disabled:d,children:"Select All"}),(0,t.jsx)("button",{type:"button",className:"text-sm text-blue-600 hover:text-blue-700 font-medium",onClick:()=>{var t;return t=e.server_id,void m({...u,[t]:[]})},disabled:d,children:"Deselect All"})]})]})]}),(0,t.jsxs)("div",{className:"p-4",children:[d&&(0,t.jsxs)("div",{className:"flex items-center justify-center py-8",children:[(0,t.jsx)(i.Spin,{size:"large"}),(0,t.jsx)(s.Text,{className:"ml-3 text-gray-500",children:"Loading tools..."})]}),c&&!d&&(0,t.jsxs)("div",{className:"p-4 bg-red-50 border border-red-200 rounded-lg text-center",children:[(0,t.jsx)(s.Text,{className:"text-red-600 font-medium",children:"Unable to load tools"}),(0,t.jsx)(s.Text,{className:"text-sm text-red-500 mt-1",children:c})]}),!d&&!c&&a.length>0&&"crud"===p&&(0,t.jsx)(o.default,{tools:a,value:u[e.server_id]?n:void 0,onChange:t=>N(e.server_id,t),readOnly:g}),!d&&!c&&a.length>0&&"flat"===p&&(0,t.jsx)("div",{className:"space-y-2",children:a.map(l=>{let a=n.includes(l.name);return(0,t.jsxs)("div",{className:"flex items-start gap-2",children:[(0,t.jsx)("input",{type:"checkbox",checked:a,onChange:()=>{if(g)return;let t=a?n.filter(e=>e!==l.name):[...n,l.name];N(e.server_id,t)},disabled:g,className:"mt-0.5"}),(0,t.jsx)("div",{className:"flex-1 min-w-0",children:(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)(s.Text,{className:"font-medium text-gray-900",children:l.name}),(0,t.jsxs)(s.Text,{className:"text-sm text-gray-500",children:["- ",l.description||"No description"]})]})})]},l.name)})}),!d&&!c&&0===a.length&&(0,t.jsx)("div",{className:"text-center py-6",children:(0,t.jsx)(s.Text,{className:"text-gray-500",children:"No tools available"})})]})]},e.server_id)})})}])},109034,e=>{"use strict";var t=e.i(266027),l=e.i(243652),a=e.i(602869),s=e.i(135214);let i=(0,l.createQueryKeys)("tags");e.s(["useTags",0,()=>{let{accessToken:e,userId:l,userRole:r}=(0,s.default)();return(0,t.useQuery)({queryKey:i.list({}),queryFn:async()=>await (0,a.tagListCall)(e),enabled:!!(e&&l&&r)})}])},533882,e=>{"use strict";var t=e.i(843476),l=e.i(271645),a=e.i(250980),s=e.i(797672),i=e.i(68155),r=e.i(304967),n=e.i(629569),o=e.i(599724),d=e.i(269200),c=e.i(427612),u=e.i(64848),m=e.i(942232),g=e.i(496020),p=e.i(977572),h=e.i(992619),x=e.i(727749);e.s(["default",0,({accessToken:e,initialModelAliases:y={},onAliasUpdate:f,showExampleConfig:b=!0})=>{let[j,_]=(0,l.useState)([]),[v,A]=(0,l.useState)({aliasName:"",targetModel:""}),[w,k]=(0,l.useState)(null);(0,l.useEffect)(()=>{_(Object.entries(y).map(([e,t],l)=>({id:`${l}-${e}`,aliasName:e,targetModel:t})))},[y]);let N=()=>{if(!w)return;if(!w.aliasName||!w.targetModel)return void x.default.fromBackend("Please provide both alias name and target model");if(j.some(e=>e.id!==w.id&&e.aliasName===w.aliasName))return void x.default.fromBackend("An alias with this name already exists");let e=j.map(e=>e.id===w.id?w:e);_(e),k(null);let t={};e.forEach(e=>{t[e.aliasName]=e.targetModel}),f&&f(t),x.default.success("Alias updated successfully")},S=()=>{k(null)},C=j.reduce((e,t)=>(e[t.aliasName]=t.targetModel,e),{});return(0,t.jsxs)("div",{className:"mt-4",children:[(0,t.jsxs)("div",{className:"mb-6",children:[(0,t.jsx)(o.Text,{className:"text-sm font-medium text-gray-700 mb-2",children:"Add New Alias"}),(0,t.jsxs)("div",{className:"grid grid-cols-3 gap-4",children:[(0,t.jsxs)("div",{children:[(0,t.jsx)("label",{className:"block text-xs text-gray-500 mb-1",children:"Alias Name"}),(0,t.jsx)("input",{type:"text",value:v.aliasName,onChange:e=>A({...v,aliasName:e.target.value}),placeholder:"e.g., gpt-4o",className:"w-full px-3 py-2 border border-gray-300 rounded-md text-sm"})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("label",{className:"block text-xs text-gray-500 mb-1",children:"Target Model"}),(0,t.jsx)(h.default,{accessToken:e,value:v.targetModel,placeholder:"Select target model",onChange:e=>A({...v,targetModel:e}),showLabel:!1})]}),(0,t.jsx)("div",{className:"flex items-end",children:(0,t.jsxs)("button",{onClick:()=>{if(!v.aliasName||!v.targetModel)return void x.default.fromBackend("Please provide both alias name and target model");if(j.some(e=>e.aliasName===v.aliasName))return void x.default.fromBackend("An alias with this name already exists");let e=[...j,{id:`${Date.now()}-${v.aliasName}`,aliasName:v.aliasName,targetModel:v.targetModel}];_(e),A({aliasName:"",targetModel:""});let t={};e.forEach(e=>{t[e.aliasName]=e.targetModel}),f&&f(t),x.default.success("Alias added successfully")},disabled:!v.aliasName||!v.targetModel,className:`flex items-center px-4 py-2 rounded-md text-sm ${!v.aliasName||!v.targetModel?"bg-gray-300 text-gray-500 cursor-not-allowed":"bg-green-600 text-white hover:bg-green-700"}`,children:[(0,t.jsx)(a.PlusCircleIcon,{className:"w-4 h-4 mr-1"}),"Add Alias"]})})]})]}),(0,t.jsx)(o.Text,{className:"text-sm font-medium text-gray-700 mb-2",children:"Manage Existing Aliases"}),(0,t.jsx)("div",{className:"rounded-lg custom-border relative mb-6",children:(0,t.jsx)("div",{className:"overflow-x-auto",children:(0,t.jsxs)(d.Table,{className:"[&_td]:py-0.5 [&_th]:py-1",children:[(0,t.jsx)(c.TableHead,{children:(0,t.jsxs)(g.TableRow,{children:[(0,t.jsx)(u.TableHeaderCell,{className:"py-1 h-8",children:"Alias Name"}),(0,t.jsx)(u.TableHeaderCell,{className:"py-1 h-8",children:"Target Model"}),(0,t.jsx)(u.TableHeaderCell,{className:"py-1 h-8",children:"Actions"})]})}),(0,t.jsxs)(m.TableBody,{children:[j.map(l=>(0,t.jsx)(g.TableRow,{className:"h-8",children:w&&w.id===l.id?(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(p.TableCell,{className:"py-0.5",children:(0,t.jsx)("input",{type:"text",value:w.aliasName,onChange:e=>k({...w,aliasName:e.target.value}),className:"w-full px-2 py-1 border border-gray-300 rounded-md text-sm"})}),(0,t.jsx)(p.TableCell,{className:"py-0.5",children:(0,t.jsx)(h.default,{accessToken:e,value:w.targetModel,onChange:e=>k({...w,targetModel:e}),showLabel:!1,style:{height:"32px"}})}),(0,t.jsx)(p.TableCell,{className:"py-0.5 whitespace-nowrap",children:(0,t.jsxs)("div",{className:"flex space-x-2",children:[(0,t.jsx)("button",{onClick:N,className:"text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded-sm hover:bg-blue-100",children:"Save"}),(0,t.jsx)("button",{onClick:S,className:"text-xs bg-gray-50 text-gray-600 px-2 py-1 rounded-sm hover:bg-gray-100",children:"Cancel"})]})})]}):(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(p.TableCell,{className:"py-0.5 text-sm text-gray-900",children:l.aliasName}),(0,t.jsx)(p.TableCell,{className:"py-0.5 text-sm text-gray-500",children:l.targetModel}),(0,t.jsx)(p.TableCell,{className:"py-0.5 whitespace-nowrap",children:(0,t.jsxs)("div",{className:"flex space-x-2",children:[(0,t.jsx)("button",{onClick:()=>{k({...l})},className:"text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded-sm hover:bg-blue-100",children:(0,t.jsx)(s.PencilIcon,{className:"w-3 h-3"})}),(0,t.jsx)("button",{onClick:()=>{var e;let t,a;return e=l.id,_(t=j.filter(t=>t.id!==e)),a={},void(t.forEach(e=>{a[e.aliasName]=e.targetModel}),f&&f(a),x.default.success("Alias deleted successfully"))},className:"text-xs bg-red-50 text-red-600 px-2 py-1 rounded-sm hover:bg-red-100",children:(0,t.jsx)(i.TrashIcon,{className:"w-3 h-3"})})]})})]})},l.id)),0===j.length&&(0,t.jsx)(g.TableRow,{children:(0,t.jsx)(p.TableCell,{colSpan:3,className:"py-0.5 text-sm text-gray-500 text-center",children:"No aliases added yet. Add a new alias above."})})]})]})})}),b&&(0,t.jsxs)(r.Card,{children:[(0,t.jsx)(n.Title,{className:"mb-4",children:"Configuration Example"}),(0,t.jsx)(o.Text,{className:"text-gray-600 mb-4",children:"Here's how your current aliases would look in the config:"}),(0,t.jsx)("div",{className:"bg-gray-100 rounded-lg p-4 font-mono text-sm",children:(0,t.jsxs)("div",{className:"text-gray-700",children:["model_aliases:",0===Object.keys(C).length?(0,t.jsxs)("span",{className:"text-gray-500",children:[(0,t.jsx)("br",{}),"  # No aliases configured yet"]}):Object.entries(C).map(([e,l])=>(0,t.jsxs)("span",{children:[(0,t.jsx)("br",{}),'  "',e,'": "',l,'"']},e))]})})]})]})}])},266484,e=>{"use strict";var t=e.i(843476),l=e.i(199133),a=e.i(592968),s=e.i(312361),i=e.i(827252),r=e.i(994388),n=e.i(304967),o=e.i(779241),d=e.i(988297),c=e.i(68155),u=e.i(810757),m=e.i(477386),g=e.i(557662),p=e.i(174553),h=e.i(435451);let{Option:x}=l.Select;e.s(["default",0,({value:e=[],onChange:y,disabledCallbacks:f=[],onDisabledCallbacksChange:b})=>{let j=Object.entries(g.callbackInfo).filter(([e,t])=>t.supports_key_team_logging).map(([e,t])=>e),_=Object.keys(g.callbackInfo),v=e=>{y?.(e)},A=(t,l,a)=>{let s=[...e];if("callback_name"===l){let e=g.callback_map[a]||a;s[t]={...s[t],[l]:e,callback_vars:{}}}else s[t]={...s[t],[l]:a};v(s)},w=(t,l,a)=>{let s=[...e];s[t]={...s[t],callback_vars:{...s[t].callback_vars,[l]:a}},v(s)};return(0,t.jsxs)("div",{className:"space-y-6",children:[(0,t.jsxs)("div",{className:"space-y-4",children:[(0,t.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,t.jsx)(m.BanIcon,{className:"w-5 h-5 text-red-500"}),(0,t.jsx)("span",{className:"text-base font-semibold text-gray-800",children:"Disabled Callbacks"}),(0,t.jsx)(a.Tooltip,{title:"Select callbacks to disable for this key. Disabled callbacks will not receive any logging data.",children:(0,t.jsx)(i.InfoCircleOutlined,{className:"text-gray-400 cursor-help"})})]}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsx)("label",{className:"text-sm font-medium text-gray-700",children:"Disabled Callbacks"}),(0,t.jsx)(l.Select,{mode:"multiple",placeholder:"Select callbacks to disable",value:f,onChange:e=>{let t=(0,g.mapDisplayToInternalNames)(e);b?.(t)},style:{width:"100%"},optionLabelProp:"label",children:_.map(e=>{let l=g.callbackInfo[e]?.description;return(0,t.jsx)(x,{value:e,label:e,children:(0,t.jsx)(a.Tooltip,{title:l,placement:"right",children:(0,t.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,t.jsx)(p.Logo,{src:g.callbackInfo[e]?.logo,label:e,className:"w-4 h-4 object-contain"}),(0,t.jsx)("span",{children:e})]})})},e)})}),(0,t.jsx)("div",{className:"text-xs text-gray-500",children:"Select callbacks that should be disabled for this key. These callbacks will not receive any logging data."})]})]}),(0,t.jsx)(s.Divider,{}),(0,t.jsxs)("div",{className:"flex justify-between items-center",children:[(0,t.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,t.jsx)(u.CogIcon,{className:"w-5 h-5 text-blue-500"}),(0,t.jsx)("span",{className:"text-base font-semibold text-gray-800",children:"Logging Integrations"}),(0,t.jsx)(a.Tooltip,{title:"Configure callback logging integrations for this team.",children:(0,t.jsx)(i.InfoCircleOutlined,{className:"text-gray-400 cursor-help"})})]}),(0,t.jsx)(r.Button,{variant:"secondary",onClick:()=>{v([...e,{callback_name:"",callback_type:"success",callback_vars:{}}])},icon:d.PlusIcon,size:"sm",className:"hover:border-blue-400 hover:text-blue-500",type:"button",children:"Add Integration"})]}),(0,t.jsx)("div",{className:"space-y-4",children:e.map((s,i)=>{let d=s.callback_name?Object.entries(g.callback_map).find(([e,t])=>t===s.callback_name)?.[0]:void 0;return(0,t.jsxs)(n.Card,{className:"border border-gray-200 shadow-xs hover:shadow-md transition-shadow duration-200",decoration:"top",decorationColor:"blue",children:[(0,t.jsxs)("div",{className:"flex justify-between items-start mb-4",children:[(0,t.jsxs)("div",{className:"flex items-center space-x-2",children:[d&&(0,t.jsx)(p.Logo,{src:g.callbackInfo[d]?.logo,label:d,className:"w-5 h-5 object-contain"}),(0,t.jsxs)("span",{className:"text-sm font-medium",children:[d||"New Integration"," Configuration"]})]}),(0,t.jsx)(r.Button,{variant:"light",onClick:()=>{v(e.filter((e,t)=>t!==i))},icon:c.TrashIcon,size:"xs",color:"red",className:"hover:bg-red-50",type:"button",children:"Remove"})]}),(0,t.jsxs)("div",{className:"space-y-4",children:[(0,t.jsxs)("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-4",children:[(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsx)("label",{className:"text-sm font-medium text-gray-700",children:"Integration Type"}),(0,t.jsx)(l.Select,{value:d,placeholder:"Select integration",onChange:e=>A(i,"callback_name",e),className:"w-full",optionLabelProp:"label",children:j.map(e=>{let l=g.callbackInfo[e]?.description;return(0,t.jsx)(x,{value:e,label:e,children:(0,t.jsx)(a.Tooltip,{title:l,placement:"right",children:(0,t.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,t.jsx)(p.Logo,{src:g.callbackInfo[e]?.logo,label:e,className:"w-4 h-4 object-contain"}),(0,t.jsx)("span",{children:e})]})})},e)})})]}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsx)("label",{className:"text-sm font-medium text-gray-700",children:"Event Type"}),(0,t.jsxs)(l.Select,{value:s.callback_type,onChange:e=>A(i,"callback_type",e),className:"w-full",children:[(0,t.jsx)(x,{value:"success",children:(0,t.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,t.jsx)("div",{className:"w-2 h-2 bg-green-500 rounded-full"}),(0,t.jsx)("span",{children:"Success Only"})]})}),(0,t.jsx)(x,{value:"failure",children:(0,t.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,t.jsx)("div",{className:"w-2 h-2 bg-red-500 rounded-full"}),(0,t.jsx)("span",{children:"Failure Only"})]})}),(0,t.jsx)(x,{value:"success_and_failure",children:(0,t.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,t.jsx)("div",{className:"w-2 h-2 bg-blue-500 rounded-full"}),(0,t.jsx)("span",{children:"Success & Failure"})]})})]})]})]}),((e,l)=>{if(!e.callback_name)return null;let a=Object.entries(g.callback_map).find(([t,l])=>l===e.callback_name)?.[0];if(!a)return null;let s=g.callbackInfo[a]?.dynamic_params||{};return 0===Object.keys(s).length?null:(0,t.jsxs)("div",{className:"mt-6 pt-4 border-t border-gray-100",children:[(0,t.jsxs)("div",{className:"flex items-center space-x-2 mb-4",children:[(0,t.jsx)("div",{className:"w-3 h-3 bg-blue-100 rounded-full flex items-center justify-center",children:(0,t.jsx)("div",{className:"w-1.5 h-1.5 bg-blue-500 rounded-full"})}),(0,t.jsx)("span",{className:"text-sm font-medium text-gray-700",children:"Integration Parameters"})]}),(0,t.jsx)("div",{className:"grid grid-cols-1 gap-4",children:Object.entries(s).map(([a,s])=>(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsxs)("label",{className:"text-sm font-medium text-gray-700 capitalize flex items-center space-x-1",children:[(0,t.jsx)("span",{children:a.replace(/_/g," ")}),"password"===s&&(0,t.jsx)("span",{className:"inline-flex items-center px-2 py-0.5 rounded-sm text-xs font-medium bg-yellow-100 text-yellow-800",children:"Sensitive"}),"number"===s&&(0,t.jsx)("span",{className:"inline-flex items-center px-2 py-0.5 rounded-sm text-xs font-medium bg-yellow-100 text-yellow-800",children:"Number"})]}),"number"===s&&(0,t.jsx)("span",{className:"text-xs text-gray-500",children:"Value must be between 0 and 1"}),"number"===s?(0,t.jsx)(h.default,{step:.01,width:400,placeholder:`os.environ/${a.toUpperCase()}`,value:e.callback_vars[a]||"",onChange:e=>w(l,a,e.target.value)}):(0,t.jsx)(o.TextInput,{type:"password"===s?"password":"text",placeholder:`os.environ/${a.toUpperCase()}`,value:e.callback_vars[a]||"",onChange:e=>w(l,a,e.target.value)})]},a))})]})})(s,i)]})]},i)})}),0===e.length&&(0,t.jsxs)("div",{className:"text-center py-12 text-gray-500 border-2 border-dashed border-gray-200 rounded-lg bg-gray-50/50",children:[(0,t.jsx)(u.CogIcon,{className:"w-12 h-12 text-gray-300 mb-3 mx-auto"}),(0,t.jsx)("div",{className:"text-base font-medium mb-1",children:"No logging integrations configured"}),(0,t.jsx)("div",{className:"text-sm text-gray-400",children:'Click "Add Integration" to configure logging for this team'})]})]})}])},651904,e=>{"use strict";var t=e.i(843476),l=e.i(599724),a=e.i(266484);e.s(["default",0,function({value:e,onChange:s,premiumUser:i=!1,disabledCallbacks:r=[],onDisabledCallbacksChange:n}){return i?(0,t.jsx)(a.default,{value:e,onChange:s,disabledCallbacks:r,onDisabledCallbacksChange:n}):(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"flex flex-wrap gap-2 mb-3",children:[(0,t.jsx)("div",{className:"inline-flex items-center px-3 py-1.5 rounded-lg bg-green-50 border border-green-200 text-green-800 text-sm font-medium opacity-50",children:"✨ langfuse-logging"}),(0,t.jsx)("div",{className:"inline-flex items-center px-3 py-1.5 rounded-lg bg-green-50 border border-green-200 text-green-800 text-sm font-medium opacity-50",children:"✨ datadog-logging"})]}),(0,t.jsx)("div",{className:"p-3 bg-yellow-50 border border-yellow-200 rounded-lg",children:(0,t.jsxs)(l.Text,{className:"text-sm text-yellow-800",children:["Setting Key/Team logging settings is a LiteLLM Enterprise feature. Global Logging Settings are available for all free users. Get a trial key"," ",(0,t.jsx)("a",{href:"https://www.litellm.ai/#pricing",target:"_blank",rel:"noopener noreferrer",className:"underline",children:"here"}),"."]})})]})}])},460285,e=>{"use strict";var t=e.i(843476),l=e.i(271645),a=e.i(404206),s=e.i(723731),i=e.i(653824),r=e.i(881073),n=e.i(197647),o=e.i(343488),d=e.i(602869),c=e.i(158392),u=e.i(419470),m=e.i(695411);let g=(0,l.forwardRef)(({accessToken:e,value:g,onChange:p,modelData:h},x)=>{let[y,f]=(0,l.useState)({routerSettings:{},selectedStrategy:null,enableTagFiltering:!1}),[b,j]=(0,l.useState)([]),[_,v]=(0,l.useState)([]),[A,w]=(0,l.useState)([]),[k,N]=(0,l.useState)([]),[S,C]=(0,l.useState)({}),[T,I]=(0,l.useState)({}),L=(0,l.useRef)(!1),E=(0,l.useRef)(null);(0,l.useEffect)(()=>{let e=g?.router_settings?JSON.stringify({routing_strategy:g.router_settings.routing_strategy,fallbacks:g.router_settings.fallbacks,enable_tag_filtering:g.router_settings.enable_tag_filtering}):null;if(L.current&&e===E.current){L.current=!1;return}if(L.current&&e!==E.current&&(L.current=!1),e!==E.current)if(E.current=e,g?.router_settings){let e=g.router_settings,{fallbacks:t,...l}=e;f({routerSettings:l,selectedStrategy:e.routing_strategy||null,enableTagFiltering:e.enable_tag_filtering??!1});let a=e.fallbacks||[];j(a),v(a&&0!==a.length?a.map((e,t)=>{let[l,a]=Object.entries(e)[0];return{id:(t+1).toString(),primaryModel:l||null,fallbackModels:a||[]}}):[{id:"1",primaryModel:null,fallbackModels:[]}])}else f({routerSettings:{},selectedStrategy:null,enableTagFiltering:!1}),j([]),v([{id:"1",primaryModel:null,fallbackModels:[]}])},[g]),(0,l.useEffect)(()=>{e&&(0,d.getRouterSettingsCall)(e).then(e=>{if(e.fields){let t={};e.fields.forEach(e=>{t[e.field_name]={ui_field_name:e.ui_field_name,field_description:e.field_description,options:e.options,link:e.link}}),C(t);let l=e.fields.find(e=>"routing_strategy"===e.field_name);l?.options&&N(l.options),e.routing_strategy_descriptions&&I(e.routing_strategy_descriptions)}})},[e]),(0,l.useEffect)(()=>{e&&(async()=>{try{let t=await (0,m.fetchAvailableModels)(e);w(t)}catch(e){console.error("Error fetching model info for fallbacks:",e)}})()},[e]);let O=()=>{let e=new Set(["allowed_fails","cooldown_time","num_retries","timeout","retry_after"]),t=new Set(["model_group_alias","retry_policy"]),l=Object.fromEntries(Object.entries({...y.routerSettings,enable_tag_filtering:y.enableTagFiltering,routing_strategy:y.selectedStrategy,fallbacks:b.length>0?b:null}).map(([l,a])=>{if("routing_strategy_args"!==l&&"routing_strategy"!==l&&"enable_tag_filtering"!==l&&"fallbacks"!==l){let s=document.querySelector(`input[name="${l}"]`);if(s){if(void 0!==s.value&&""!==s.value){let i=((l,a,s)=>{if(null==a)return s;let i=String(a).trim();if(""===i||"null"===i.toLowerCase())return null;if(e.has(l)){let e=Number(i);return Number.isNaN(e)?s:e}if(t.has(l)){if(""===i)return null;try{return JSON.parse(i)}catch{return s}}return"true"===i.toLowerCase()||"false"!==i.toLowerCase()&&i})(l,s.value,a);return[l,i]}return[l,null]}}else if("routing_strategy"===l)return[l,y.selectedStrategy];else if("enable_tag_filtering"===l)return[l,y.enableTagFiltering];else if("fallbacks"===l)return[l,b.length>0?b:null];else if("routing_strategy_args"===l&&"latency-based-routing"===y.selectedStrategy){let e=document.querySelector('input[name="lowest_latency_buffer"]'),t=document.querySelector('input[name="ttl"]'),l={};return e?.value&&(l.lowest_latency_buffer=Number(e.value)),t?.value&&(l.ttl=Number(t.value)),["routing_strategy_args",Object.keys(l).length>0?l:null]}return[l,a]}).filter(e=>null!=e)),a=(e,t=!1)=>null==e||"object"==typeof e&&!Array.isArray(e)&&0===Object.keys(e).length||t&&("number"!=typeof e||Number.isNaN(e))?null:e;return{routing_strategy:a(l.routing_strategy),allowed_fails:a(l.allowed_fails,!0),cooldown_time:a(l.cooldown_time,!0),num_retries:a(l.num_retries,!0),timeout:a(l.timeout,!0),retry_after:a(l.retry_after,!0),fallbacks:b.length>0?b:null,context_window_fallbacks:a(l.context_window_fallbacks),retry_policy:a(l.retry_policy),model_group_alias:a(l.model_group_alias),enable_tag_filtering:y.enableTagFiltering,routing_strategy_args:a(l.routing_strategy_args)}},F=(0,o.useDebouncedCallback)(()=>{p&&(L.current=!0,p({router_settings:O()}))},{wait:100});(0,l.useEffect)(()=>{p&&F()},[y,b]);let M=Array.from(new Set(A.map(e=>e.model_group))).sort();return((0,l.useImperativeHandle)(x,()=>({getValue:()=>({router_settings:O()})})),e)?(0,t.jsx)("div",{className:"w-full",children:(0,t.jsxs)(i.TabGroup,{className:"w-full",children:[(0,t.jsxs)(r.TabList,{variant:"line",defaultValue:"1",className:"px-8 pt-4",children:[(0,t.jsx)(n.Tab,{value:"1",children:"Loadbalancing"}),(0,t.jsx)(n.Tab,{value:"2",children:"Fallbacks"})]}),(0,t.jsxs)(s.TabPanels,{className:"px-8 py-6",children:[(0,t.jsx)(a.TabPanel,{children:(0,t.jsx)(c.default,{value:y,onChange:f,routerFieldsMetadata:S,availableRoutingStrategies:k,routingStrategyDescriptions:T})}),(0,t.jsx)(a.TabPanel,{children:(0,t.jsx)(u.FallbackSelectionForm,{groups:_,onGroupsChange:e=>{v(e),j(e.filter(e=>e.primaryModel&&e.fallbackModels.length>0).map(e=>({[e.primaryModel]:e.fallbackModels})))},availableModels:M,maxGroups:5})})]})]})}):null});g.displayName="RouterSettingsAccordion",e.s(["default",0,g])},575260,e=>{"use strict";var t=e.i(843476),l=e.i(199133),a=e.i(482725),s=e.i(56456);e.s(["default",0,({projects:e,value:i,onChange:r,disabled:n,loading:o,teamId:d})=>{let c=d?e?.filter(e=>e.team_id===d):e;return(0,t.jsx)(l.Select,{showSearch:!0,placeholder:"Search or select a project",value:i,onChange:r,disabled:n,loading:o,allowClear:!0,notFoundContent:o?(0,t.jsx)(a.Spin,{indicator:(0,t.jsx)(s.LoadingOutlined,{spin:!0}),size:"small"}):void 0,filterOption:(e,t)=>{if(!t)return!1;let l=c?.find(e=>e.project_id===t.key);if(!l)return!1;let a=e.toLowerCase().trim(),s=(l.project_alias||"").toLowerCase(),i=(l.project_id||"").toLowerCase();return s.includes(a)||i.includes(a)},optionFilterProp:"children",children:!o&&c?.map(e=>(0,t.jsxs)(l.Select.Option,{value:e.project_id,children:[(0,t.jsx)("span",{className:"font-medium",children:e.project_alias||e.project_id})," ",(0,t.jsxs)("span",{className:"text-gray-500",children:["(",e.project_id,")"]})]},e.project_id))})}])},364769,e=>{"use strict";var t=e.i(843476),l=e.i(271645),a=e.i(237016),s=e.i(464571),i=e.i(888259);e.s(["default",0,({apiKey:e})=>{let[r,n]=(0,l.useState)(!1);return(0,t.jsxs)("div",{children:[(0,t.jsxs)("p",{className:"mb-2",children:["Please save this secret key somewhere safe and accessible. For security reasons,"," ",(0,t.jsx)("b",{children:"you will not be able to view it again"})," through your LiteLLM account. If you lose this secret key, you will need to generate a new one."]}),(0,t.jsx)("p",{className:"text-sm text-gray-600 mt-3 mb-1",children:"Virtual Key:"}),(0,t.jsx)("div",{style:{background:"#f8f8f8",padding:"10px",borderRadius:"5px",marginBottom:"10px"},children:(0,t.jsx)("pre",{style:{wordWrap:"break-word",whiteSpace:"normal",margin:0},children:e})}),(0,t.jsx)(a.CopyToClipboard,{text:e,onCopy:()=>{n(!0),i.default.success("Key copied to clipboard"),setTimeout(()=>n(!1),2e3)},children:(0,t.jsx)(s.Button,{type:"primary",style:{marginTop:12},children:r?"Copied!":"Copy Virtual Key"})})]})}])},702597,e=>{"use strict";var t=e.i(843476),l=e.i(207082),a=e.i(109799),s=e.i(510674),i=e.i(109034),r=e.i(292639),n=e.i(135214),o=e.i(500330),d=e.i(827252),c=e.i(912598),u=e.i(677667),m=e.i(130643),g=e.i(898667),p=e.i(994388),h=e.i(309426),x=e.i(350967),y=e.i(599724),f=e.i(779241),b=e.i(629569),j=e.i(464571),_=e.i(808613),v=e.i(311451),A=e.i(212931),w=e.i(91739),k=e.i(199133),N=e.i(790848),S=e.i(262218),C=e.i(592968),T=e.i(898586),I=e.i(343488),L=e.i(741466),E=e.i(271645),O=e.i(708347),F=e.i(552130),M=e.i(557662),R=e.i(9314),B=e.i(860585),P=e.i(82946),D=e.i(392110),U=e.i(533882),z=e.i(844565),V=e.i(651904),K=e.i(939510),G=e.i(460285),Q=e.i(663435),W=e.i(363256),H=e.i(575260),q=e.i(371455),J=e.i(128233),$=e.i(319312),Y=e.i(833400),X=e.i(355619),Z=e.i(75921),ee=e.i(234713),et=e.i(390605),el=e.i(727749),ea=e.i(602869),es=e.i(364769),ei=e.i(435451),er=e.i(916940);let{Option:en}=k.Select,eo=async(e,t,l,a)=>{try{if(null===e||null===t)return[];if(null!==l)return(await (0,ea.modelAvailableCall)(l,e,t,!0,a,!0)).data.map(e=>e.id);return[]}catch(e){return console.error("Error fetching user models:",e),[]}},ed=async(e,t,l,a)=>{try{if(null===e||null===t)return;if(null!==l){let s=(await (0,ea.modelAvailableCall)(l,e,t)).data.map(e=>e.id);a(s)}}catch(e){console.error("Error fetching user models:",e)}};e.s(["default",0,({team:e,teams:ec,data:eu,addKey:em,autoOpenCreate:eg,prefillData:ep})=>{let{accessToken:eh,userId:ex,userRole:ey,premiumUser:ef}=(0,n.default)(),eb=ef||null!=ey&&O.rolesWithWriteAccess.includes(ey),{data:ej,isLoading:e_}=(0,a.useOrganizations)(),{data:ev,isLoading:eA}=(0,s.useProjects)(),{data:ew}=(0,r.useUISettings)(),{data:ek}=(0,i.useTags)(),eN=!!ew?.values?.enable_projects_ui,eS=!!ew?.values?.disable_custom_api_keys,eC=ek?Object.values(ek).map(e=>({value:e.name,label:e.name})):[],eT=(0,c.useQueryClient)(),[eI]=_.Form.useForm(),[eL,eE]=(0,E.useState)(!1),[eO,eF]=(0,E.useState)(null),[eM,eR]=(0,E.useState)(null),[eB,eP]=(0,E.useState)([]),[eD,eU]=(0,E.useState)([]),[ez,eV]=(0,E.useState)("you"),[eK,eG]=(0,E.useState)(!1),[eQ,eW]=(0,E.useState)(null),[eH,eq]=(0,E.useState)([]),[eJ,e$]=(0,E.useState)([]),[eY,eX]=(0,E.useState)([]),[eZ,e0]=(0,E.useState)([]),[e1,e4]=(0,E.useState)(e),[e2,e3]=(0,E.useState)(null),[e6,e5]=(0,E.useState)(null),[e7,e8]=(0,E.useState)(!1),[e9,te]=(0,E.useState)(null),[tt,tl]=(0,E.useState)({}),[ta,ts]=(0,E.useState)([]),[ti,tr]=(0,E.useState)(!1),[tn,to]=(0,E.useState)([]),[td,tc]=(0,E.useState)([]),[tu,tm]=(0,E.useState)("llm_api"),[tg,tp]=(0,E.useState)({}),[th,tx]=(0,E.useState)(!1),[ty,tf]=(0,E.useState)("30d"),[tb,tj]=(0,E.useState)(null),[t_,tv]=(0,E.useState)([]),[tA,tw]=(0,E.useState)([]),[tk,tN]=(0,E.useState)({}),[tS,tC]=(0,E.useState)(0),[tT,tI]=(0,E.useState)(0),[tL,tE]=(0,E.useState)([]),[tO,tF]=(0,E.useState)(null),tM=_.Form.useWatch("models",eI)??[],tR=()=>{eE(!1),eI.resetFields(),e0([]),tc([]),tm("llm_api"),tp({}),tx(!1),tf("30d"),tj(null),tI(e=>e+1),tF(null),e3(null),e5(null),tv([]),tw([]),tN({}),tC(e=>e+1)},tB=()=>{eE(!1),eF(null),e4(null),eI.resetFields(),e0([]),tc([]),tm("llm_api"),tp({}),tx(!1),tf("30d"),tj(null),tI(e=>e+1),tF(null),e3(null),e5(null),tv([]),tw([]),tN({}),tC(e=>e+1)};(0,E.useEffect)(()=>{ex&&ey&&eh&&ed(ex,ey,eh,eP)},[eh,ex,ey]),(0,E.useEffect)(()=>{eh&&(0,ea.getAgentsList)(eh).then(e=>tE(e?.agents||[])).catch(()=>tE([]))},[eh]),(0,E.useEffect)(()=>{let e=async()=>{try{let e=(await (0,ea.getPoliciesList)(eh)).policies.map(e=>e.policy_name);e$(e)}catch(e){console.error("Failed to fetch policies:",e)}},t=async()=>{try{let e=await (0,ea.getPromptsList)(eh);eX(e.prompts.map(e=>e.prompt_id))}catch(e){console.error("Failed to fetch prompts:",e)}};(async()=>{try{let e=(await (0,ea.getGuardrailsList)(eh)).guardrails.map(e=>e.guardrail_name);eq(e)}catch(e){console.error("Failed to fetch guardrails:",e)}})(),e(),t()},[eh]),(0,E.useEffect)(()=>{(async()=>{try{if(eh){let e=sessionStorage.getItem("possibleUserRoles");if(e)tl(JSON.parse(e));else{let e=await (0,ea.getPossibleUserRoles)(eh);sessionStorage.setItem("possibleUserRoles",JSON.stringify(e)),tl(e)}}}catch(e){console.error("Error fetching possible user roles:",e)}})()},[eh]),(0,E.useEffect)(()=>{if(eg&&!eK&&ec&&ey&&O.rolesWithWriteAccess.includes(ey)&&(eE(!0),eG(!0),ep)){if(ep.owned_by&&("another_user"===ep.owned_by&&"Admin"!==ey?eV("you"):eV(ep.owned_by)),ep.team_id){let e=ec?.find(e=>e.team_id===ep.team_id)||null;e&&(e4(e),eI.setFieldsValue({team_id:ep.team_id}))}ep.key_alias&&eI.setFieldsValue({key_alias:ep.key_alias}),ep.models&&ep.models.length>0&&eW(ep.models),ep.key_type&&(tm(ep.key_type),eI.setFieldsValue({key_type:ep.key_type}))}},[eg,ep,ec,eK,eI,ey]);let tP=eD.includes("no-default-models")&&!e1,tD=async e=>{try{let t,a=e?.key_alias??"",s=e?.team_id??null;if((eu?.filter(e=>e.team_id===s).map(e=>e.key_alias)??[]).includes(a))throw Error(`Key alias ${a} already exists for team with ID ${s}, please provide another key alias`);if(el.default.info("Making API Call"),eE(!0),"you"===ez)e.user_id=ex;else if("agent"===ez){if(!tO)return void el.default.fromBackend("Please select an agent");e.agent_id=tO}let i={};try{i=JSON.parse(e.metadata||"{}")}catch(e){console.error("Error parsing metadata:",e)}if("service_account"===ez&&(i.service_account_id=e.key_alias),eZ.length>0&&(i={...i,logging:eZ.filter(e=>e.callback_name)}),td.length>0){let e=(0,M.mapDisplayToInternalNames)(td);i={...i,litellm_disabled_callbacks:e}}if(th&&(e.auto_rotate=!0,e.rotation_interval=ty),e.duration&&""!==e.duration.trim()||(e.duration=null),e.metadata=JSON.stringify(i),e.disable_global_guardrails||delete e.disable_global_guardrails,e.allowed_vector_store_ids&&e.allowed_vector_store_ids.length>0&&(e.object_permission={vector_stores:e.allowed_vector_store_ids},delete e.allowed_vector_store_ids),e.allowed_mcp_servers_and_groups&&(e.allowed_mcp_servers_and_groups.servers?.length>0||e.allowed_mcp_servers_and_groups.accessGroups?.length>0||e.allowed_mcp_servers_and_groups.toolsets?.length>0)){e.object_permission||(e.object_permission={});let{servers:t,accessGroups:l,toolsets:a}=e.allowed_mcp_servers_and_groups;t&&t.length>0&&(e.object_permission.mcp_servers=t),l&&l.length>0&&(e.object_permission.mcp_access_groups=l),a&&a.length>0&&(e.object_permission.mcp_toolsets=a),delete e.allowed_mcp_servers_and_groups}let r=e.mcp_tool_permissions||{};if(Object.keys(r).length>0&&(e.object_permission||(e.object_permission={}),e.object_permission.mcp_tool_permissions=r),delete e.mcp_tool_permissions,e.allowed_mcp_access_groups&&e.allowed_mcp_access_groups.length>0&&(e.object_permission||(e.object_permission={}),e.object_permission.mcp_access_groups=e.allowed_mcp_access_groups,delete e.allowed_mcp_access_groups),e.allowed_agents_and_groups&&(e.allowed_agents_and_groups.agents?.length>0||e.allowed_agents_and_groups.accessGroups?.length>0)){e.object_permission||(e.object_permission={});let{agents:t,accessGroups:l}=e.allowed_agents_and_groups;t&&t.length>0&&(e.object_permission.agents=t),l&&l.length>0&&(e.object_permission.agent_access_groups=l),delete e.allowed_agents_and_groups}Object.keys(tg).length>0&&(e.aliases=JSON.stringify(tg)),tb?.router_settings&&Object.values(tb.router_settings).some(e=>null!=e&&""!==e)&&(e.router_settings=tb.router_settings);let n=t_.filter(e=>e.budget_duration&&null!==e.max_budget&&void 0!==e.max_budget);n.length>0&&(e.budget_limits=n);let{tag_rpm_limit:o}=(0,Y.tagRowsToLimits)(tA);Object.keys(o).length>0&&(e.tag_rpm_limit=o),Object.keys(tk).length>0&&(e.budget_fallbacks=tk),t="service_account"===ez?await (0,ea.keyCreateServiceAccountCall)(eh,e):await (0,ea.keyCreateCall)(eh,ex,e),em(t),eT.invalidateQueries({queryKey:l.keyKeys.lists()}),eF(t.key),eR(t.soft_budget),el.default.success("Virtual Key Created"),eI.resetFields(),tv([]),tw([]),tN({}),tC(e=>e+1),localStorage.removeItem("userData"+ex)}catch(t){let e=(e=>{let t;if(!(t=!e||"object"!=typeof e||e instanceof Error?String(e):JSON.stringify(e)).includes("/key/generate")&&!t.includes("KeyManagementRoutes.KEY_GENERATE"))return`Error creating the key: ${e}`;let l=t;try{if(!e||"object"!=typeof e||e instanceof Error){let e=t.match(/\{[\s\S]*\}/);if(e){let t=JSON.parse(e[0]),a=t?.error||t;a?.message&&(l=a.message)}}else{let t=e?.error||e;t?.message&&(l=t.message)}}catch(e){}return t.includes("team_member_permission_error")||l.includes("Team member does not have permissions")?"Team member does not have permission to generate key for this team. Ask your proxy admin to configure the team member permission settings.":`Error creating the key: ${e}`})(t);el.default.fromBackend(e)}};(0,E.useEffect)(()=>{if(e6){let e=ev?.find(e=>e.project_id===e6);eU(e?.models??[]),eI.setFieldValue("models",[]);return}ex&&ey&&eh&&eo(ex,ey,eh,e1?.team_id??null).then(e=>{eU((0,X.excludeProxyWideSentinel)(Array.from(new Set([...e1?.models??[],...e]))))}),eQ||eI.setFieldValue("models",[]),eI.setFieldValue("allowed_mcp_servers_and_groups",{servers:[],accessGroups:[]})},[e1,e6,eh,ex,ey,eI]),(0,E.useEffect)(()=>{if(!eQ||0===eQ.length||!eD||0===eD.length)return;let e=eQ.filter(e=>eD.includes(e));e.length>0&&eI.setFieldsValue({models:e}),eW(null)},[eQ,eD,eI]),(0,E.useEffect)(()=>{if(!e6||!ec)return;let e=ev?.find(e=>e.project_id===e6);if(!e?.team_id||e1?.team_id===e.team_id)return;let t=ec.find(t=>t.team_id===e.team_id)||null;t&&(e4(t),eI.setFieldValue("team_id",t.team_id))},[ec,e6,ev]);let tU=async e=>{if(!e)return void ts([]);tr(!0);try{let t=new URLSearchParams;if(t.append("user_email",e),null==eh)return;let l=(await (0,ea.userFilterUICall)(eh,t)).map(e=>({label:`${e.user_email} (${e.user_id})`,value:e.user_id,user:e}));ts(l)}catch(e){console.error("Error fetching users:",e),el.default.fromBackend("Failed to search for users")}finally{tr(!1)}},tz=(0,I.useDebouncedCallback)(e=>tU(e),{wait:L.DEBOUNCE_WAIT_MS});return(0,t.jsxs)("div",{children:[ey&&O.rolesWithWriteAccess.includes(ey)&&(0,t.jsx)(p.Button,{className:"mx-auto",onClick:()=>eE(!0),"data-testid":"create-key-button",children:"+ Create New Key"}),(0,t.jsx)(A.Modal,{open:eL,width:1e3,footer:null,onOk:tR,onCancel:tB,children:(0,t.jsxs)(_.Form,{form:eI,onFinish:tD,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",children:[(0,t.jsxs)("div",{className:"mb-8",children:[(0,t.jsx)(b.Title,{className:"mb-4",children:"Key Ownership"}),(0,t.jsx)(_.Form.Item,{label:(0,t.jsxs)("span",{children:["Owned By"," ",(0,t.jsx)(C.Tooltip,{title:"Select who will own this Virtual Key",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),className:"mb-4",children:(0,t.jsxs)(w.Radio.Group,{onChange:e=>eV(e.target.value),value:ez,children:[(0,t.jsx)(w.Radio,{value:"you",children:"You"}),(0,t.jsx)(w.Radio,{value:"service_account",children:"Service Account"}),"Admin"===ey&&(0,t.jsx)(w.Radio,{value:"another_user",children:"Another User"}),(0,t.jsxs)(w.Radio,{value:"agent",children:["Agent ",(0,t.jsx)(S.Tag,{color:"purple",children:"New"})]})]})}),"another_user"===ez&&(0,t.jsx)(_.Form.Item,{label:(0,t.jsxs)("span",{children:["User ID"," ",(0,t.jsx)(C.Tooltip,{title:"The user who will own this key and be responsible for its usage",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:"user_id",className:"mt-4",rules:[{required:"another_user"===ez,message:"Please input the user ID of the user you are assigning the key to"}],children:(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{style:{display:"flex",marginBottom:"8px"},children:[(0,t.jsx)(k.Select,{showSearch:!0,placeholder:"Type email to search for users",filterOption:!1,onSearch:tz,onSelect:(e,t)=>{let l;return l=t.user,void eI.setFieldsValue({user_id:l.user_id})},options:ta,loading:ti,allowClear:!0,style:{width:"100%"},notFoundContent:ti?"Searching...":"No users found"}),(0,t.jsx)(j.Button,{onClick:()=>e8(!0),style:{marginLeft:"8px"},children:"Create User"})]}),(0,t.jsx)("div",{className:"text-xs text-gray-500",children:"Search by email to find users"})]})}),"agent"===ez&&(0,t.jsxs)("div",{className:"mt-4 p-4 bg-purple-50 border border-purple-200 rounded-md",children:[(0,t.jsx)("div",{className:"mb-3",children:(0,t.jsxs)("span",{className:"text-sm font-medium text-gray-700",children:["Select Agent ",(0,t.jsx)("span",{className:"text-red-500",children:"*"})]})}),(0,t.jsx)(k.Select,{showSearch:!0,placeholder:"Select an agent",style:{width:"100%"},value:tO,onChange:e=>tF(e),filterOption:(e,t)=>t?.label?.toLowerCase().includes(e.toLowerCase()),options:tL.map(e=>({label:e.agent_name||e.agent_id,value:e.agent_id}))}),(0,t.jsx)("div",{className:"text-xs text-gray-500 mt-2",children:"This key will be used by the selected agent to make requests to LiteLLM"})]}),(0,t.jsx)(_.Form.Item,{label:(0,t.jsxs)("span",{children:["Organization"," ",(0,t.jsx)(C.Tooltip,{title:"The organization this key belongs to. Selecting an organization filters the available teams.",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:"organization_id",className:"mt-4",children:(0,t.jsx)(W.default,{organizations:ej,loading:e_,disabled:"Admin"!==ey,onChange:e=>{e3(e||null),e4(null),e5(null),eI.setFieldValue("team_id",void 0),eI.setFieldValue("project_id",void 0)}})}),(0,t.jsx)(_.Form.Item,{label:(0,t.jsxs)("span",{children:["Team"," ",(0,t.jsx)(C.Tooltip,{title:"The team this key belongs to, which determines available models and budget limits",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:"team_id",initialValue:e?e.team_id:null,className:"mt-4",rules:[{required:"service_account"===ez,message:"Please select a team for the service account"}],help:"service_account"===ez?"required":"",children:(0,t.jsx)(Q.default,{disabled:null!==e6,organizationId:e2,onTeamSelect:e=>{e4(e),e5(null),eI.setFieldValue("project_id",void 0),e?.organization_id?(e3(e.organization_id),eI.setFieldValue("organization_id",e.organization_id)):e||(e3(null),eI.setFieldValue("organization_id",void 0))}})}),eN&&(0,t.jsx)(_.Form.Item,{label:(0,t.jsxs)("span",{children:["Project"," ",(0,t.jsx)(C.Tooltip,{title:"Assign this key to a project. Selecting a project will lock the team to the project's team.",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:"project_id",className:"mt-4",children:(0,t.jsx)(H.default,{projects:ev,teamId:e1?.team_id,loading:eA||!ec,onChange:e=>{if(!e){e5(null),e4(null),eI.setFieldValue("team_id",void 0);return}e5(e)}})})]}),tP&&(0,t.jsx)("div",{className:"mb-8 p-4 bg-blue-50 border border-blue-200 rounded-md",children:(0,t.jsx)(y.Text,{className:"text-blue-800 text-sm",children:"Please select a team to continue configuring your Virtual Key. If you do not see any teams, please contact your Proxy Admin to either provide you with access to models or to add you to a team."})}),!tP&&(0,t.jsxs)("div",{className:"mb-8",children:[(0,t.jsx)(b.Title,{className:"mb-4",children:"Key Details"}),(0,t.jsx)(_.Form.Item,{label:(0,t.jsxs)("span",{children:["you"===ez||"another_user"===ez?"Key Name":"Service Account ID"," ",(0,t.jsx)(C.Tooltip,{title:"you"===ez||"another_user"===ez?"A descriptive name to identify this key":"Unique identifier for this service account",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:"key_alias",rules:[{required:!0,message:`Please input a ${"you"===ez?"key name":"service account ID"}`}],help:"required",children:(0,t.jsx)(f.TextInput,{placeholder:""})}),(0,t.jsx)(_.Form.Item,{label:(0,t.jsxs)("span",{children:["Models"," ",(0,t.jsx)(C.Tooltip,{title:"Select which models this key can access. Choose 'All Team Models' to grant access to all models available to the team. Leave empty to allow access to all models.",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:"models",rules:[],help:"management"===tu||"read_only"===tu?"Models field is disabled for this key type":"optional - leave empty to allow access to all models",className:"mt-4",children:(0,t.jsxs)(k.Select,{mode:"multiple",placeholder:"Select models",style:{width:"100%"},disabled:"management"===tu||"read_only"===tu,onChange:e=>{e.includes("all-team-models")?eI.setFieldsValue({models:["all-team-models"]}):e.includes("all-proxy-models")&&eI.setFieldsValue({models:["all-proxy-models"]})},children:[!e6&&e1&&(0,t.jsx)(en,{value:"all-team-models",children:"All Team Models"},"all-team-models"),!e6&&!e1&&(0,t.jsx)(en,{value:"all-proxy-models",children:"All Proxy Models"},"all-proxy-models"),eD.map(e=>(0,t.jsx)(en,{value:e,disabled:(0,X.hasAllModelsSentinel)(tM),children:(0,X.getModelDisplayName)(e)},e))]})}),(0,t.jsx)(_.Form.Item,{label:(0,t.jsxs)("span",{children:["Key Type"," ",(0,t.jsx)(C.Tooltip,{title:"Select the type of key to determine what routes and operations this key can access",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:"key_type",initialValue:"llm_api",className:"mt-4",children:(0,t.jsxs)(k.Select,{defaultValue:"llm_api",placeholder:"Select key type",style:{width:"100%"},optionLabelProp:"label",onChange:e=>{tm(e),("management"===e||"read_only"===e)&&eI.setFieldsValue({models:[]})},children:[(0,t.jsx)(en,{value:"llm_api",label:"AI APIs",children:(0,t.jsxs)("div",{style:{padding:"4px 0"},children:[(0,t.jsx)(T.Typography.Text,{strong:!0,children:"AI APIs"}),(0,t.jsx)(T.Typography.Paragraph,{type:"secondary",style:{fontSize:11,margin:"2px 0 0"},children:"Can call only AI API routes (chat/completions, embeddings, etc.)"})]})}),(0,t.jsx)(en,{value:"management",label:"Management",children:(0,t.jsxs)("div",{style:{padding:"4px 0"},children:[(0,t.jsx)(T.Typography.Text,{strong:!0,children:"Management"}),(0,t.jsx)(T.Typography.Paragraph,{type:"secondary",style:{fontSize:11,margin:"2px 0 0"},children:"Can call only management routes (user/team/key management)"})]})}),(0,t.jsx)(en,{value:"default",label:"Full Access",children:(0,t.jsxs)("div",{style:{padding:"4px 0"},children:[(0,t.jsx)(T.Typography.Text,{strong:!0,children:"Full Access"}),(0,t.jsx)(T.Typography.Paragraph,{type:"secondary",style:{fontSize:11,margin:"2px 0 0"},children:"Can call all routes (AI APIs, Management, and read-only)"})]})})]})})]}),!tP&&(0,t.jsx)("div",{className:"mb-8",children:(0,t.jsxs)(u.Accordion,{className:"mt-4 mb-4",children:[(0,t.jsx)(g.AccordionHeader,{children:(0,t.jsx)(b.Title,{className:"m-0",children:"Optional Settings"})}),(0,t.jsxs)(m.AccordionBody,{children:[(0,t.jsx)(_.Form.Item,{className:"mt-4",label:(0,t.jsxs)("span",{children:["Max Budget (USD)"," ",(0,t.jsx)(C.Tooltip,{title:"Maximum amount in USD this key can spend. When reached, the key will be blocked from making further requests",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:"max_budget",help:`Budget cannot exceed team max budget: $${e?.max_budget!==null&&e?.max_budget!==void 0?e?.max_budget:"unlimited"}`,rules:[{validator:async(t,l)=>{if(l&&e&&null!==e.max_budget&&l>e.max_budget)throw Error(`Budget cannot exceed team max budget: $${(0,o.formatNumberWithCommas)(e.max_budget,4)}`)}}],children:(0,t.jsx)(ei.default,{step:.01,precision:2,width:200})}),(0,t.jsx)(_.Form.Item,{className:"mt-4",label:(0,t.jsxs)("span",{children:["Reset Budget"," ",(0,t.jsx)(C.Tooltip,{title:"How often the budget should reset. For example, setting 'daily' will reset the budget every 24 hours",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:"budget_duration",help:`Team Reset Budget: ${e?.budget_duration!==null&&e?.budget_duration!==void 0?e?.budget_duration:"None"}`,children:(0,t.jsx)(B.default,{onChange:e=>eI.setFieldValue("budget_duration",e)})}),(0,t.jsx)(_.Form.Item,{className:"mt-4",label:(0,t.jsxs)("span",{children:["Budget Windows"," ",(0,t.jsx)(C.Tooltip,{title:"Set multiple independent budget windows (e.g., hourly $10 AND monthly $200). Each window tracks spend separately and resets on its own schedule.",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),children:(0,t.jsx)($.BudgetWindowsEditor,{value:t_,onChange:tv})}),(0,t.jsx)(_.Form.Item,{className:"mt-4",label:(0,t.jsxs)("span",{children:["Budget Fallbacks"," ",(0,t.jsx)(C.Tooltip,{title:"When a model exceeds its per-model budget (model_max_budget), requests automatically reroute to fallback models instead of failing. Configure per-model budgets in Advanced Settings.",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),children:(0,t.jsx)(J.BudgetFallbacksEditor,{value:tk,onChange:tN,availableModels:eD},tS)}),(0,t.jsx)(_.Form.Item,{className:"mt-4",label:(0,t.jsxs)("span",{children:["Tokens per minute Limit (TPM)"," ",(0,t.jsx)(C.Tooltip,{title:"Maximum number of tokens this key can process per minute. Helps control usage and costs",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:"tpm_limit",help:`TPM cannot exceed team TPM limit: ${e?.tpm_limit!==null&&e?.tpm_limit!==void 0?e?.tpm_limit:"unlimited"}`,rules:[{validator:async(t,l)=>{if(l&&e&&null!==e.tpm_limit&&l>e.tpm_limit)throw Error(`TPM limit cannot exceed team TPM limit: ${e.tpm_limit}`)}}],children:(0,t.jsx)(ei.default,{step:1,width:400})}),(0,t.jsx)(K.default,{type:"tpm",name:"tpm_limit_type",className:"mt-4",initialValue:null,form:eI,showDetailedDescriptions:!0}),(0,t.jsx)(_.Form.Item,{className:"mt-4",label:(0,t.jsxs)("span",{children:["Requests per minute Limit (RPM)"," ",(0,t.jsx)(C.Tooltip,{title:"Maximum number of API requests this key can make per minute. Helps prevent abuse and manage load",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:"rpm_limit",help:`RPM cannot exceed team RPM limit: ${e?.rpm_limit!==null&&e?.rpm_limit!==void 0?e?.rpm_limit:"unlimited"}`,rules:[{validator:async(t,l)=>{if(l&&e&&null!==e.rpm_limit&&l>e.rpm_limit)throw Error(`RPM limit cannot exceed team RPM limit: ${e.rpm_limit}`)}}],children:(0,t.jsx)(ei.default,{step:1,width:400})}),(0,t.jsx)(K.default,{type:"rpm",name:"rpm_limit_type",className:"mt-4",initialValue:null,form:eI,showDetailedDescriptions:!0}),(0,t.jsx)(_.Form.Item,{className:"mt-4",label:(0,t.jsxs)("span",{children:["Per-Tag Rate Limits"," ",(0,t.jsx)(C.Tooltip,{title:"Scope rate limits to a request tag so each tag (e.g. a cell or group) gets its own RPM counter. Requests without a matching tag fall back to the key-level limit.",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),children:(0,t.jsx)(Y.TagRateLimitEditor,{value:tA,onChange:tw})}),(0,t.jsx)(_.Form.Item,{className:"mt-4",label:(0,t.jsxs)("span",{children:["Throttle on budget exceeded"," ",(0,t.jsx)(C.Tooltip,{title:"When this key exceeds its max budget, throttle its TPM/RPM to the globally configured percentage instead of blocking access entirely. Requires budget_exceeded_throttle_percentage in litellm_settings and a TPM/RPM limit on the key.",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:"throttle_on_budget_exceeded",valuePropName:"checked",children:(0,t.jsx)(N.Switch,{checkedChildren:"Yes",unCheckedChildren:"No"})}),(0,t.jsx)(_.Form.Item,{label:(0,t.jsxs)("span",{children:["Guardrails"," ",(0,t.jsx)(C.Tooltip,{title:"Apply safety guardrails to this key to filter content or enforce policies",children:(0,t.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/guardrails/quick_start",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})})]}),name:"guardrails",className:"mt-4",help:eb?"Select existing guardrails or enter new ones":"Premium feature - Upgrade to set guardrails by key",children:(0,t.jsx)(k.Select,{mode:"tags",style:{width:"100%"},disabled:!eb,placeholder:eb?"Select or enter guardrails":"Premium feature - Upgrade to set guardrails by key",options:eH.map(e=>({value:e,label:e}))})}),(0,t.jsx)(_.Form.Item,{label:(0,t.jsxs)("span",{children:["Disable Global Guardrails"," ",(0,t.jsx)(C.Tooltip,{title:"When enabled, this key will bypass any guardrails configured to run on every request (global guardrails)",children:(0,t.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/guardrails/quick_start",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})})]}),name:"disable_global_guardrails",className:"mt-4",valuePropName:"checked",help:eb?"Bypass global guardrails for this key":"Premium feature - Upgrade to disable global guardrails by key",children:(0,t.jsx)(N.Switch,{disabled:!eb,checkedChildren:"Yes",unCheckedChildren:"No"})}),(0,t.jsx)(_.Form.Item,{label:(0,t.jsxs)("span",{children:["Policies"," ",(0,t.jsx)(C.Tooltip,{title:"Apply policies to this key to control guardrails and other settings",children:(0,t.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/guardrails/guardrail_policies",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})})]}),name:"policies",className:"mt-4",help:ef?"Select existing policies or enter new ones":"Premium feature - Upgrade to set policies by key",children:(0,t.jsx)(k.Select,{mode:"tags",style:{width:"100%"},disabled:!ef,placeholder:ef?"Select or enter policies":"Premium feature - Upgrade to set policies by key",options:eJ.map(e=>({value:e,label:e}))})}),(0,t.jsx)(_.Form.Item,{label:(0,t.jsxs)("span",{children:["Prompts"," ",(0,t.jsx)(C.Tooltip,{title:"Allow this key to use specific prompt templates",children:(0,t.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/prompt_management",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})})]}),name:"prompts",className:"mt-4",help:ef?"Select existing prompts or enter new ones":"Premium feature - Upgrade to set prompts by key",children:(0,t.jsx)(k.Select,{mode:"tags",style:{width:"100%"},disabled:!ef,placeholder:ef?"Select or enter prompts":"Premium feature - Upgrade to set prompts by key",options:eY.map(e=>({value:e,label:e}))})}),(0,t.jsx)(_.Form.Item,{label:(0,t.jsxs)("span",{children:["Access Groups"," ",(0,t.jsx)(C.Tooltip,{title:"Assign access groups to this key. Access groups control which models, MCP servers, and agents this key can use",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:"access_group_ids",className:"mt-4",help:"Select access groups to assign to this key",children:(0,t.jsx)(R.default,{placeholder:"Select access groups (optional)"})}),(0,t.jsx)(_.Form.Item,{label:(0,t.jsxs)("span",{children:["Allowed Pass Through Routes"," ",(0,t.jsx)(C.Tooltip,{title:"Allow this key to use specific pass through routes",children:(0,t.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/pass_through",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})})]}),name:"allowed_passthrough_routes",className:"mt-4",help:ef?"Select existing pass through routes or enter new ones":"Premium feature - Upgrade to set pass through routes by key",children:(0,t.jsx)(z.default,{onChange:e=>eI.setFieldValue("allowed_passthrough_routes",e),value:eI.getFieldValue("allowed_passthrough_routes"),accessToken:eh,placeholder:ef?"Select or enter pass through routes":"Premium feature - Upgrade to set pass through routes by key",disabled:!ef,teamId:e1?e1.team_id:null})}),(0,t.jsx)(_.Form.Item,{label:(0,t.jsxs)("span",{children:["Allowed Vector Stores"," ",(0,t.jsx)(C.Tooltip,{title:"Select which vector stores this key can access. If none selected, the key will have access to all available vector stores",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:"allowed_vector_store_ids",className:"mt-4",help:"Select vector stores this key can access. Leave empty for access to all vector stores",children:(0,t.jsx)(er.default,{onChange:e=>eI.setFieldValue("allowed_vector_store_ids",e),value:eI.getFieldValue("allowed_vector_store_ids"),accessToken:eh,placeholder:"Select vector stores (optional)"})}),(0,t.jsx)(_.Form.Item,{label:(0,t.jsxs)("span",{children:["Metadata"," ",(0,t.jsx)(C.Tooltip,{title:"JSON object with additional information about this key. Used for tracking or custom logic",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:"metadata",className:"mt-4",children:(0,t.jsx)(v.Input.TextArea,{rows:4,placeholder:"Enter metadata as JSON"})}),(0,t.jsx)(_.Form.Item,{label:(0,t.jsxs)("span",{children:["Tags"," ",(0,t.jsx)(C.Tooltip,{title:"Tags for tracking spend and/or doing tag-based routing. Used for analytics and filtering",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:"tags",className:"mt-4",help:"Tags for tracking spend and/or doing tag-based routing.",children:(0,t.jsx)(k.Select,{mode:"tags",style:{width:"100%"},placeholder:"Select or enter tags",tokenSeparators:[","],options:eC})}),(0,t.jsxs)(u.Accordion,{className:"mt-4 mb-4",children:[(0,t.jsx)(g.AccordionHeader,{children:(0,t.jsx)("b",{children:"MCP Settings"})}),(0,t.jsxs)(m.AccordionBody,{children:[(0,t.jsx)(_.Form.Item,{label:(0,t.jsxs)("span",{children:["Allowed MCP Servers"," ",(0,t.jsx)(C.Tooltip,{title:"Select which MCP servers or access groups this key can access",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:"allowed_mcp_servers_and_groups",help:"Select MCP servers or access groups this key can access",children:(0,t.jsx)(Z.default,{onChange:e=>eI.setFieldValue("allowed_mcp_servers_and_groups",e),value:eI.getFieldValue("allowed_mcp_servers_and_groups"),accessToken:eh,teamId:e1?.team_id??null,placeholder:"Select MCP servers or access groups (optional)",allowNoMcpServers:!0})}),(0,t.jsx)(_.Form.Item,{name:"mcp_tool_permissions",initialValue:{},hidden:!0,children:(0,t.jsx)(v.Input,{type:"hidden"})}),(0,t.jsx)(_.Form.Item,{noStyle:!0,shouldUpdate:(e,t)=>e.allowed_mcp_servers_and_groups!==t.allowed_mcp_servers_and_groups||e.mcp_tool_permissions!==t.mcp_tool_permissions,children:()=>(0,t.jsx)("div",{className:"mt-6",children:(0,t.jsx)(et.default,{accessToken:eh,selectedServers:(eI.getFieldValue("allowed_mcp_servers_and_groups")?.servers||[]).filter(e=>e!==ee.NO_MCP_SERVERS_SENTINEL),toolPermissions:eI.getFieldValue("mcp_tool_permissions")||{},onChange:e=>eI.setFieldsValue({mcp_tool_permissions:e})})})})]})]}),(0,t.jsxs)(u.Accordion,{className:"mt-4 mb-4",children:[(0,t.jsx)(g.AccordionHeader,{children:(0,t.jsx)("b",{children:"Agent Settings"})}),(0,t.jsx)(m.AccordionBody,{children:(0,t.jsx)(_.Form.Item,{label:(0,t.jsxs)("span",{children:["Allowed Agents"," ",(0,t.jsx)(C.Tooltip,{title:"Select which agents or access groups this key can access",children:(0,t.jsx)(d.InfoCircleOutlined,{style:{marginLeft:"4px"}})})]}),name:"allowed_agents_and_groups",help:"Select agents or access groups this key can access",children:(0,t.jsx)(F.default,{onChange:e=>eI.setFieldValue("allowed_agents_and_groups",e),value:eI.getFieldValue("allowed_agents_and_groups"),accessToken:eh,placeholder:"Select agents or access groups (optional)"})})})]}),ef?(0,t.jsxs)(u.Accordion,{className:"mt-4 mb-4",children:[(0,t.jsx)(g.AccordionHeader,{children:(0,t.jsx)("b",{children:"Logging Settings"})}),(0,t.jsx)(m.AccordionBody,{children:(0,t.jsx)("div",{className:"mt-4",children:(0,t.jsx)(V.default,{value:eZ,onChange:e0,premiumUser:!0,disabledCallbacks:td,onDisabledCallbacksChange:tc})})})]}):(0,t.jsx)(C.Tooltip,{title:(0,t.jsxs)("span",{children:["Key-level logging settings is an enterprise feature, get in touch -",(0,t.jsx)("a",{href:"https://www.litellm.ai/enterprise",target:"_blank",children:"https://www.litellm.ai/enterprise"})]}),placement:"top",children:(0,t.jsxs)("div",{style:{position:"relative"},children:[(0,t.jsx)("div",{style:{opacity:.5},children:(0,t.jsxs)(u.Accordion,{className:"mt-4 mb-4",children:[(0,t.jsx)(g.AccordionHeader,{children:(0,t.jsx)("b",{children:"Logging Settings"})}),(0,t.jsx)(m.AccordionBody,{children:(0,t.jsx)("div",{className:"mt-4",children:(0,t.jsx)(V.default,{value:eZ,onChange:e0,premiumUser:!1,disabledCallbacks:td,onDisabledCallbacksChange:tc})})})]})}),(0,t.jsx)("div",{style:{position:"absolute",inset:0,cursor:"not-allowed"}})]})}),(0,t.jsxs)(u.Accordion,{className:"mt-4 mb-4",children:[(0,t.jsx)(g.AccordionHeader,{children:(0,t.jsx)("b",{children:"Router Settings"})}),(0,t.jsx)(m.AccordionBody,{children:(0,t.jsx)("div",{className:"mt-4 w-full",children:(0,t.jsx)(G.default,{accessToken:eh||"",value:tb||void 0,onChange:tj,modelData:eB.length>0?{data:eB.map(e=>({model_name:e}))}:void 0},tT)})})]},`router-settings-accordion-${tT}`),(0,t.jsxs)(u.Accordion,{className:"mt-4 mb-4",children:[(0,t.jsx)(g.AccordionHeader,{children:(0,t.jsx)("b",{children:"Model Aliases"})}),(0,t.jsx)(m.AccordionBody,{children:(0,t.jsxs)("div",{className:"mt-4",children:[(0,t.jsx)(y.Text,{className:"text-sm text-gray-600 mb-4",children:"Create custom aliases for models that can be used in API calls. This allows you to create shortcuts for specific models."}),(0,t.jsx)(U.default,{accessToken:eh,initialModelAliases:tg,onAliasUpdate:tp,showExampleConfig:!1})]})})]}),(0,t.jsxs)(u.Accordion,{className:"mt-4 mb-4",children:[(0,t.jsx)(g.AccordionHeader,{children:(0,t.jsx)("b",{children:"Key Lifecycle"})}),(0,t.jsx)(m.AccordionBody,{children:(0,t.jsx)("div",{className:"mt-4",children:(0,t.jsx)(D.default,{form:eI,autoRotationEnabled:th,onAutoRotationChange:tx,rotationInterval:ty,onRotationIntervalChange:tf,isCreateMode:!0})})})]}),(0,t.jsxs)(u.Accordion,{className:"mt-4 mb-4",children:[(0,t.jsx)(g.AccordionHeader,{children:(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("b",{children:"Advanced Settings"}),(0,t.jsx)(C.Tooltip,{title:(0,t.jsxs)("span",{children:["Learn more about advanced settings in our"," ",(0,t.jsx)("a",{href:ea.proxyBaseUrl?`${ea.proxyBaseUrl}/#/key%20management/generate_key_fn_key_generate_post`:"/#/key%20management/generate_key_fn_key_generate_post",target:"_blank",rel:"noopener noreferrer",className:"text-blue-400 hover:text-blue-300",children:"documentation"})]}),children:(0,t.jsx)(d.InfoCircleOutlined,{className:"text-gray-400 hover:text-gray-300 cursor-help"})})]})}),(0,t.jsx)(m.AccordionBody,{children:(0,t.jsx)(P.default,{schemaComponent:"GenerateKeyRequest",form:eI,excludedFields:["key_alias","team_id","organization_id","models","duration","metadata","tags","guardrails","max_budget","budget_duration","tpm_limit","rpm_limit",...eS?["key"]:[]]})})]})]})]})}),(0,t.jsx)("div",{style:{textAlign:"right",marginTop:"10px"},children:(0,t.jsx)(j.Button,{htmlType:"submit",disabled:tP,style:{opacity:tP?.5:1},children:"Create Key"})})]})}),e7&&(0,t.jsx)(A.Modal,{title:"Create New User",open:e7,onCancel:()=>e8(!1),footer:null,width:800,children:(0,t.jsx)(q.CreateUserButton,{userID:ex,accessToken:eh,teams:ec,possibleUIRoles:tt,onUserCreated:e=>{te(e),eI.setFieldsValue({user_id:e}),e8(!1)},isEmbedded:!0})}),eO&&(0,t.jsx)(A.Modal,{open:eL,onOk:tR,onCancel:tB,footer:null,children:(0,t.jsxs)(x.Grid,{numItems:1,className:"gap-2 w-full",children:[(0,t.jsx)(b.Title,{children:"Save your Key"}),(0,t.jsx)(h.Col,{numColSpan:1,children:null!=eO?(0,t.jsx)(es.default,{apiKey:eO}):(0,t.jsx)(y.Text,{children:"Key being created, this might take 30s"})})]})})]})},"fetchTeamModels",0,eo,"fetchUserModels",0,ed],702597)}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/05ttqlxo9w0ow.js b/litellm/proxy/_experimental/out/_next/static/chunks/05ttqlxo9w0ow.js new file mode 100644 index 00000000000..62aaff6d34e --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/05ttqlxo9w0ow.js @@ -0,0 +1 @@ +(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,646563,e=>{"use strict";var t=e.i(959013);e.s(["PlusOutlined",()=>t.default])},913383,e=>{e.q("/litellm-asset-prefix/_next/static/media/a2a_agent.14el5-6dflh1h.png")},238564,e=>{e.q("/litellm-asset-prefix/_next/static/media/ai21.0m_u-tih8nm0v.svg")},941533,e=>{e.q("/litellm-asset-prefix/_next/static/media/aiml_api.1dq6dbpwklhlg.svg")},994636,e=>{e.q("/litellm-asset-prefix/_next/static/media/anthropic.3s95kgy8jpc64.svg")},824770,e=>{e.q("/litellm-asset-prefix/_next/static/media/assemblyai_small.0w_dslsra91uw.png")},920076,e=>{e.q("/litellm-asset-prefix/_next/static/media/baseten.41k2lnwp3c5g9.svg")},519380,e=>{e.q("/litellm-asset-prefix/_next/static/media/bedrock.2vhamxr8q6y4o.svg")},96889,e=>{e.q("/litellm-asset-prefix/_next/static/media/cerebras.1ur1xyfqk9ncz.svg")},348026,e=>{e.q("/litellm-asset-prefix/_next/static/media/cloudflare.2n1spdq7u5yki.svg")},727148,e=>{e.q("/litellm-asset-prefix/_next/static/media/cohere.22i3i2e449j5i.svg")},411703,e=>{e.q("/litellm-asset-prefix/_next/static/media/cometapi.3w0xficbg3dkk.svg")},922480,e=>{e.q("/litellm-asset-prefix/_next/static/media/cursor.1q1ev-_5l7exg.svg")},769490,e=>{e.q("/litellm-asset-prefix/_next/static/media/databricks.2hiet9qlqzn-g.svg")},829789,e=>{e.q("/litellm-asset-prefix/_next/static/media/deepgram.3-krp20p_xed3.png")},41730,e=>{e.q("/litellm-asset-prefix/_next/static/media/deepinfra.3lr_lsr7mhui6.png")},662576,e=>{e.q("/litellm-asset-prefix/_next/static/media/deepseek.3n4cu0x32i_7w.svg")},987202,e=>{e.q("/litellm-asset-prefix/_next/static/media/elevenlabs.2982m_dk2-h_y.png")},953265,e=>{e.q("/litellm-asset-prefix/_next/static/media/fal_ai.3rahrirki8sby.jpg")},346512,e=>{e.q("/litellm-asset-prefix/_next/static/media/featherless.3hmlef3fhc0h5.svg")},84416,e=>{e.q("/litellm-asset-prefix/_next/static/media/fireworks.3t1b6p8edeqyo.svg")},104602,e=>{e.q("/litellm-asset-prefix/_next/static/media/friendli.0ymiswh6l35bq.svg")},888193,e=>{e.q("/litellm-asset-prefix/_next/static/media/github_copilot.3k-8jyadoaq2u.svg")},946013,e=>{e.q("/litellm-asset-prefix/_next/static/media/google.3y8ypywwtqob_.svg")},447500,e=>{e.q("/litellm-asset-prefix/_next/static/media/groq.20csmqzsusp1k.svg")},270039,e=>{e.q("/litellm-asset-prefix/_next/static/media/huggingface.1-07ypt9ii_-p.svg")},227733,e=>{e.q("/litellm-asset-prefix/_next/static/media/hyperbolic.3le20v59sebn8.svg")},823929,e=>{e.q("/litellm-asset-prefix/_next/static/media/infinity.0s9s12bl4lccx.png")},333845,e=>{e.q("/litellm-asset-prefix/_next/static/media/jina.0ukab8o-3-5m_.png")},813878,e=>{e.q("/litellm-asset-prefix/_next/static/media/lambda.26_vz7cmyuodo.svg")},767788,e=>{e.q("/litellm-asset-prefix/_next/static/media/lmstudio.35s3-83mlhcms.svg")},808630,e=>{e.q("/litellm-asset-prefix/_next/static/media/meta_llama.1kxk24vwsem49.svg")},641266,e=>{e.q("/litellm-asset-prefix/_next/static/media/microsoft_azure.3626-7zx7wf09.svg")},468732,e=>{e.q("/litellm-asset-prefix/_next/static/media/minimax.2mfkkqc-lnsen.svg")},610503,e=>{e.q("/litellm-asset-prefix/_next/static/media/mistral.0n8jv_67hgq4h.svg")},438969,e=>{e.q("/litellm-asset-prefix/_next/static/media/moonshot.3__i9wvf37ksm.svg")},852459,e=>{e.q("/litellm-asset-prefix/_next/static/media/morph.2av06eo-t0-ve.svg")},494696,e=>{e.q("/litellm-asset-prefix/_next/static/media/nebius.2ipf7rmjccira.svg")},148973,e=>{e.q("/litellm-asset-prefix/_next/static/media/novita.0_nzmm_rl4lrf.svg")},570036,e=>{e.q("/litellm-asset-prefix/_next/static/media/nvidia_nim.1fz-5ugf_um0v.svg")},151170,e=>{e.q("/litellm-asset-prefix/_next/static/media/nvidia_triton.1aotoxig_m2w1.png")},862330,e=>{e.q("/litellm-asset-prefix/_next/static/media/ollama.144cif369atc5.svg")},956529,e=>{e.q("/litellm-asset-prefix/_next/static/media/openai_small.3rj8nedgjpevh.svg")},788158,e=>{e.q("/litellm-asset-prefix/_next/static/media/openrouter.1xk7748-_jixf.svg")},350567,e=>{e.q("/litellm-asset-prefix/_next/static/media/oracle.43kws59xxr8ig.svg")},822797,e=>{e.q("/litellm-asset-prefix/_next/static/media/perplexity-ai.2do8hoc8tw__0.svg")},840911,e=>{e.q("/litellm-asset-prefix/_next/static/media/qwen.0a49x9i08_0gz.png")},445735,e=>{e.q("/litellm-asset-prefix/_next/static/media/recraft.2f4cv-c9ad-mo.svg")},635202,e=>{e.q("/litellm-asset-prefix/_next/static/media/replicate.445bidyix2tyh.svg")},669161,e=>{e.q("/litellm-asset-prefix/_next/static/media/runway.2zzwye1fddnnn.png")},159508,e=>{e.q("/litellm-asset-prefix/_next/static/media/sambanova.1vcyu0faw1x8h.svg")},544025,e=>{e.q("/litellm-asset-prefix/_next/static/media/sap.1367hgge0s0xl.png")},100588,e=>{e.q("/litellm-asset-prefix/_next/static/media/snowflake.2_p8qqpi0r3lr.svg")},183804,e=>{e.q("/litellm-asset-prefix/_next/static/media/soniox.3nhjmssy7ybh7.svg")},865502,e=>{e.q("/litellm-asset-prefix/_next/static/media/togetherai.1wk-mouzgw5ho.svg")},96912,e=>{e.q("/litellm-asset-prefix/_next/static/media/topaz.3rjp3zvx4eags.svg")},395073,e=>{e.q("/litellm-asset-prefix/_next/static/media/v0.15trd3tb1ulop.svg")},407829,e=>{e.q("/litellm-asset-prefix/_next/static/media/vercel.1mvnwxofolt8y.svg")},846077,e=>{e.q("/litellm-asset-prefix/_next/static/media/vllm.3_gqby46r7s3x.png")},914861,e=>{e.q("/litellm-asset-prefix/_next/static/media/volcengine.23ly9ik_qc138.png")},285328,e=>{e.q("/litellm-asset-prefix/_next/static/media/voyage.0krq6ew-yr8dk.webp")},779278,e=>{e.q("/litellm-asset-prefix/_next/static/media/watsonx.19rrg39yvhpk8.svg")},325532,e=>{e.q("/litellm-asset-prefix/_next/static/media/xai.2kc3gjiopn9om.svg")},288330,e=>{e.q("/litellm-asset-prefix/_next/static/media/xinference.063cy_ievvy5u.svg")},555987,938137,301035,470524,901539,434339,857152,e=>{"use strict";var t=e.i(221688),i=e.i(950643);let a=/^(https?:|data:|blob:|\/\/)/i;e.s(["resolveLogoSrc",0,(e,l=t.serverRootPath)=>{let A;if(!e)return;if(a.test(e)||e.includes("/_next/static/"))return e;let r=(0,i.normalizeRootPath)(l);return r&&(e===r||e.startsWith(`${r}/`))?e:(A=(0,i.normalizeRootPath)(l),`${A}${e.startsWith("/")?e:`/${e}`}`)}],555987);let l={src:e.i(913383).default,width:661,height:768,blurWidth:7,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAICAYAAAA1BOUGAAAAwUlEQVR42h2OvQpGcByFf5dK7oBJFoooJDaDZFA+BoVkNBikGJSUJINyGeft/Q+nznDO00O6rsM0Tfi+z6JpGizLgqqqIEVR0HUdqqqCbdus930PWZZBy7IgCAKkaYpxHJEkCaIowjAMoGmaEMcx8jxHURRo2xZ1XaMsS9B5nrjvG+u6Yp5nfN+H53nwJ9JxHMiyDO/7Mtx1Xey1bRvIcRzwPI993xGGIZqmgSAIMAwDJEkSPM9jphzHsYHruhBFET+hn6S8pf6D7AAAAABJRU5ErkJggg=="};e.s(["default",0,l],938137);let A={src:e.i(238564).default,width:103,height:24,blurWidth:0,blurHeight:0};e.s(["default",0,A],301035);let r={src:e.i(941533).default,width:115,height:24,blurWidth:0,blurHeight:0};e.s(["default",0,r],470524);let s={src:e.i(994636).default,width:46,height:46,blurWidth:0,blurHeight:0};e.s(["default",0,s],901539);let o={src:e.i(824770).default,width:28,height:28,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAlElEQVR42m3OoQqDUBQGYF9mwzLbFiZsMNgwuGa4YFgYLC6PgcXuA5gFzdcgFkExGr33ogZRNIr6CAoKKgo/nAMfh/NTu3u4GWoY+0fIAsIC3C8LuIiRH9S2W5wBXsBPKaHTGGb+lZMJmCe2vPYjZe9/bMDswKMRbq9Yg9VRICcBq3p6FckINIf6o6ECwyOam/1YpwPFUFu+Lt2F2QAAAABJRU5ErkJggg=="};e.s(["default",0,o],434339);let d={src:e.i(920076).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,d],857152)},922158,e=>{"use strict";let t={src:e.i(519380).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,t])},896614,9774,503119,272896,144923,562171,533881,837957,227247,708889,859320,586455,921117,21296,579967,e=>{"use strict";let t={src:e.i(96889).default,width:800,height:600,blurWidth:0,blurHeight:0};e.s(["default",0,t],896614);let i={src:e.i(348026).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,i],9774);let a={src:e.i(727148).default,width:75,height:75,blurWidth:0,blurHeight:0};e.s(["default",0,a],503119);let l={src:e.i(411703).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,l],272896);let A={src:e.i(922480).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,A],144923);let r={src:e.i(769490).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,r],562171);let s={src:e.i(829789).default,width:32,height:32,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAzklEQVR42k3PPWuDcBDHccUiiikYSwqhg+IkuKUpOLqK4OYrcBZcFCcH352mgy/Ch9mxEPg2+s+Q4W65zx33kyRJQpZlFOUNTdM4HN45Hi1Op0/O56+7tAHbtqmqiqZpKIqCy+Uby/rYkABhGLKuK+M4siwLt9sv1+sPpmkKEAQBwzCQpilxHDNNE3VdYxiGALqu47oujuMQRdEOyrJEVVUBFEUhSRK6rmOeZ/q+x/f97XkBHqfIsoy2bcnzHM/z9mSP2Q62dt/0c+O1/v4BYRxu3LgaR7UAAAAASUVORK5CYII="};e.s(["default",0,s],533881);let o={src:e.i(41730).default,width:225,height:225,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAfklEQVR42nVOvQqDMBjM+7+RpbQ0Ji2NQhwdzGIwCMZFiCAa9STqIPhxw/3A3UeW41w/AKck87w0tqvbngkFgEDCJOPoKc+fovxKzaUGiXkOk0zei1TRzDxYEbHiIw0kzD2Aji5BqHolW9Uv0+9U01AVxivr4r8CjHX7+N27K5WIrP56XsFTAAAAAElFTkSuQmCC"};e.s(["default",0,o],837957);let d={src:e.i(662576).default,width:293,height:215,blurWidth:0,blurHeight:0};e.s(["default",0,d],227247);let u={src:e.i(987202).default,width:1563,height:1563,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA5ElEQVR42h2PPwtBURjGj3wCk93sC1iUCYXFIlGSMjC4QiiTBSE3Ge5M3e0uPoJFBhOTP8NNukXduFL3uJ1Xzz11znCe93me38uitQSL11Pe8rAanKpycbSa5Ar9UiAmJT3QXHGwHGfvj/vJcRzOfz/7fLvsW4tuJFZLehicEIUQpOs6GYZBOIfrcZPp5f0MsXDatk2yLJOqqu6A9f2YjXk7zNCJWM45KYpCmqa5A6/P+ynNGiEGIHSiwjRNsiyLBAmxPezW6U7Gx0ALIHQiFk6I5WEl6G6BB7QAQqc0a4bgxD/uH4FYoVec8m3dAAAAAElFTkSuQmCC"};e.s(["default",0,u],708889);let n={src:e.i(953265).default,width:400,height:400,blurWidth:8,blurHeight:8,blurDataURL:"data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/wAARCAAIAAgDAREAAhEBAxEB/9sAQwAKBwcIBwYKCAgICwoKCw4YEA4NDQ4dFRYRGCMfJSQiHyIhJis3LyYpNCkhIjBBMTQ5Oz4+PiUuRElDPEg3PT47/9sAQwEKCwsODQ4cEBAcOygiKDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDtP9N/tL+PO/8A7ZeV/wDFUj6L917L+r3/AMj/2Q=="};e.s(["default",0,n],859320);let h={src:e.i(346512).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,h],586455);let c={src:e.i(84416).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,c],921117);let g={src:e.i(104602).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,g],21296);let f={src:e.i(888193).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,f],579967)},336712,e=>{"use strict";let t={src:e.i(946013).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,t])},770752,383963,862493,902860,901372,206258,176228,728685,e=>{"use strict";let t={src:e.i(447500).default,width:26,height:26,blurWidth:0,blurHeight:0};e.s(["default",0,t],770752);let i={src:e.i(270039).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,i],383963);let a={src:e.i(227733).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,a],862493);let l={src:e.i(823929).default,width:128,height:129,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAApklEQVR42oWPsQrCMBRFY51KaUuwNktBQbcgSenQ32hCTBAHXfSfulUK4g/0/7yvdO+FB5dzXhLC2FoiJE3TfZ7nYrskyzIBVpBjTdPcu677GGPGtm1fmDd1YnVd31gIYSrLUnLOjxC9tfaLfhBCXLz307xQVZUCODvnfljoceAEpskxKeUV1w0Qo1LqobV+Lk8McI5tkDiOeZIkO9SIBr0gRm71l38QASaqNz7VMwAAAABJRU5ErkJggg=="};e.s(["default",0,l],902860);let A={src:e.i(333845).default,width:200,height:200,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAXUlEQVR42p2MoRHAMAwDQwJzCcgCwQEZIDjA03gED2BzewSPWRe2qFcB3Z+kU0o/lXNea4U/0t77OYeIACDq4FLKnSKimYmIqjKzu885U6117z3GCGitxTbg/flJF7DAEmcR3WAqAAAAAElFTkSuQmCC"};e.s(["default",0,A],901372);let r={src:e.i(813878).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,r],206258);let s={src:e.i(767788).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,s],176228);let o={src:e.i(808630).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,o],728685)},39182,e=>{"use strict";let t={src:e.i(641266).default,width:200,height:58,blurWidth:0,blurHeight:0};e.s(["default",0,t])},272967,551726,399495,740876,709103,277207,836473,768493,297720,e=>{"use strict";let t={src:e.i(468732).default,width:490,height:412,blurWidth:0,blurHeight:0};e.s(["default",0,t],272967);let i={src:e.i(610503).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,i],551726);let a={src:e.i(438969).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,a],399495);let l={src:e.i(852459).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,l],740876);let A={src:e.i(494696).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,A],709103);let r={src:e.i(148973).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,r],277207);let s={src:e.i(570036).default,width:16,height:16,blurWidth:0,blurHeight:0};e.s(["default",0,s],836473);let o={src:e.i(151170).default,width:95,height:81,blurWidth:8,blurHeight:7,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAHCAYAAAA1WQxeAAAA0UlEQVR42kXMvWrCUBjG8VxIL6KX0IvobXQSOrQd2kLSdMzQdMmaEErPUqoIIYtKOC5iFD9BEBwiaAY/AgpH/2oUfOGFh4cfj8blNmrNKK0zmFUZzmust2nea7u9IlkNkBMPv1Xgs3JHoXRDe1o+g8Vmyl/vnf++wU/7EdF54jW8JU6K14WTPgE3fjiCZ77kPa2kdAZZlrFcLpikXYLeN6Lxxm/zhfEsRimF5roupmli2zbGh4Gu61iWlX8URWhSSsIwxPM8hBA4jpNn3/cJgoADqC23HJzwo6wAAAAASUVORK5CYII="};e.s(["default",0,o],768493);let d={src:e.i(862330).default,width:646,height:854,blurWidth:0,blurHeight:0};e.s(["default",0,d],297720)},980385,e=>{"use strict";let t={src:e.i(956529).default,width:28,height:28,blurWidth:0,blurHeight:0};e.s(["default",0,t])},916925,247044,e=>{"use strict";var t,i=e.i(555987),a=e.i(938137),l=e.i(301035),A=e.i(470524),r=e.i(901539),s=e.i(434339),o=e.i(857152),d=e.i(922158),u=e.i(896614),n=e.i(9774),h=e.i(503119),c=e.i(272896),g=e.i(144923),f=e.i(562171),m=e.i(533881),p=e.i(837957),b=e.i(227247),x=e.i(708889),I=e.i(859320),E=e.i(586455),C=e.i(921117),O=e.i(21296),w=e.i(579967),_=e.i(336712),v=e.i(770752),R=e.i(383963),L=e.i(862493),B=e.i(902860),k=e.i(901372),T=e.i(206258),H=e.i(176228),U=e.i(728685),D=e.i(39182),M=e.i(272967),S=e.i(551726),y=e.i(399495),q=e.i(740876),W=e.i(709103),N=e.i(277207),G=e.i(836473),P=e.i(768493),Q=e.i(297720),z=e.i(980385);let F={src:e.i(788158).default,width:300,height:300,blurWidth:0,blurHeight:0},V={src:e.i(350567).default,width:231,height:30,blurWidth:0,blurHeight:0},K={src:e.i(822797).default,width:48,height:48,blurWidth:0,blurHeight:0},j={src:e.i(840911).default,width:1024,height:1024,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAABE0lEQVR42gEIAff+AAAAAAAAAAAAEQwmIT8ukIAgGUpBBwYQDQQECggAAAAAAAIBBQQJBhUTFw80LUIxloZbR8+5UUS3oUA4kH0HBxEOACMUUkolFlVNCAYTERwUPzgkHFJIIhxNRCsmYVQODSAbADYffnNFKaCSDgkgHD0tjX4+MI5+CQgVEhQSLCULChgUABAKJiJTM7+vJhlYTzEkb2MxJm9iIh1NQywnZFYhH0o/AA8JIh9RMrurNiR9cQkHFhMQDSUgUkW5o1hOx60pJlxPAAcEDw4qGmJYFw80LhENKCMyKHFjUEK0nhEOJSADAwYFAAAAAAADAgcGBQMLCRUPMClCNJeEIRtLQgAAAQAAAAAAfjQxsiCQipMAAAAASUVORK5CYII="},Y={src:e.i(445735).default,width:16,height:16,blurWidth:0,blurHeight:0},J={src:e.i(635202).default,width:16,height:16,blurWidth:0,blurHeight:0},Z={src:e.i(669161).default,width:320,height:320,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA7UlEQVR42lVQvc7BYBR+B/kM3w+fFm2p9m3fRhFRP7UQlTAQCVfAgE3EHZAQMTBIzBIrYbEgFhaza3ABrsF7bIYnOTl5/s5BVrvIY5MMvYTbUuy8hN8CcFIdW62CgHCKjFwie/So3PqH+b0Hc+GebkW6Lj9zUtJaHwEbFvGqWf7zOG5GJVmjcwUEAUOevQmhfKSTqJol+7f94RT+L7Yv25OV3PtsI699EByc85qpW5qcUCaMyB7orH9EgJLaTnPNgk/QfUsxKi0QtKWFzjRzA7a08IEPCivYKWkyQHAKtJUMPIVMQCAmz3FKHcELXnxEPy9E1lYCAAAAAElFTkSuQmCC"},X={src:e.i(159508).default,width:52,height:46,blurWidth:0,blurHeight:0},$={src:e.i(544025).default,width:3300,height:2550,blurWidth:8,blurHeight:6,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAYAAAD+Bd/7AAAAZklEQVR42oWNTQ5AMBhE3f8mEhux4BgiIRzAwoKWplrt146fBbVhkpnNvORF+El0jvMAV4RR2qtsJZD3DyA2h6xhiMsRSTUhrRm0DYBFE4qOI285yl5CGvdW0OEYhEE/G6jgvIGv7A4KupBwIVaNAAAAAElFTkSuQmCC"},ee={src:e.i(100588).default,width:146,height:139,blurWidth:0,blurHeight:0};e.s(["default",0,ee],247044);let et={src:e.i(183804).default,width:100,height:18,blurWidth:0,blurHeight:0},ei={src:e.i(865502).default,width:32,height:32,blurWidth:0,blurHeight:0},ea={src:e.i(96912).default,width:16,height:16,blurWidth:0,blurHeight:0},el={src:e.i(395073).default,width:16,height:16,blurWidth:0,blurHeight:0},eA={src:e.i(407829).default,width:16,height:16,blurWidth:0,blurHeight:0},er={src:e.i(846077).default,width:64,height:64,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAo0lEQVR42lWOvQrCQBCE7718oUA6wVcQBF/AXhu1CIgcpgoi/qCNguBPwCaChYHDv9zt3e6ZeEFxq9n5GGaY/T8iq3QhWKFRkZGWzEPaaE/RDoiI5e7z0BILP7tMB2vyO3p2BJew8syvvJLGQTPEaheSVJeAQIi5F2+CWt80uHopLMEnNBpPuNc2vaXK+38A4RauTvUhbBNwDvsOlYD3DA2Se99kLK7hC5QFVQAAAABJRU5ErkJggg=="},es={src:e.i(914861).default,width:1024,height:1024,blurWidth:8,blurHeight:8,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA7ElEQVR42l2LMUsCAQBG39GFFQ02ad1oq0QIRR2hER4EGdQqdBDdQZAFEudU0NZSQ0O1VUOjiuJvEBxc3BQFEVQQdFAU9OROFwf9lg/e48Hiti8vWJekBSoIODacLK04CP79IAX8815cW2X3IYL3JIhaKuKLGdNGmMUCTo+Hq1yO72yB6HDEeTrFjm8fUVwGt3uL00eD5+6AfMvixbS5bTR5/43jcm2CooT4KNf56ltUGzafA5uoZZGpdJDlABhPr7z1TJLtMbXamMT0702T//aQu0gM5GOFvRuNkKoTDuucqRpeXePoWuPg0D8BGkpJjYhKXN4AAAAASUVORK5CYII="},eo={src:e.i(285328).default,width:400,height:399,blurWidth:8,blurHeight:8,blurDataURL:"data:image/webp;base64,UklGRgwBAABXRUJQVlA4TAABAAAvB8ABEM1VICICHggIDgIAAIDzDAACD0FoAAARAAwAAAAAAAAAAAAAAACAAABAAcBCAoAWgGyoXwUAAA8E7QQCAADg/N+nrsYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4CClZXkg5AQAAAAA538LAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL0HIgEAAAAAcP4ZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMsHA39Qutr2AtkROu8CASWZ/nMlNyzz5AdgWmkK65oUI6AlgIkY/M3zxIW3iABq15KT/Uz7BXidCOONHieAz1v6HqAC"},ed={src:e.i(779278).default,width:16,height:16,blurWidth:0,blurHeight:0},eu={src:e.i(325532).default,width:1e3,height:1e3,blurWidth:0,blurHeight:0},en={src:e.i(288330).default,width:16,height:16,blurWidth:0,blurHeight:0};var eh=((t={}).A2A_Agent="A2A Agent",t.AI21="Ai21",t.AI21_CHAT="Ai21 Chat",t.AIML="AI/ML API",t.AIOHTTP_OPENAI="Aiohttp Openai",t.Anthropic="Anthropic",t.ANTHROPIC_TEXT="Anthropic Text",t.AssemblyAI="AssemblyAI",t.AUTO_ROUTER="Auto Router",t.Bedrock="Amazon Bedrock",t.BedrockMantle="Amazon Bedrock Mantle",t.SageMaker="AWS SageMaker",t.Azure="Azure",t.Azure_AI_Studio="Azure AI Foundry (Studio)",t.AZURE_TEXT="Azure Text",t.BASETEN="Baseten",t.BYTEZ="Bytez",t.Cerebras="Cerebras",t.CLARIFAI="Clarifai",t.CLOUDFLARE="Cloudflare",t.CODESTRAL="Codestral",t.Cohere="Cohere",t.COHERE_CHAT="Cohere Chat",t.COMETAPI="Cometapi",t.COMPACTIFAI="Compactifai",t.Cursor="Cursor",t.Dashscope="Dashscope",t.Databricks="Databricks (Qwen API)",t.DATAROBOT="Datarobot",t.DeepInfra="DeepInfra",t.Deepgram="Deepgram",t.Deepseek="Deepseek",t.DOCKER_MODEL_RUNNER="Docker Model Runner",t.DOTPROMPT="Dotprompt",t.ElevenLabs="ElevenLabs",t.EMPOWER="Empower",t.FalAI="Fal AI",t.FEATHERLESS_AI="Featherless Ai",t.FireworksAI="Fireworks AI",t.FRIENDLIAI="Friendliai",t.GALADRIEL="Galadriel",t.GITHUB_COPILOT="Github Copilot",t.Google_AI_Studio="Google AI Studio",t.GradientAI="GradientAI",t.Groq="Groq",t.HEROKU="Heroku",t.Hosted_Vllm="vllm",t.HUGGINGFACE="Huggingface",t.HYPERBOLIC="Hyperbolic",t.Infinity="Infinity",t.JinaAI="Jina AI",t.LAMBDA_AI="Lambda Ai",t.LEMONADE="Lemonade",t.LLAMAFILE="Llamafile",t.LM_STUDIO="Lm Studio",t.LLAMA="Meta Llama",t.MARITALK="Maritalk",t.MiniMax="MiniMax",t.MistralAI="Mistral AI",t.MOONSHOT="Moonshot",t.MORPH="Morph",t.NEBIUS="Nebius",t.NLP_CLOUD="Nlp Cloud",t.NOVITA="Novita",t.NSCALE="Nscale",t.NVIDIA_NIM="Nvidia Nim",t.Ollama="Ollama",t.OLLAMA_CHAT="Ollama Chat",t.OOBABOOGA="Oobabooga",t.OpenAI="OpenAI",t.OPENAI_LIKE="Openai Like",t.OpenAI_Compatible="OpenAI-Compatible Chat Completions (Together AI, vLLM, etc.)",t.OpenAI_Text="OpenAI Text Completion",t.OpenAI_Text_Compatible="OpenAI-Compatible Completions (legacy /v1/completions)",t.Openrouter="Openrouter",t.Oracle="Oracle Cloud Infrastructure (OCI)",t.OVHCLOUD="Ovhcloud",t.Perplexity="Perplexity",t.PETALS="Petals",t.PG_VECTOR="Pg Vector",t.PREDIBASE="Predibase",t.RECRAFT="Recraft",t.REPLICATE="Replicate",t.RunwayML="RunwayML",t.SAGEMAKER_LEGACY="Sagemaker",t.Sambanova="Sambanova",t.SAP="SAP Generative AI Hub",t.Snowflake="Snowflake",t.Soniox="Soniox",t.TEXT_COMPLETION_CODESTRAL="Text-Completion-Codestral",t.TogetherAI="TogetherAI",t.TOPAZ="Topaz",t.Triton="Triton",t.V0="V0",t.VERCEL_AI_GATEWAY="Vercel Ai Gateway",t.Vertex_AI="Vertex AI (Anthropic, Gemini, etc.)",t.VERTEX_AI_BETA="Vertex Ai Beta",t.VLLM="Vllm",t.VolcEngine="VolcEngine",t.Voyage="Voyage AI",t.WANDB="Wandb",t.WATSONX="Watsonx",t.WATSONX_TEXT="Watsonx Text",t.xAI="xAI",t.XINFERENCE="Xinference",t.ZAI="Z.AI (Zhipu AI)",t);let ec={A2A_Agent:"a2a_agent",AI21:"ai21",AI21_CHAT:"ai21_chat",AIML:"aiml",AIOHTTP_OPENAI:"aiohttp_openai",Anthropic:"anthropic",ANTHROPIC_TEXT:"anthropic_text",AssemblyAI:"assemblyai",AUTO_ROUTER:"auto_router",Azure:"azure",Azure_AI_Studio:"azure_ai",AZURE_TEXT:"azure_text",BASETEN:"baseten",Bedrock:"bedrock",BedrockMantle:"bedrock_mantle",BYTEZ:"bytez",Cerebras:"cerebras",CLARIFAI:"clarifai",CLOUDFLARE:"cloudflare",CODESTRAL:"codestral",Cohere:"cohere",COHERE_CHAT:"cohere_chat",COMETAPI:"cometapi",COMPACTIFAI:"compactifai",Cursor:"cursor",Dashscope:"dashscope",Databricks:"databricks",DATAROBOT:"datarobot",DeepInfra:"deepinfra",Deepgram:"deepgram",Deepseek:"deepseek",DOCKER_MODEL_RUNNER:"docker_model_runner",DOTPROMPT:"dotprompt",ElevenLabs:"elevenlabs",EMPOWER:"empower",FalAI:"fal_ai",FEATHERLESS_AI:"featherless_ai",FireworksAI:"fireworks_ai",FRIENDLIAI:"friendliai",GALADRIEL:"galadriel",GITHUB_COPILOT:"github_copilot",Google_AI_Studio:"gemini",GradientAI:"gradient_ai",Groq:"groq",HEROKU:"heroku",Hosted_Vllm:"hosted_vllm",HUGGINGFACE:"huggingface",HYPERBOLIC:"hyperbolic",Infinity:"infinity",JinaAI:"jina_ai",LAMBDA_AI:"lambda_ai",LEMONADE:"lemonade",LLAMAFILE:"llamafile",LLAMA:"meta_llama",LM_STUDIO:"lm_studio",MARITALK:"maritalk",MiniMax:"minimax",MistralAI:"mistral",MOONSHOT:"moonshot",MORPH:"morph",NEBIUS:"nebius",NLP_CLOUD:"nlp_cloud",NOVITA:"novita",NSCALE:"nscale",NVIDIA_NIM:"nvidia_nim",Ollama:"ollama",OLLAMA_CHAT:"ollama_chat",OOBABOOGA:"oobabooga",OpenAI:"openai",OPENAI_LIKE:"openai_like",OpenAI_Compatible:"openai",OpenAI_Text:"text-completion-openai",OpenAI_Text_Compatible:"text-completion-openai",Openrouter:"openrouter",Oracle:"oci",OVHCLOUD:"ovhcloud",Perplexity:"perplexity",PETALS:"petals",PG_VECTOR:"pg_vector",PREDIBASE:"predibase",RECRAFT:"recraft",REPLICATE:"replicate",RunwayML:"runwayml",SAGEMAKER_LEGACY:"sagemaker",SageMaker:"sagemaker_chat",Sambanova:"sambanova",SAP:"sap",Snowflake:"snowflake",Soniox:"soniox",TEXT_COMPLETION_CODESTRAL:"text-completion-codestral",TogetherAI:"together_ai",TOPAZ:"topaz",Triton:"triton",V0:"v0",VERCEL_AI_GATEWAY:"vercel_ai_gateway",Vertex_AI:"vertex_ai",VERTEX_AI_BETA:"vertex_ai_beta",VLLM:"vllm",VolcEngine:"volcengine",Voyage:"voyage",WANDB:"wandb",WATSONX:"watsonx",WATSONX_TEXT:"watsonx_text",xAI:"xai",XINFERENCE:"xinference",ZAI:"zai"},eg=new Set(["bedrock_mantle"]),ef={"A2A Agent":a.default.src,Ai21:l.default.src,"Ai21 Chat":l.default.src,"AI/ML API":A.default.src,"Aiohttp Openai":z.default.src,Anthropic:r.default.src,"Anthropic Text":r.default.src,AssemblyAI:s.default.src,Azure:D.default.src,"Azure AI Foundry (Studio)":D.default.src,"Azure Text":D.default.src,Baseten:o.default.src,"Amazon Bedrock":d.default.src,"Amazon Bedrock Mantle":d.default.src,"AWS SageMaker":d.default.src,Cerebras:u.default.src,Cloudflare:n.default.src,Codestral:S.default.src,Cohere:h.default.src,"Cohere Chat":h.default.src,Cometapi:c.default.src,Cursor:g.default.src,"Databricks (Qwen API)":f.default.src,Dashscope:j.src,Deepseek:b.default.src,Deepgram:m.default.src,DeepInfra:p.default.src,ElevenLabs:x.default.src,"Fal AI":I.default.src,"Featherless Ai":E.default.src,"Fireworks AI":C.default.src,Friendliai:O.default.src,"Github Copilot":w.default.src,"Google AI Studio":_.default.src,Groq:v.default.src,vllm:er.src,Huggingface:R.default.src,Hyperbolic:L.default.src,Infinity:B.default.src,"Jina AI":k.default.src,"Lambda Ai":T.default.src,"Lm Studio":H.default.src,"Meta Llama":U.default.src,MiniMax:M.default.src,"Mistral AI":S.default.src,Moonshot:y.default.src,Morph:q.default.src,Nebius:W.default.src,Novita:N.default.src,"Nvidia Nim":G.default.src,Ollama:Q.default.src,"Ollama Chat":Q.default.src,Oobabooga:z.default.src,OpenAI:z.default.src,"Openai Like":z.default.src,"OpenAI Text Completion":z.default.src,"OpenAI-Compatible Completions (legacy /v1/completions)":z.default.src,"OpenAI-Compatible Chat Completions (Together AI, vLLM, etc.)":z.default.src,Openrouter:F.src,"Oracle Cloud Infrastructure (OCI)":V.src,Perplexity:K.src,Recraft:Y.src,Replicate:J.src,RunwayML:Z.src,Sagemaker:d.default.src,Sambanova:X.src,"SAP Generative AI Hub":$.src,Snowflake:ee.src,Soniox:et.src,"Text-Completion-Codestral":S.default.src,TogetherAI:ei.src,Topaz:ea.src,Triton:P.default.src,V0:el.src,"Vercel Ai Gateway":eA.src,"Vertex AI (Anthropic, Gemini, etc.)":_.default.src,"Vertex Ai Beta":_.default.src,Vllm:er.src,VolcEngine:es.src,"Voyage AI":eo.src,Watsonx:ed.src,"Watsonx Text":ed.src,xAI:eu.src,Xinference:en.src};e.s(["Providers",()=>eh,"getPlaceholder",0,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)return"claude-3-opus";if("Amazon Bedrock"==e)return"claude-3-opus";if("AWS SageMaker"==e)return"sagemaker/jumpstart-dft-meta-textgeneration-llama-2-7b";else if("Google AI Studio"==e)return"gemini-pro";else if("Azure AI Foundry (Studio)"==e)return"azure_ai/command-r-plus";else if("Azure"==e)return"my-deployment";else if("Oracle Cloud Infrastructure (OCI)"==e)return"oci/xai.grok-4";else if("Snowflake"==e)return"snowflake/mistral-7b";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 if("Fal AI"==e)return"fal_ai/fal-ai/flux-pro/v1.1-ultra";else if("RunwayML"==e)return"runwayml/gen4_turbo";else if("Watsonx"===e)return"watsonx/ibm/granite-3-3-8b-instruct";else if("Cursor"===e)return"cursor/claude-4-sonnet";else if("Z.AI (Zhipu AI)"===e)return"zai/glm-4.5";else return"gpt-3.5-turbo"},"getProviderLogoAndName",0,e=>{if(!e)return{logo:"",displayName:"-"};if("gemini"===e.toLowerCase()){let e="Google AI Studio";return{logo:(0,i.resolveLogoSrc)(ef[e])??"",displayName:e}}let t=Object.keys(ec).find(t=>ec[t].toLowerCase()===e.toLowerCase())??Object.keys(ec).find(t=>t.toLowerCase()===e.toLowerCase());if(!t)return{logo:"",displayName:e};let a=eh[t];return{logo:(0,i.resolveLogoSrc)(ef[a])??"",displayName:a}},"getProviderModels",0,(e,t)=>{let i=ec[e],a=[];return e&&"object"==typeof t&&(Object.entries(t).forEach(([e,t])=>{if(null!==t&&"object"==typeof t&&"litellm_provider"in t){let l=t.litellm_provider,A="string"==typeof l&&(l.startsWith(`${i}_`)||l.startsWith(`${i}-`));(l===i||A&&!eg.has(l))&&a.push(e)}}),"Cohere"==e&&Object.entries(t).forEach(([e,t])=>{null!==t&&"object"==typeof t&&"litellm_provider"in t&&"cohere_chat"===t.litellm_provider&&a.push(e)}),"AWS SageMaker"==e&&Object.entries(t).forEach(([e,t])=>{null!==t&&"object"==typeof t&&"litellm_provider"in t&&"sagemaker_chat"===t.litellm_provider&&a.push(e)})),a},"providerLogoMap",0,ef,"provider_map",0,ec],916925)},174553,e=>{"use strict";var t=e.i(843476),i=e.i(271645),a=e.i(916925),l=e.i(555987);e.s(["Logo",0,({provider:e,src:A,label:r,className:s="w-4 h-4"})=>{let[o,d]=(0,i.useState)(null),u=void 0!==e?(0,a.getProviderLogoAndName)(e).logo:(0,l.resolveLogoSrc)(A)??"",n=r??e??"";return o!==u&&u?(0,t.jsx)("img",{src:u,alt:`${n||"-"} logo`,className:s,onError:()=>{console.warn(`Logo failed to load: ${u}`),d(u)}}):(0,t.jsx)("div",{className:`${s} rounded-full bg-gray-200 flex items-center justify-center text-xs`,children:n.charAt(0)||"-"})}])},695411,e=>{"use strict";var t=e.i(602869);let i=async e=>{try{let i=await (0,t.modelHubCall)(e);if(i?.data.length>0){let e=i.data.map(e=>({model_group:e.model_group,mode:e?.mode}));return e.sort((e,t)=>e.model_group.localeCompare(t.model_group)),e}return[]}catch(e){throw console.error("Error fetching model info:",e),e}};e.s(["fetchAvailableModels",0,i])},525720,e=>{"use strict";e.i(247167);var t=e.i(271645),i=e.i(343794),a=e.i(529681),l=e.i(908286),A=e.i(242064),r=e.i(246422),s=e.i(838378);let o=["wrap","nowrap","wrap-reverse"],d=["flex-start","flex-end","start","end","center","space-between","space-around","space-evenly","stretch","normal","left","right"],u=["center","start","end","flex-start","flex-end","self-start","self-end","baseline","normal","stretch"],n=function(e,t){let a,l,A;return(0,i.default)(Object.assign(Object.assign(Object.assign({},(a=!0===t.wrap?"wrap":t.wrap,{[`${e}-wrap-${a}`]:a&&o.includes(a)})),(l={},u.forEach(i=>{l[`${e}-align-${i}`]=t.align===i}),l[`${e}-align-stretch`]=!t.align&&!!t.vertical,l)),(A={},d.forEach(i=>{A[`${e}-justify-${i}`]=t.justify===i}),A)))},h=(0,r.genStyleHooks)("Flex",e=>{let{paddingXS:t,padding:i,paddingLG:a}=e,l=(0,s.mergeToken)(e,{flexGapSM:t,flexGap:i,flexGapLG:a});return[(e=>{let{componentCls:t}=e;return{[t]:{display:"flex",margin:0,padding:0,"&-vertical":{flexDirection:"column"},"&-rtl":{direction:"rtl"},"&:empty":{display:"none"}}}})(l),(e=>{let{componentCls:t}=e;return{[t]:{"&-gap-small":{gap:e.flexGapSM},"&-gap-middle":{gap:e.flexGap},"&-gap-large":{gap:e.flexGapLG}}}})(l),(e=>{let{componentCls:t}=e,i={};return o.forEach(e=>{i[`${t}-wrap-${e}`]={flexWrap:e}}),i})(l),(e=>{let{componentCls:t}=e,i={};return u.forEach(e=>{i[`${t}-align-${e}`]={alignItems:e}}),i})(l),(e=>{let{componentCls:t}=e,i={};return d.forEach(e=>{i[`${t}-justify-${e}`]={justifyContent:e}}),i})(l)]},()=>({}),{resetStyle:!1});var c=function(e,t){var i={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(i[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var l=0,a=Object.getOwnPropertySymbols(e);lt.indexOf(a[l])&&Object.prototype.propertyIsEnumerable.call(e,a[l])&&(i[a[l]]=e[a[l]]);return i};let g=t.default.forwardRef((e,r)=>{let{prefixCls:s,rootClassName:o,className:d,style:u,flex:g,gap:f,vertical:m=!1,component:p="div",children:b}=e,x=c(e,["prefixCls","rootClassName","className","style","flex","gap","vertical","component","children"]),{flex:I,direction:E,getPrefixCls:C}=t.default.useContext(A.ConfigContext),O=C("flex",s),[w,_,v]=h(O),R=null!=m?m:null==I?void 0:I.vertical,L=(0,i.default)(d,o,null==I?void 0:I.className,O,_,v,n(O,e),{[`${O}-rtl`]:"rtl"===E,[`${O}-gap-${f}`]:(0,l.isPresetSize)(f),[`${O}-vertical`]:R}),B=Object.assign(Object.assign({},null==I?void 0:I.style),u);return g&&(B.flex=g),f&&!(0,l.isPresetSize)(f)&&(B.gap=f),w(t.default.createElement(p,Object.assign({ref:r,className:L,style:B},(0,a.default)(x,["justify","wrap","align"])),b))});e.s(["Flex",0,g],525720)}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/06hxe45fjy7x7.js b/litellm/proxy/_experimental/out/_next/static/chunks/06hxe45fjy7x7.js new file mode 100644 index 00000000000..027449f8e8c --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/06hxe45fjy7x7.js @@ -0,0 +1,7 @@ +(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,196361,e=>{e.q("/litellm-asset-prefix/_next/static/media/arize.2q0zcoh7v2j00.png")},614148,e=>{e.q("/litellm-asset-prefix/_next/static/media/aws.2vuu_29f0wx7g.svg")},858236,e=>{e.q("/litellm-asset-prefix/_next/static/media/braintrust.1qnhppdggfxdj.png")},508296,e=>{e.q("/litellm-asset-prefix/_next/static/media/datadog.20j6djly_hrsx.png")},324755,e=>{e.q("/litellm-asset-prefix/_next/static/media/galileo.1jnyj81fv75mp.ico")},475151,e=>{e.q("/litellm-asset-prefix/_next/static/media/lago.146vobxeazdxy.svg")},274286,e=>{e.q("/litellm-asset-prefix/_next/static/media/langfuse.1y39530irujaj.png")},436494,e=>{e.q("/litellm-asset-prefix/_next/static/media/langsmith.0cuekyutow5l_.png")},204086,e=>{e.q("/litellm-asset-prefix/_next/static/media/openmeter.1wzo3xv7qwtb8.png")},531150,e=>{e.q("/litellm-asset-prefix/_next/static/media/otel.1dei3v2u03nit.png")},653496,e=>{"use strict";var t=e.i(721369);e.s(["Tabs",()=>t.default])},629569,e=>{"use strict";var t=e.i(290571),r=e.i(95779),a=e.i(444755),l=e.i(673706),o=e.i(271645);let n=o.default.forwardRef((e,n)=>{let{color:s,children:i,className:c}=e,d=(0,t.__rest)(e,["color","children","className"]);return o.default.createElement("p",Object.assign({ref:n,className:(0,a.tremorTwMerge)("font-medium text-tremor-title",s?(0,l.getColorClassNames)(s,r.colorPalette.darkText).textColor:"text-tremor-content-strong dark:text-dark-tremor-content-strong",c)},d),i)});n.displayName="Title",e.s(["Title",0,n],629569)},304967,e=>{"use strict";var t=e.i(290571),r=e.i(271645),a=e.i(480731),l=e.i(95779),o=e.i(444755),n=e.i(673706);let s=(0,n.makeClassName)("Card"),i=r.default.forwardRef((e,i)=>{let{decoration:c="",decorationColor:d,children:u,className:p}=e,f=(0,t.__rest)(e,["decoration","decorationColor","children","className"]);return r.default.createElement("div",Object.assign({ref:i,className:(0,o.tremorTwMerge)(s("root"),"relative w-full text-left ring-1 rounded-tremor-default p-6","bg-tremor-background ring-tremor-ring shadow-tremor-card","dark:bg-dark-tremor-background dark:ring-dark-tremor-ring dark:shadow-dark-tremor-card",d?(0,n.getColorClassNames)(d,l.colorPalette.border).borderColor:"border-tremor-brand dark:border-dark-tremor-brand",(e=>{if(!e)return"";switch(e){case a.HorizontalPositions.Left:return"border-l-4";case a.VerticalPositions.Top:return"border-t-4";case a.HorizontalPositions.Right:return"border-r-4";case a.VerticalPositions.Bottom:return"border-b-4";default:return""}})(c),p)},f),u)});i.displayName="Card",e.s(["Card",0,i],304967)},91874,681216,e=>{"use strict";var t=e.i(931067),r=e.i(209428),a=e.i(211577),l=e.i(392221),o=e.i(703923),n=e.i(343794),s=e.i(914949),i=e.i(271645),c=["prefixCls","className","style","checked","disabled","defaultChecked","type","title","onChange"],d=(0,i.forwardRef)(function(e,d){var u=e.prefixCls,p=void 0===u?"rc-checkbox":u,f=e.className,m=e.style,v=e.checked,b=e.disabled,h=e.defaultChecked,g=e.type,x=void 0===g?"checkbox":g,y=e.title,C=e.onChange,S=(0,o.default)(e,c),k=(0,i.useRef)(null),w=(0,i.useRef)(null),E=(0,s.default)(void 0!==h&&h,{value:v}),_=(0,l.default)(E,2),N=_[0],$=_[1];(0,i.useImperativeHandle)(d,function(){return{focus:function(e){var t;null==(t=k.current)||t.focus(e)},blur:function(){var e;null==(e=k.current)||e.blur()},input:k.current,nativeElement:w.current}});var j=(0,n.default)(p,f,(0,a.default)((0,a.default)({},"".concat(p,"-checked"),N),"".concat(p,"-disabled"),b));return i.createElement("span",{className:j,title:y,style:m,ref:w},i.createElement("input",(0,t.default)({},S,{className:"".concat(p,"-input"),ref:k,onChange:function(t){b||("checked"in e||$(t.target.checked),null==C||C({target:(0,r.default)((0,r.default)({},e),{},{type:x,checked:t.target.checked}),stopPropagation:function(){t.stopPropagation()},preventDefault:function(){t.preventDefault()},nativeEvent:t.nativeEvent}))},disabled:b,checked:!!N,type:x})),i.createElement("span",{className:"".concat(p,"-inner")}))});e.s(["default",0,d],91874);var u=e.i(963188);e.s(["default",0,function(e){let t=i.default.useRef(null),r=()=>{u.default.cancel(t.current),t.current=null};return[()=>{r(),t.current=(0,u.default)(()=>{t.current=null})},a=>{t.current&&(a.stopPropagation(),r()),null==e||e(a)}]}],681216)},374276,236836,e=>{"use strict";e.i(247167);var t=e.i(271645),r=e.i(343794),a=e.i(91874),l=e.i(611935),o=e.i(121872),n=e.i(26905),s=e.i(242064),i=e.i(937328),c=e.i(321883),d=e.i(62139);let u=t.default.createContext(null);e.i(296059);var p=e.i(915654),f=e.i(183293),m=e.i(246422),v=e.i(838378);function b(e,t){return(e=>{let{checkboxCls:t}=e,r=`${t}-wrapper`;return[{[`${t}-group`]:Object.assign(Object.assign({},(0,f.resetComponent)(e)),{display:"inline-flex",flexWrap:"wrap",columnGap:e.marginXS,[`> ${e.antCls}-row`]:{flex:1}}),[r]:Object.assign(Object.assign({},(0,f.resetComponent)(e)),{display:"inline-flex",alignItems:"baseline",cursor:"pointer","&:after":{display:"inline-block",width:0,overflow:"hidden",content:"'\\a0'"},[`& + ${r}`]:{marginInlineStart:0},[`&${r}-in-form-item`]:{'input[type="checkbox"]':{width:14,height:14}}}),[t]:Object.assign(Object.assign({},(0,f.resetComponent)(e)),{position:"relative",whiteSpace:"nowrap",lineHeight:1,cursor:"pointer",borderRadius:e.borderRadiusSM,alignSelf:"center",[`${t}-input`]:{position:"absolute",inset:0,zIndex:1,cursor:"pointer",opacity:0,margin:0,[`&:focus-visible + ${t}-inner`]:(0,f.genFocusOutline)(e)},[`${t}-inner`]:{boxSizing:"border-box",display:"block",width:e.checkboxSize,height:e.checkboxSize,direction:"ltr",backgroundColor:e.colorBgContainer,border:`${(0,p.unit)(e.lineWidth)} ${e.lineType} ${e.colorBorder}`,borderRadius:e.borderRadiusSM,borderCollapse:"separate",transition:`all ${e.motionDurationSlow}`,"&:after":{boxSizing:"border-box",position:"absolute",top:"50%",insetInlineStart:"25%",display:"table",width:e.calc(e.checkboxSize).div(14).mul(5).equal(),height:e.calc(e.checkboxSize).div(14).mul(8).equal(),border:`${(0,p.unit)(e.lineWidthBold)} solid ${e.colorWhite}`,borderTop:0,borderInlineStart:0,transform:"rotate(45deg) scale(0) translate(-50%,-50%)",opacity:0,content:'""',transition:`all ${e.motionDurationFast} ${e.motionEaseInBack}, opacity ${e.motionDurationFast}`}},"& + span":{paddingInlineStart:e.paddingXS,paddingInlineEnd:e.paddingXS}})},{[` + ${r}:not(${r}-disabled), + ${t}:not(${t}-disabled) + `]:{[`&:hover ${t}-inner`]:{borderColor:e.colorPrimary}},[`${r}:not(${r}-disabled)`]:{[`&:hover ${t}-checked:not(${t}-disabled) ${t}-inner`]:{backgroundColor:e.colorPrimaryHover,borderColor:"transparent"},[`&:hover ${t}-checked:not(${t}-disabled):after`]:{borderColor:e.colorPrimaryHover}}},{[`${t}-checked`]:{[`${t}-inner`]:{backgroundColor:e.colorPrimary,borderColor:e.colorPrimary,"&:after":{opacity:1,transform:"rotate(45deg) scale(1) translate(-50%,-50%)",transition:`all ${e.motionDurationMid} ${e.motionEaseOutBack} ${e.motionDurationFast}`}}},[` + ${r}-checked:not(${r}-disabled), + ${t}-checked:not(${t}-disabled) + `]:{[`&:hover ${t}-inner`]:{backgroundColor:e.colorPrimaryHover,borderColor:"transparent"}}},{[t]:{"&-indeterminate":{"&":{[`${t}-inner`]:{backgroundColor:`${e.colorBgContainer}`,borderColor:`${e.colorBorder}`,"&:after":{top:"50%",insetInlineStart:"50%",width:e.calc(e.fontSizeLG).div(2).equal(),height:e.calc(e.fontSizeLG).div(2).equal(),backgroundColor:e.colorPrimary,border:0,transform:"translate(-50%, -50%) scale(1)",opacity:1,content:'""'}},[`&:hover ${t}-inner`]:{backgroundColor:`${e.colorBgContainer}`,borderColor:`${e.colorPrimary}`}}}}},{[`${r}-disabled`]:{cursor:"not-allowed"},[`${t}-disabled`]:{[`&, ${t}-input`]:{cursor:"not-allowed",pointerEvents:"none"},[`${t}-inner`]:{background:e.colorBgContainerDisabled,borderColor:e.colorBorder,"&:after":{borderColor:e.colorTextDisabled}},"&:after":{display:"none"},"& + span":{color:e.colorTextDisabled},[`&${t}-indeterminate ${t}-inner::after`]:{background:e.colorTextDisabled}}}]})((0,v.mergeToken)(t,{checkboxCls:`.${e}`,checkboxSize:t.controlInteractiveSize}))}let h=(0,m.genStyleHooks)("Checkbox",(e,{prefixCls:t})=>[b(t,e)]);e.s(["default",0,h,"getStyle",0,b],236836);var g=e.i(681216),x=function(e,t){var r={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(r[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var l=0,a=Object.getOwnPropertySymbols(e);lt.indexOf(a[l])&&Object.prototype.propertyIsEnumerable.call(e,a[l])&&(r[a[l]]=e[a[l]]);return r};let y=t.forwardRef((e,p)=>{var f;let{prefixCls:m,className:v,rootClassName:b,children:y,indeterminate:C=!1,style:S,onMouseEnter:k,onMouseLeave:w,skipGroup:E=!1,disabled:_}=e,N=x(e,["prefixCls","className","rootClassName","children","indeterminate","style","onMouseEnter","onMouseLeave","skipGroup","disabled"]),{getPrefixCls:$,direction:j,checkbox:O}=t.useContext(s.ConfigContext),P=t.useContext(u),{isFormItemInput:R}=t.useContext(d.FormItemInputContext),M=t.useContext(i.default),T=null!=(f=(null==P?void 0:P.disabled)||_)?f:M,L=t.useRef(N.value),I=t.useRef(null),z=(0,l.composeRef)(p,I);t.useEffect(()=>{null==P||P.registerValue(N.value)},[]),t.useEffect(()=>{if(!E)return N.value!==L.current&&(null==P||P.cancelValue(L.current),null==P||P.registerValue(N.value),L.current=N.value),()=>null==P?void 0:P.cancelValue(N.value)},[N.value]),t.useEffect(()=>{var e;(null==(e=I.current)?void 0:e.input)&&(I.current.input.indeterminate=C)},[C]);let D=$("checkbox",m),A=(0,c.default)(D),[q,V,B]=h(D,A),G=Object.assign({},N);P&&!E&&(G.onChange=(...e)=>{N.onChange&&N.onChange.apply(N,e),P.toggleOption&&P.toggleOption({label:y,value:N.value})},G.name=P.name,G.checked=P.value.includes(N.value));let H=(0,r.default)(`${D}-wrapper`,{[`${D}-rtl`]:"rtl"===j,[`${D}-wrapper-checked`]:G.checked,[`${D}-wrapper-disabled`]:T,[`${D}-wrapper-in-form-item`]:R},null==O?void 0:O.className,v,b,B,A,V),K=(0,r.default)({[`${D}-indeterminate`]:C},n.TARGET_CLS,V),[W,F]=(0,g.default)(G.onClick);return q(t.createElement(o.default,{component:"Checkbox",disabled:T},t.createElement("label",{className:H,style:Object.assign(Object.assign({},null==O?void 0:O.style),S),onMouseEnter:k,onMouseLeave:w,onClick:W},t.createElement(a.default,Object.assign({},G,{onClick:F,prefixCls:D,className:K,disabled:T,ref:z})),null!=y&&t.createElement("span",{className:`${D}-label`},y))))});var C=e.i(8211),S=e.i(529681),k=function(e,t){var r={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(r[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var l=0,a=Object.getOwnPropertySymbols(e);lt.indexOf(a[l])&&Object.prototype.propertyIsEnumerable.call(e,a[l])&&(r[a[l]]=e[a[l]]);return r};let w=t.forwardRef((e,a)=>{let{defaultValue:l,children:o,options:n=[],prefixCls:i,className:d,rootClassName:p,style:f,onChange:m}=e,v=k(e,["defaultValue","children","options","prefixCls","className","rootClassName","style","onChange"]),{getPrefixCls:b,direction:g}=t.useContext(s.ConfigContext),[x,w]=t.useState(v.value||l||[]),[E,_]=t.useState([]);t.useEffect(()=>{"value"in v&&w(v.value||[])},[v.value]);let N=t.useMemo(()=>n.map(e=>"string"==typeof e||"number"==typeof e?{label:e,value:e}:e),[n]),$=e=>{_(t=>t.filter(t=>t!==e))},j=e=>{_(t=>[].concat((0,C.default)(t),[e]))},O=e=>{let t=x.indexOf(e.value),r=(0,C.default)(x);-1===t?r.push(e.value):r.splice(t,1),"value"in v||w(r),null==m||m(r.filter(e=>E.includes(e)).sort((e,t)=>N.findIndex(t=>t.value===e)-N.findIndex(e=>e.value===t)))},P=b("checkbox",i),R=`${P}-group`,M=(0,c.default)(P),[T,L,I]=h(P,M),z=(0,S.default)(v,["value","disabled"]),D=n.length?N.map(e=>t.createElement(y,{prefixCls:P,key:e.value.toString(),disabled:"disabled"in e?e.disabled:v.disabled,value:e.value,checked:x.includes(e.value),onChange:e.onChange,className:(0,r.default)(`${R}-item`,e.className),style:e.style,title:e.title,id:e.id,required:e.required},e.label)):o,A=t.useMemo(()=>({toggleOption:O,value:x,disabled:v.disabled,name:v.name,registerValue:j,cancelValue:$}),[O,x,v.disabled,v.name,j,$]),q=(0,r.default)(R,{[`${R}-rtl`]:"rtl"===g},d,p,I,M,L);return T(t.createElement("div",Object.assign({className:q,style:f},z,{ref:a}),t.createElement(u.Provider,{value:A},D)))});y.Group=w,y.__ANT_CHECKBOX=!0,e.s(["default",0,y],374276)},536916,e=>{"use strict";var t=e.i(374276);e.s(["Checkbox",()=>t.default])},250980,e=>{"use strict";var t=e.i(271645);let r=t.forwardRef(function(e,r){return t.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),t.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"}))});e.s(["PlusCircleIcon",0,r],250980)},983561,e=>{"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={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"};var l=e.i(9583),o=r.forwardRef(function(e,o){return r.createElement(l.default,(0,t.default)({},e,{ref:o,icon:a}))});e.s(["RobotOutlined",0,o],983561)},992619,e=>{"use strict";var t=e.i(843476),r=e.i(271645),a=e.i(779241),l=e.i(599724),o=e.i(199133),n=e.i(983561),s=e.i(343488),i=e.i(695411);e.s(["default",0,({accessToken:e,value:c,placeholder:d="Select a Model",onChange:u,disabled:p=!1,style:f,className:m,showLabel:v=!0,labelText:b="Select Model"})=>{let[h,g]=(0,r.useState)(c),[x,y]=(0,r.useState)(!1),[C,S]=(0,r.useState)([]);(0,r.useEffect)(()=>{g(c)},[c]),(0,r.useEffect)(()=>{e&&(async()=>{try{let t=await (0,i.fetchAvailableModels)(e);t.length>0&&S(t)}catch(e){console.error("Error fetching model info:",e)}})()},[e]);let k=(0,s.useDebouncedCallback)(e=>{g(e),u?.(e)},{wait:500});return(0,t.jsxs)("div",{children:[v&&(0,t.jsxs)(l.Text,{className:"font-medium block mb-2 text-gray-700 flex items-center",children:[(0,t.jsx)(n.RobotOutlined,{className:"mr-2"})," ",b]}),(0,t.jsx)(o.Select,{value:h,placeholder:d,onChange:e=>{"custom"===e?(y(!0),g(void 0)):(y(!1),g(e),u&&u(e))},options:[...Array.from(new Set(C.map(e=>e.model_group))).map((e,t)=>({value:e,label:e,key:t})),{value:"custom",label:"Enter custom model",key:"custom"}],style:{width:"100%",...f},showSearch:!0,className:`rounded-md ${m||""}`,disabled:p}),x&&(0,t.jsx)(a.TextInput,{className:"mt-2",placeholder:"Enter custom model name",onValueChange:k,disabled:p})]})}])},797672,e=>{"use strict";var t=e.i(271645);let r=t.forwardRef(function(e,r){return t.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),t.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"}))});e.s(["PencilIcon",0,r],797672)},916940,e=>{"use strict";var t=e.i(843476),r=e.i(271645),a=e.i(199133),l=e.i(602869);e.s(["default",0,({onChange:e,value:o,className:n,accessToken:s,placeholder:i="Select vector stores",disabled:c=!1})=>{let[d,u]=(0,r.useState)([]),[p,f]=(0,r.useState)(!1);return(0,r.useEffect)(()=>{(async()=>{if(s){f(!0);try{let e=await (0,l.vectorStoreListCall)(s);e.data&&u(e.data)}catch(e){console.error("Error fetching vector stores:",e)}finally{f(!1)}}})()},[s]),(0,t.jsx)("div",{children:(0,t.jsx)(a.Select,{mode:"multiple",placeholder:i,onChange:e,value:o,loading:p,className:n,allowClear:!0,options:d.map(e=>({label:`${e.vector_store_name||e.vector_store_id} (${e.vector_store_id})`,value:e.vector_store_id,title:e.vector_store_description||e.vector_store_id})),optionFilterProp:"label",showSearch:!0,style:{width:"100%"},disabled:c})})}])},75921,e=>{"use strict";var t=e.i(843476),r=e.i(266027),a=e.i(243652),l=e.i(602869),o=e.i(135214);let n=(0,a.createQueryKeys)("mcpAccessGroups");var s=e.i(500727),i=e.i(699857),c=e.i(199133),d=e.i(234713);let u="toolset:";e.s(["default",0,({onChange:e,value:a,className:p,accessToken:f,placeholder:m="Select MCP servers",disabled:v=!1,teamId:b,allowNoMcpServers:h=!1,allowAllProxyMcpServers:g=!1})=>{let{data:x=[],isLoading:y}=(0,s.useMCPServers)(b),{data:C=[],isLoading:S}=(()=>{let{accessToken:e}=(0,o.default)();return(0,r.useQuery)({queryKey:n.list({}),queryFn:async()=>await (0,l.fetchMCPAccessGroups)(e),enabled:!!e})})(),{data:k=[],isLoading:w}=(0,i.useMCPToolsets)(),E=new Set(C),_=[...C.map(e=>({label:e,value:e,type:"accessGroup",searchText:`${e} Access Group`})),...x.map(e=>({label:`${e.server_name||e.server_id} (${e.server_id})`,value:e.server_id,type:"server",searchText:`${e.server_name||e.server_id} ${e.server_id} MCP Server`})),...k.map(e=>({label:e.toolset_name,value:`${u}${e.toolset_id}`,type:"toolset",searchText:`${e.toolset_name} ${e.toolset_id} Toolset`}))],N={accessGroup:"#52c41a",server:"#1890ff",toolset:"#722ed1"},$={accessGroup:"Access Group",server:"MCP Server",toolset:"Toolset"},j=[...a?.servers||[],...a?.accessGroups||[],...(a?.toolsets||[]).map(e=>`${u}${e}`)],O=h&&j.includes(d.NO_MCP_SERVERS_SENTINEL),P=j.includes(d.ALL_PROXY_MCP_SERVERS_SENTINEL);return(0,t.jsx)("div",{children:(0,t.jsxs)(c.Select,{mode:"multiple",placeholder:m,onChange:t=>{if(g&&t.includes(d.ALL_PROXY_MCP_SERVERS_SENTINEL))return void e({servers:[d.ALL_PROXY_MCP_SERVERS_SENTINEL],accessGroups:[],toolsets:[]});if(h&&t.includes(d.NO_MCP_SERVERS_SENTINEL))return void e({servers:[d.NO_MCP_SERVERS_SENTINEL],accessGroups:[],toolsets:[]});let r=t.filter(e=>e.startsWith(u)).map(e=>e.slice(u.length)),a=t.filter(e=>!e.startsWith(u));e({servers:a.filter(e=>!E.has(e)),accessGroups:a.filter(e=>E.has(e)),toolsets:r})},value:j,loading:y||S||w,className:p,allowClear:!0,showSearch:!0,style:{width:"100%"},disabled:v,filterOption:(e,t)=>t?.value===d.NO_MCP_SERVERS_SENTINEL||t?.value===d.ALL_PROXY_MCP_SERVERS_SENTINEL||(_.find(e=>e.value===t?.value)?.searchText||"").toLowerCase().includes(e.toLowerCase()),children:[(g||P)&&(0,t.jsx)(c.Select.Option,{value:d.ALL_PROXY_MCP_SERVERS_SENTINEL,label:"All Proxy MCP Servers",children:(0,t.jsx)("span",{style:{color:"#1890ff",fontWeight:500},children:"All Proxy MCP Servers"})},d.ALL_PROXY_MCP_SERVERS_SENTINEL),h&&(0,t.jsx)(c.Select.Option,{value:d.NO_MCP_SERVERS_SENTINEL,label:"No MCP Servers",children:(0,t.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:"8px"},children:[(0,t.jsx)("span",{style:{flex:1},children:"No MCP Servers"}),(0,t.jsx)("span",{style:{color:"#8c8c8c",fontSize:"12px",fontWeight:500,opacity:.8},children:"Block all"})]})},d.NO_MCP_SERVERS_SENTINEL),_.map(e=>(0,t.jsx)(c.Select.Option,{value:e.value,label:e.label,disabled:O||P,children:(0,t.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:"8px"},children:[(0,t.jsx)("span",{style:{display:"inline-block",width:8,height:8,borderRadius:"50%",background:N[e.type],flexShrink:0}}),(0,t.jsx)("span",{style:{flex:1},children:e.label}),(0,t.jsx)("span",{style:{color:N[e.type],fontSize:"12px",fontWeight:500,opacity:.8},children:$[e.type]})]})},e.value))]})})}],75921)},213205,e=>{"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M678.3 642.4c24.2-13 51.9-20.4 81.4-20.4h.1c3 0 4.4-3.6 2.2-5.6a371.67 371.67 0 00-103.7-65.8c-.4-.2-.8-.3-1.2-.5C719.2 505 759.6 431.7 759.6 349c0-137-110.8-248-247.5-248S264.7 212 264.7 349c0 82.7 40.4 156 102.6 201.1-.4.2-.8.3-1.2.5-44.7 18.9-84.8 46-119.3 80.6a373.42 373.42 0 00-80.4 119.5A373.6 373.6 0 00137 888.8a8 8 0 008 8.2h59.9c4.3 0 7.9-3.5 8-7.8 2-77.2 32.9-149.5 87.6-204.3C357 628.2 432.2 597 512.2 597c56.7 0 111.1 15.7 158 45.1a8.1 8.1 0 008.1.3zM512.2 521c-45.8 0-88.9-17.9-121.4-50.4A171.2 171.2 0 01340.5 349c0-45.9 17.9-89.1 50.3-121.6S466.3 177 512.2 177s88.9 17.9 121.4 50.4A171.2 171.2 0 01683.9 349c0 45.9-17.9 89.1-50.3 121.6C601.1 503.1 558 521 512.2 521zM880 759h-84v-84c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v84h-84c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h84v84c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-84h84c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z"}}]},name:"user-add",theme:"outlined"};var l=e.i(9583),o=r.forwardRef(function(e,o){return r.createElement(l.default,(0,t.default)({},e,{ref:o,icon:a}))});e.s(["UserAddOutlined",0,o],213205)},435451,e=>{"use strict";var t=e.i(843476),r=e.i(290571),a=e.i(271645);let l=e=>{var t=(0,r.__rest)(e,[]);return a.default.createElement("svg",Object.assign({},t,{xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:"2.5"}),a.default.createElement("path",{d:"M12 4v16m8-8H4"}))},o=e=>{var t=(0,r.__rest)(e,[]);return a.default.createElement("svg",Object.assign({},t,{xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:"2.5"}),a.default.createElement("path",{d:"M20 12H4"}))};var n=e.i(444755),s=e.i(673706),i=e.i(677955);let c="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",u=a.default.forwardRef((e,t)=>{let{onSubmit:u,enableStepper:p=!0,disabled:f,onValueChange:m,onChange:v}=e,b=(0,r.__rest)(e,["onSubmit","enableStepper","disabled","onValueChange","onChange"]),h=(0,a.useRef)(null),[g,x]=a.default.useState(!1),y=a.default.useCallback(()=>{x(!0)},[]),C=a.default.useCallback(()=>{x(!1)},[]),[S,k]=a.default.useState(!1),w=a.default.useCallback(()=>{k(!0)},[]),E=a.default.useCallback(()=>{k(!1)},[]);return a.default.createElement(i.default,Object.assign({type:"number",ref:(0,s.mergeRefs)([h,t]),disabled:f,makeInputClassName:(0,s.makeClassName)("NumberInput"),onKeyDown:e=>{var t;if("Enter"===e.key&&!e.ctrlKey&&!e.altKey&&!e.shiftKey){let e=null==(t=h.current)?void 0:t.value;null==u||u(parseFloat(null!=e?e:""))}"ArrowDown"===e.key&&y(),"ArrowUp"===e.key&&w()},onKeyUp:e=>{"ArrowDown"===e.key&&C(),"ArrowUp"===e.key&&E()},onChange:e=>{f||(null==m||m(parseFloat(e.target.value)),null==v||v(e))},stepper:p?a.default.createElement("div",{className:(0,n.tremorTwMerge)("flex justify-center align-middle")},a.default.createElement("div",{tabIndex:-1,onClick:e=>e.preventDefault(),onMouseDown:e=>e.preventDefault(),onTouchStart:e=>{e.cancelable&&e.preventDefault()},onMouseUp:()=>{var e,t;f||(null==(e=h.current)||e.stepDown(),null==(t=h.current)||t.dispatchEvent(new Event("input",{bubbles:!0})))},className:(0,n.tremorTwMerge)(!f&&d,c,"group py-[10px] px-2.5 border-l border-tremor-border dark:border-dark-tremor-border")},a.default.createElement(o,{"data-testid":"step-down",className:(g?"scale-95":"")+" h-4 w-4 duration-75 transition group-active:scale-95"})),a.default.createElement("div",{tabIndex:-1,onClick:e=>e.preventDefault(),onMouseDown:e=>e.preventDefault(),onTouchStart:e=>{e.cancelable&&e.preventDefault()},onMouseUp:()=>{var e,t;f||(null==(e=h.current)||e.stepUp(),null==(t=h.current)||t.dispatchEvent(new Event("input",{bubbles:!0})))},className:(0,n.tremorTwMerge)(!f&&d,c,"group py-[10px] px-2.5 border-l border-tremor-border dark:border-dark-tremor-border")},a.default.createElement(l,{"data-testid":"step-up",className:(S?"scale-95":"")+" h-4 w-4 duration-75 transition group-active:scale-95"}))):null},b))});u.displayName="NumberInput",e.s(["default",0,({step:e=.01,style:r={width:"100%"},placeholder:a="Enter a numerical value",min:l,max:o,onChange:n,...s})=>(0,t.jsx)(u,{onWheel:e=>e.currentTarget.blur(),step:e,style:r,placeholder:a,min:l,max:o,onChange:n,...s})],435451)},860585,e=>{"use strict";var t=e.i(843476),r=e.i(199133);let{Option:a}=r.Select;e.s(["default",0,({value:e,onChange:l,className:o="",style:n={}})=>(0,t.jsxs)(r.Select,{style:{width:"100%",...n},value:e||void 0,onChange:l,className:o,placeholder:"n/a",allowClear:!0,children:[(0,t.jsx)(a,{value:"1h",children:"hourly"}),(0,t.jsx)(a,{value:"24h",children:"daily"}),(0,t.jsx)(a,{value:"7d",children:"weekly"}),(0,t.jsx)(a,{value:"30d",children:"monthly"})]}),"getBudgetDurationLabel",0,e=>e?({"1h":"hourly","24h":"daily","7d":"weekly","30d":"monthly"})[e]||e:"Not set"])},343488,e=>{"use strict";var t=e.i(540626),r=e.i(271645);e.s(["useDebouncedCallback",0,function(e,a){let l=(0,t.useDebouncer)(e,a).maybeExecute;return(0,r.useCallback)((...e)=>l(...e),[l])}])},981339,e=>{"use strict";var t=e.i(185793);e.s(["Skeleton",()=>t.default])},500727,e=>{"use strict";var t=e.i(266027),r=e.i(243652),a=e.i(602869),l=e.i(135214);let o=(0,r.createQueryKeys)("mcpServers");e.s(["useMCPServers",0,e=>{let{accessToken:r}=(0,l.default)();return(0,t.useQuery)({queryKey:o.list(e?{filters:{teamId:e}}:void 0),queryFn:async()=>await (0,a.fetchMCPServers)(r,e),enabled:!!r})}])},699857,e=>{"use strict";var t=e.i(266027),r=e.i(243652),a=e.i(602869),l=e.i(135214);let o=(0,r.createQueryKeys)("mcpToolsets");e.s(["useMCPToolsets",0,()=>{let{accessToken:e}=(0,l.default)();return(0,t.useQuery)({queryKey:o.list(),queryFn:async()=>await (0,a.fetchMCPToolsets)(e),enabled:!!e})}])}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/06q8aep867ss7.js b/litellm/proxy/_experimental/out/_next/static/chunks/06q8aep867ss7.js deleted file mode 100644 index 251a9ba7430..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/06q8aep867ss7.js +++ /dev/null @@ -1 +0,0 @@ -(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,515288,e=>{"use strict";var t=e.i(843476),r=e.i(271645),a=e.i(115504);let n=r.forwardRef(({className:e,size:r="default",...n},o)=>(0,t.jsx)("div",{ref:o,"data-slot":"card","data-size":r,className:(0,a.cn)("group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground shadow-xs ring-1 ring-foreground/10 [--card-spacing:--spacing(6)] has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(4)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",e),...n}));n.displayName="Card";let o=r.forwardRef(({className:e,...r},n)=>(0,t.jsx)("div",{ref:n,"data-slot":"card-header",className:(0,a.cn)("group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",e),...r}));o.displayName="CardHeader";let s=r.forwardRef(({className:e,...r},n)=>(0,t.jsx)("div",{ref:n,"data-slot":"card-title",className:(0,a.cn)("text-base leading-normal font-medium group-data-[size=sm]/card:text-sm",e),...r}));s.displayName="CardTitle";let i=r.forwardRef(({className:e,...r},n)=>(0,t.jsx)("div",{ref:n,"data-slot":"card-description",className:(0,a.cn)("text-sm text-muted-foreground",e),...r}));i.displayName="CardDescription";let l=r.forwardRef(({className:e,...r},n)=>(0,t.jsx)("div",{ref:n,"data-slot":"card-action",className:(0,a.cn)("col-start-2 row-span-2 row-start-1 self-start justify-self-end",e),...r}));l.displayName="CardAction";let d=r.forwardRef(({className:e,...r},n)=>(0,t.jsx)("div",{ref:n,"data-slot":"card-content",className:(0,a.cn)("px-(--card-spacing)",e),...r}));d.displayName="CardContent";let u=r.forwardRef(({className:e,...r},n)=>(0,t.jsx)("div",{ref:n,"data-slot":"card-footer",className:(0,a.cn)("flex items-center rounded-b-xl px-(--card-spacing) [.border-t]:pt-(--card-spacing)",e),...r}));u.displayName="CardFooter",e.s(["Card",0,n,"CardAction",0,l,"CardContent",0,d,"CardDescription",0,i,"CardFooter",0,u,"CardHeader",0,o,"CardTitle",0,s])},727612,e=>{"use strict";let t=(0,e.i(475254).default)("trash-2",[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}],["line",{x1:"10",x2:"10",y1:"11",y2:"17",key:"1uufr5"}],["line",{x1:"14",x2:"14",y1:"11",y2:"17",key:"xtxkd"}]]);e.s(["Trash2",0,t],727612)},541071,373488,e=>{"use strict";let t=(0,e.i(475254).default)("ellipsis",[["circle",{cx:"12",cy:"12",r:"1",key:"41hilf"}],["circle",{cx:"19",cy:"12",r:"1",key:"1wjl8i"}],["circle",{cx:"5",cy:"12",r:"1",key:"1pcz8c"}]]);e.s(["default",0,t],373488),e.s(["MoreHorizontal",0,t],541071)},755146,e=>{"use strict";var t=e.i(843476),r=e.i(451512),a=e.i(115504);e.i(233565),e.i(678784),e.s(["DropdownMenu",0,function({...e}){return(0,t.jsx)(r.Menu.Root,{"data-slot":"dropdown-menu",...e})},"DropdownMenuContent",0,function({align:e="start",alignOffset:n=0,side:o="bottom",sideOffset:s=4,className:i,...l}){return(0,t.jsx)(r.Menu.Portal,{children:(0,t.jsx)(r.Menu.Positioner,{className:"isolate z-50 outline-none",align:e,alignOffset:n,side:o,sideOffset:s,children:(0,t.jsx)(r.Menu.Popup,{"data-slot":"dropdown-menu-content",className:(0,a.cn)("z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-md bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95",i),...l})})})},"DropdownMenuItem",0,function({className:e,inset:n,variant:o="default",...s}){return(0,t.jsx)(r.Menu.Item,{"data-slot":"dropdown-menu-item","data-inset":n,"data-variant":o,className:(0,a.cn)("group/dropdown-menu-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",e),...s})},"DropdownMenuSeparator",0,function({className:e,...n}){return(0,t.jsx)(r.Menu.Separator,{"data-slot":"dropdown-menu-separator",className:(0,a.cn)("-mx-1 my-1 h-px bg-border",e),...n})},"DropdownMenuTrigger",0,function({...e}){return(0,t.jsx)(r.Menu.Trigger,{"data-slot":"dropdown-menu-trigger",...e})}])},788699,e=>{"use strict";let t=(0,e.i(475254).default)("pencil",[["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",key:"1a8usu"}],["path",{d:"m15 5 4 4",key:"1mk7zo"}]]);e.s(["Pencil",0,t],788699)},879664,e=>{"use strict";let t=(0,e.i(475254).default)("info",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 16v-4",key:"1dtifu"}],["path",{d:"M12 8h.01",key:"e9boi3"}]]);e.s(["default",0,t])},952571,e=>{"use strict";var t=e.i(879664);e.s(["Info",()=>t.default])},888288,e=>{"use strict";var t=e.i(271645);e.s(["default",0,(e,r)=>{let a=void 0!==r,[n,o]=(0,t.useState)(e);return[a?r:n,e=>{a||o(e)}]}])},118366,e=>{"use strict";var t=e.i(991124);e.s(["CopyIcon",()=>t.default])},768371,e=>{"use strict";let t,r;var a=e.i(247167);let n=/\{[^{}]+\}/g;function o(e,t,r){if(null==t)return"";if("object"==typeof t)throw Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");return`${e}=${r?.allowReserved===!0?t:encodeURIComponent(t)}`}function s(e,t,r){if(!t||"object"!=typeof t)return"";let a=[],n={simple:",",label:".",matrix:";"}[r.style]||"&";if("deepObject"!==r.style&&!1===r.explode){for(let e in t)a.push(e,!0===r.allowReserved?t[e]:encodeURIComponent(t[e]));let n=a.join(",");switch(r.style){case"form":return`${e}=${n}`;case"label":return`.${n}`;case"matrix":return`;${e}=${n}`;default:return n}}for(let n in t){let s="deepObject"===r.style?`${e}[${n}]`:n;a.push(o(s,t[n],r))}let s=a.join(n);return"label"===r.style||"matrix"===r.style?`${n}${s}`:s}function i(e,t,r){if(!Array.isArray(t))return"";if(!1===r.explode){let a={form:",",spaceDelimited:"%20",pipeDelimited:"|"}[r.style]||",",n=(!0===r.allowReserved?t:t.map(e=>encodeURIComponent(e))).join(a);switch(r.style){case"simple":return n;case"label":return`.${n}`;case"matrix":return`;${e}=${n}`;default:return`${e}=${n}`}}let a={simple:",",label:".",matrix:";"}[r.style]||"&",n=[];for(let a of t)"simple"===r.style||"label"===r.style?n.push(!0===r.allowReserved?a:encodeURIComponent(a)):n.push(o(e,a,r));return"label"===r.style||"matrix"===r.style?`${a}${n.join(a)}`:n.join(a)}function l(e){return function(t){let r=[];if(t&&"object"==typeof t)for(let a in t){let n=t[a];if(null!=n){if(Array.isArray(n)){if(0===n.length)continue;r.push(i(a,n,{style:"form",explode:!0,...e?.array,allowReserved:e?.allowReserved||!1}));continue}if("object"==typeof n){r.push(s(a,n,{style:"deepObject",explode:!0,...e?.object,allowReserved:e?.allowReserved||!1}));continue}r.push(o(a,n,e))}}return r.join("&")}}function d(e,t){let r=e;for(let a of e.match(n)??[]){let e=a.substring(1,a.length-1),n=!1,l="simple";if(e.endsWith("*")&&(n=!0,e=e.substring(0,e.length-1)),e.startsWith(".")?(l="label",e=e.substring(1)):e.startsWith(";")&&(l="matrix",e=e.substring(1)),!t||void 0===t[e]||null===t[e])continue;let d=t[e];if(Array.isArray(d)){r=r.replace(a,i(e,d,{style:l,explode:n}));continue}if("object"==typeof d){r=r.replace(a,s(e,d,{style:l,explode:n}));continue}if("matrix"===l){r=r.replace(a,`;${o(e,d)}`);continue}r=r.replace(a,"label"===l?`.${encodeURIComponent(d)}`:encodeURIComponent(d))}return r}function u(e,t){return e instanceof FormData?e:t&&"application/x-www-form-urlencoded"===(t.get instanceof Function?t.get("Content-Type")??t.get("content-type"):t["Content-Type"]??t["content-type"])?new URLSearchParams(e).toString():JSON.stringify(e)}function c(...e){let t=new Headers;for(let r of e)if(r&&"object"==typeof r)for(let[e,a]of r instanceof Headers?r.entries():Object.entries(r))if(null===a)t.delete(e);else if(Array.isArray(a))for(let r of a)t.append(e,r);else void 0!==a&&t.set(e,a);return t}function p(e){return e.endsWith("/")?e.substring(0,e.length-1):e}var f=e.i(954616),m=e.i(621482),h=e.i(869230),g=e.i(469637),b=e.i(254440),x=e.i(266027),v=e.i(431703),y=e.i(97198);let w=async(e,t)=>new Request(t,{method:e.method,headers:e.headers,body:e.body?await e.arrayBuffer():void 0,mode:e.mode,credentials:e.credentials,cache:e.cache,redirect:e.redirect,referrer:e.referrer,referrerPolicy:e.referrerPolicy,integrity:e.integrity,keepalive:e.keepalive,signal:e.signal}),j=function(e){let{baseUrl:t="",Request:r=globalThis.Request,fetch:n=globalThis.fetch,querySerializer:o,bodySerializer:s,pathSerializer:i,headers:f,requestInitExt:m,...h}={...e};m="object"==typeof a.default&&Number.parseInt(a.default?.versions?.node?.substring(0,2))>=18&&a.default.versions.undici?m:void 0,t=p(t);let g=[];async function b(e,a){var b,x;let v,y,w,j,C,{baseUrl:k,fetch:N=n,Request:R=r,headers:T,params:E={},parseAs:M="json",querySerializer:S,bodySerializer:z=s??u,pathSerializer:I,body:O,middleware:$=[],...A}=a||{},q=t;k&&(q=p(k)??t);let P="function"==typeof o?o:l(o);S&&(P="function"==typeof S?S:l({..."object"==typeof o?o:{},...S}));let U=I||i||d,D=void 0===O?void 0:z(O,c(f,T,E.header)),L=c(void 0===D||D instanceof FormData?{}:{"Content-Type":"application/json"},f,T,E.header),H=[...g,...$],V={redirect:"follow",...h,...A,body:D,headers:L},_=new R((b=e,x={baseUrl:q,params:E,querySerializer:P,pathSerializer:U},v=`${x.baseUrl}${b}`,x.params?.path&&(v=x.pathSerializer(v,x.params.path)),(y=x.querySerializer(x.params.query??{})).startsWith("?")&&(y=y.substring(1)),y&&(v+=`?${y}`),v),V);for(let e in A)e in _||(_[e]=A[e]);if(H.length){for(let t of(w=Math.random().toString(36).slice(2,11),j=Object.freeze({baseUrl:q,fetch:N,parseAs:M,querySerializer:P,bodySerializer:z,pathSerializer:U}),H))if(t&&"object"==typeof t&&"function"==typeof t.onRequest){let r=await t.onRequest({request:_,schemaPath:e,params:E,options:j,id:w});if(r)if(r instanceof R)_=r;else if(r instanceof Response){C=r;break}else throw Error("onRequest: must return new Request() or Response() when modifying the request")}}if(!C){try{C=await N(_,m)}catch(r){let t=r;if(H.length)for(let r=H.length-1;r>=0;r--){let a=H[r];if(a&&"object"==typeof a&&"function"==typeof a.onError){let r=await a.onError({request:_,error:t,schemaPath:e,params:E,options:j,id:w});if(r){if(r instanceof Response){t=void 0,C=r;break}if(r instanceof Error){t=r;continue}throw Error("onError: must return new Response() or instance of Error")}}}if(t)throw t}if(H.length)for(let t=H.length-1;t>=0;t--){let r=H[t];if(r&&"object"==typeof r&&"function"==typeof r.onResponse){let t=await r.onResponse({request:_,response:C,schemaPath:e,params:E,options:j,id:w});if(t){if(!(t instanceof Response))throw Error("onResponse: must return new Response() when modifying the response");C=t}}}}let B=C.headers.get("Content-Length");if(204===C.status||"HEAD"===_.method||"0"===B&&!C.headers.get("Transfer-Encoding")?.includes("chunked"))return C.ok?{data:void 0,response:C}:{error:void 0,response:C};if(C.ok){let e=async()=>{if("stream"===M)return C.body;if("json"===M&&!B){let e=await C.text();return e?JSON.parse(e):void 0}return await C[M]()};return{data:await e(),response:C}}let G=await C.text();try{G=JSON.parse(G)}catch{}return{error:G,response:C}}return{request:(e,t,r)=>b(t,{...r,method:e.toUpperCase()}),GET:(e,t)=>b(e,{...t,method:"GET"}),PUT:(e,t)=>b(e,{...t,method:"PUT"}),POST:(e,t)=>b(e,{...t,method:"POST"}),DELETE:(e,t)=>b(e,{...t,method:"DELETE"}),OPTIONS:(e,t)=>b(e,{...t,method:"OPTIONS"}),HEAD:(e,t)=>b(e,{...t,method:"HEAD"}),PATCH:(e,t)=>b(e,{...t,method:"PATCH"}),TRACE:(e,t)=>b(e,{...t,method:"TRACE"}),use(...e){for(let t of e)if(t){if("object"!=typeof t||!("onRequest"in t||"onResponse"in t||"onError"in t))throw Error("Middleware must be an object with one of `onRequest()`, `onResponse() or `onError()`");g.push(t)}},eject(...e){for(let t of e){let e=g.indexOf(t);-1!==e&&g.splice(e,1)}}}}({baseUrl:globalThis.location?.origin??""});j.use({async onRequest({request:e}){let t=(0,y.getRequestBaseUrl)(),r=t?await w(e,((e,t)=>{let{pathname:r,search:a}=new URL(e);return`${t.replace(/\/+$/,"")}${r}${a}`})(e.url,t)):e,a=(0,y.getAuthToken)();return a&&r.headers.set((0,y.getAuthHeaderName)(),`Bearer ${a}`),r},async onResponse({response:e}){let t;if(e.ok)return e;let r=await e.clone().text(),a=r;try{a=JSON.parse(r),t=(0,v.deriveErrorMessage)(a)}catch{t=r||`HTTP ${e.status}`}throw(0,y.reportError)(t),new v.ApiError(t,e.status,a)}});let C=(t=async({queryKey:[e,t,r],signal:a})=>{let n=j[e.toUpperCase()],{data:o,error:s,response:i}=await n(t,{signal:a,...r});if(s)throw s;return 204===i.status||"0"===i.headers.get("Content-Length")?o??null:o},{queryOptions:r=(e,r,...[a,n])=>({queryKey:void 0===a?[e,r]:[e,r,a],queryFn:t,...n}),useQuery:(e,t,...[a,n,o])=>(0,x.useQuery)(r(e,t,a,n),o),useSuspenseQuery:(e,t,...[a,n,o])=>{var s;return s=r(e,t,a,n),(0,g.useBaseQuery)({...s,enabled:!0,suspense:!0,throwOnError:b.defaultThrowOnError,placeholderData:void 0},h.QueryObserver,o)},useInfiniteQuery:(e,t,a,n,o)=>{let{pageParamName:s="cursor",...i}=n,{queryKey:l}=r(e,t,a);return(0,m.useInfiniteQuery)({queryKey:l,queryFn:async({queryKey:[e,t,r],pageParam:a=0,signal:n})=>{let o=j[e.toUpperCase()],i={...r,signal:n,params:{...r?.params||{},query:{...r?.params?.query,[s]:a}}},{data:l,error:d}=await o(t,i);if(d)throw d;return l},...i},o)},useMutation:(e,t,r,a)=>(0,f.useMutation)({mutationKey:[e,t],mutationFn:async r=>{let a=j[e.toUpperCase()],{data:n,error:o}=await a(t,r);if(o)throw o;return n},...r},a)});e.s(["$api",0,C,"fetchClient",0,j],768371)},738014,e=>{"use strict";var t=e.i(135214),r=e.i(602869),a=e.i(266027);let n=(0,e.i(243652).createQueryKeys)("users");e.s(["useCurrentUser",0,()=>{let{accessToken:e,userId:o}=(0,t.default)();return(0,a.useQuery)({queryKey:n.detail(o),queryFn:async()=>await (0,r.userGetInfoV2)(e),enabled:!!(e&&o)})}])},162386,e=>{"use strict";var t=e.i(843476),r=e.i(625901),a=e.i(109799),n=e.i(785242),o=e.i(738014),s=e.i(199133),i=e.i(981339),l=e.i(592968);let d={label:"All Proxy Models",value:"all-proxy-models"},u={label:"No Default Models",value:"no-default-models"},c=[d,u],p={user:({allProxyModels:e,userModels:t,options:r})=>t&&r?.includeUserModels?t:[],team:({allProxyModels:e,selectedOrganization:t,userModels:r})=>t?t.models.includes(d.value)||0===t.models.length?e:e.filter(e=>t.models.includes(e)):e??[],organization:({allProxyModels:e})=>e,global:({allProxyModels:e})=>e};e.s(["MODEL_SENTINEL_OPTIONS",0,c,"ModelSelect",0,e=>{let{teamID:f,organizationID:m,options:h,context:g,dataTestId:b,value:x=[],onChange:v,style:y}=e,{includeUserModels:w,showAllTeamModelsOption:j,showAllProxyModelsOverride:C,includeSpecialOptions:k}=h||{},{data:N,isLoading:R}=(0,r.useAllProxyModels)(),{data:T,isLoading:E}=(0,n.useTeam)(f),{data:M,isLoading:S}=(0,a.useOrganization)(m),{data:z,isLoading:I}=(0,o.useCurrentUser)(),O=e=>c.some(t=>t.value===e),$=x.some(O),A=M?.models.includes(d.value)||M?.models.length===0;if(R||E||S||I)return(0,t.jsx)(i.Skeleton.Input,{active:!0,block:!0});let{wildcard:q,regular:P}=(e=>{let t=[],r=[];for(let a of e)a.endsWith("/*")?t.push(a):r.push(a);return{wildcard:t,regular:r}})(((e,t,r)=>{let a=Array.from(new Map(e.map(e=>[e.id,e])).values()).map(e=>e.id);if(t.options?.showAllProxyModelsOverride)return a;let n=p[t.context];return n?n({allProxyModels:a,...r,options:t.options}):[]})(N?.data??[],e,{selectedTeam:T,selectedOrganization:M,userModels:z?.models}));return(0,t.jsx)(s.Select,{"data-testid":b,value:x,onChange:e=>{let t=e.filter(O);v(t.length>0?[t[t.length-1]]:e)},style:y,options:[...k?[{label:(0,t.jsx)("span",{children:"Special Options"}),title:"Special Options",options:[...C||A&&k||"global"===g?[{label:(0,t.jsx)("span",{children:"All Proxy Models"}),value:d.value,disabled:x.length>0&&x.some(e=>O(e)&&e!==d.value),key:d.value}]:[],{label:(0,t.jsx)("span",{children:"No Default Models"}),value:u.value,disabled:x.length>0&&x.some(e=>O(e)&&e!==u.value),key:u.value}]}]:[],...q.length>0?[{label:(0,t.jsx)("span",{children:"Wildcard Options"}),title:"Wildcard Options",options:q.map(e=>{let r=e.replace("/*",""),a=r.charAt(0).toUpperCase()+r.slice(1);return{label:(0,t.jsx)("span",{children:`All ${a} models`}),value:e,disabled:$}})}]:[],{label:(0,t.jsx)("span",{children:"Models"}),title:"Models",options:P.map(e=>({label:(0,t.jsx)("span",{children:e}),value:e,disabled:$}))}],mode:"multiple",placeholder:"Select Models",allowClear:!0,maxTagCount:"responsive",maxTagPlaceholder:e=>(0,t.jsx)(l.Tooltip,{styles:{root:{pointerEvents:"none"}},title:e.map(({value:e})=>e).join(", "),children:(0,t.jsxs)("span",{children:["+",e.length," more"]})})})}],162386)},655063,e=>{"use strict";var t=e.i(399029),r=e.i(271645);e.s(["useDebouncedValue",0,function(e,a,n){let[o,s,i]=(0,t.useDebouncedState)(e,a,n);return(0,r.useEffect)(()=>{s(e)},[e,s]),[o,i]}])},278587,e=>{"use strict";var t=e.i(271645);let r=t.forwardRef(function(e,r){return t.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),t.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"}))});e.s(["RefreshIcon",0,r],278587)},624687,e=>{"use strict";var t=e.i(843476),r=e.i(271645),a=e.i(115504);let n=r.forwardRef(({className:e,...r},n)=>(0,t.jsx)("textarea",{ref:n,"data-slot":"textarea",className:(0,a.cn)("flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",e),...r}));n.displayName="Textarea",e.s(["Textarea",0,n])},950594,e=>{"use strict";var t=e.i(843476),r=e.i(271645),a=e.i(115504),n=e.i(519455),o=e.i(793479),s=e.i(624687);let i=(0,a.cva)({base:"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",variants:{align:{"inline-start":"order-first pl-2 has-[>button]:-ml-1 has-[>kbd]:ml-[-0.15rem]","inline-end":"order-last pr-2 has-[>button]:-mr-1 has-[>kbd]:mr-[-0.15rem]","block-start":"order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2","block-end":"order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2"}},defaultVariants:{align:"inline-start"}}),l=(0,a.cva)({base:"flex items-center gap-2 text-sm shadow-none",variants:{size:{xs:"h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",sm:"","icon-xs":"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0","icon-sm":"size-8 p-0 has-[>svg]:p-0"}},defaultVariants:{size:"xs"}}),d=r.forwardRef(({className:e,type:r="button",variant:o="ghost",size:s="xs",...i},d)=>(0,t.jsx)(n.Button,{ref:d,type:r,"data-size":s,variant:o,className:(0,a.cn)(l({size:s}),e),...i}));d.displayName="InputGroupButton";let u=r.forwardRef(({className:e,...r},n)=>(0,t.jsx)(o.Input,{ref:n,"data-slot":"input-group-control",className:(0,a.cn)("flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",e),...r}));u.displayName="InputGroupInput",r.forwardRef(({className:e,...r},n)=>(0,t.jsx)(s.Textarea,{ref:n,"data-slot":"input-group-control",className:(0,a.cn)("flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",e),...r})).displayName="InputGroupTextarea",e.s(["InputGroup",0,function({className:e,...r}){return(0,t.jsx)("div",{"data-slot":"input-group",role:"group",className:(0,a.cn)("group/input-group relative flex h-9 w-full min-w-0 items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",e),...r})},"InputGroupAddon",0,function({className:e,align:r="inline-start",...n}){return(0,t.jsx)("div",{role:"group","data-slot":"input-group-addon","data-align":r,className:(0,a.cn)(i({align:r}),e),onClick:e=>{e.target.closest("button")||e.currentTarget.parentElement?.querySelector("input")?.focus()},...n})},"InputGroupButton",0,d,"InputGroupInput",0,u,"InputGroupText",0,function({className:e,...r}){return(0,t.jsx)("span",{className:(0,a.cn)("flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",e),...r})}])},552546,e=>{"use strict";var t=e.i(843476),r=e.i(131792);let a=(e,t)=>{let r=t.trim().toLowerCase();return!r||e.label.toLowerCase().includes(r)||(e.sublabel?.toLowerCase().includes(r)??!1)};e.s(["SearchSelect",0,function({options:e,value:n,onValueChange:o,placeholder:s="Select…",emptyText:i="No results",disabled:l=!1,className:d}){let u=e.find(e=>e.value===n)??null;return(0,t.jsxs)(r.Combobox,{items:e,value:u,onValueChange:e=>o(e?.value??""),isItemEqualToValue:(e,t)=>e.value===t.value,itemToStringLabel:e=>e.label,filter:a,disabled:l,children:[(0,t.jsx)(r.ComboboxInput,{placeholder:s,showClear:null!=n&&""!==n,className:`w-full ${d??""}`}),(0,t.jsxs)(r.ComboboxContent,{children:[(0,t.jsx)(r.ComboboxEmpty,{children:i}),(0,t.jsx)(r.ComboboxList,{children:e=>(0,t.jsx)(r.ComboboxItem,{value:e,children:(0,t.jsxs)("span",{className:"flex min-w-0 flex-col",children:[(0,t.jsx)("span",{className:"truncate",children:e.label}),null!=e.sublabel&&""!==e.sublabel&&(0,t.jsx)("span",{className:"truncate text-xs text-muted-foreground",children:e.sublabel})]})},e.value)})]})]})}])},78085,e=>{"use strict";var t=e.i(290571),r=e.i(103471),a=e.i(888288),n=e.i(271645),o=e.i(444755),s=e.i(673706);let i=(0,s.makeClassName)("Textarea"),l=n.default.forwardRef((e,l)=>{let{value:d,defaultValue:u="",placeholder:c="Type...",error:p=!1,errorMessage:f,disabled:m=!1,className:h,onChange:g,onValueChange:b,autoHeight:x=!1}=e,v=(0,t.__rest)(e,["value","defaultValue","placeholder","error","errorMessage","disabled","className","onChange","onValueChange","autoHeight"]),[y,w]=(0,a.default)(u,d),j=(0,n.useRef)(null),C=(0,r.hasValue)(y);return(0,n.useEffect)(()=>{let e=j.current;if(x&&e){e.style.height="60px";let t=e.scrollHeight;e.style.height=t+"px"}},[x,j,y]),n.default.createElement(n.default.Fragment,null,n.default.createElement("textarea",Object.assign({ref:(0,s.mergeRefs)([j,l]),value:y,placeholder:c,disabled:m,className:(0,o.tremorTwMerge)(i("Textarea"),"w-full flex items-center outline-none rounded-tremor-default px-3 py-2 text-tremor-default focus:ring-2 transition duration-100 border","shadow-tremor-input focus:border-tremor-brand-subtle focus:ring-tremor-brand-muted","dark:shadow-dark-tremor-input focus:dark:border-dark-tremor-brand-subtle focus:dark:ring-dark-tremor-brand-muted",(0,r.getSelectButtonColors)(C,m,p),m?"placeholder:text-tremor-content-subtle dark:placeholder:text-dark-tremor-content-subtle":"placeholder:text-tremor-content dark:placeholder:text-dark-tremor-content",h),"data-testid":"text-area",onChange:e=>{null==g||g(e),w(e.target.value),null==b||b(e.target.value)}},v)),p&&f?n.default.createElement("p",{className:(0,o.tremorTwMerge)(i("errorMessage"),"text-sm text-red-500 mt-1")},f):null)});l.displayName="Textarea",e.s(["Textarea",0,l],78085)},744582,e=>{"use strict";var t=e.i(843476),r=e.i(343488),a=e.i(531278),n=e.i(271645),o=e.i(131792),s=e.i(741466);let i=new Set(["input-change","input-clear","clear-press"]);e.s(["PaginatedSearchSelect",0,function({options:e,value:l,onValueChange:d,onSearchChange:u,onLoadMore:c,hasNextPage:p=!1,isLoading:f=!1,isFetchingNextPage:m=!1,placeholder:h="Search…",emptyText:g="No results",loadingText:b="Loading…",disabled:x=!1,className:v,inputId:y,"aria-invalid":w,"aria-describedby":j}){let C=(0,n.useMemo)(()=>void 0===l||""===l?null:e.find(e=>e.value===l)??{label:l,value:l},[e,l]),k=(0,n.useMemo)(()=>null===C||e.some(e=>e.value===C.value)?e:[C,...e],[e,C]),N=(0,r.useDebouncedCallback)(u,{wait:s.DEBOUNCE_WAIT_MS});return(0,t.jsxs)(o.Combobox,{items:k,value:C,onValueChange:e=>d(e?.value??""),onInputValueChange:(e,t)=>{var r;return r=t.reason,void(i.has(r)&&N(e))},isItemEqualToValue:(e,t)=>e.value===t.value,itemToStringLabel:e=>e.label,filter:null,disabled:x,children:[(0,t.jsx)(o.ComboboxInput,{id:y,"aria-invalid":w,"aria-describedby":j,placeholder:h,showClear:void 0!==l&&""!==l,className:`w-full ${v??""}`}),(0,t.jsxs)(o.ComboboxContent,{children:[(0,t.jsx)(o.ComboboxEmpty,{children:f?b:g}),(0,t.jsx)(o.ComboboxList,{onScroll:e=>{let t=e.currentTarget;0===t.scrollHeight||(t.scrollTop+t.clientHeight)/t.scrollHeight>=.8&&p&&!m&&c()},"data-testid":"paginated-search-select-list",children:e=>(0,t.jsx)(o.ComboboxItem,{value:e,children:(0,t.jsxs)("span",{className:"flex min-w-0 flex-col",children:[(0,t.jsx)("span",{className:"truncate",children:e.label}),null!=e.sublabel&&""!==e.sublabel&&(0,t.jsx)("span",{className:"truncate text-xs text-muted-foreground",children:e.sublabel})]})},e.value)}),m&&(0,t.jsx)("div",{className:"flex justify-center py-2","data-testid":"paginated-search-select-loading-more",children:(0,t.jsx)(a.Loader2,{className:"size-4 animate-spin text-muted-foreground"})})]})]})}])}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/06sx0oh8weeph.js b/litellm/proxy/_experimental/out/_next/static/chunks/06sx0oh8weeph.js new file mode 100644 index 00000000000..57f03aca8e1 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/06sx0oh8weeph.js @@ -0,0 +1,8 @@ +(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,784774,e=>{"use strict";var t=e.i(843476),a=e.i(271645),n=e.i(115504);let r=a.forwardRef(({className:e,...a},r)=>(0,t.jsx)("div",{"data-slot":"table-container",className:"relative w-full overflow-x-auto",children:(0,t.jsx)("table",{ref:r,"data-slot":"table",className:(0,n.cn)("w-full caption-bottom text-sm",e),...a})}));r.displayName="Table";let l=a.forwardRef(({className:e,...a},r)=>(0,t.jsx)("thead",{ref:r,"data-slot":"table-header",className:(0,n.cn)("[&_tr]:border-b",e),...a}));l.displayName="TableHeader";let o=a.forwardRef(({className:e,...a},r)=>(0,t.jsx)("tbody",{ref:r,"data-slot":"table-body",className:(0,n.cn)("[&_tr:last-child]:border-0",e),...a}));o.displayName="TableBody";let i=a.forwardRef(({className:e,...a},r)=>(0,t.jsx)("tfoot",{ref:r,"data-slot":"table-footer",className:(0,n.cn)("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",e),...a}));i.displayName="TableFooter";let s=a.forwardRef(({className:e,...a},r)=>(0,t.jsx)("tr",{ref:r,"data-slot":"table-row",className:(0,n.cn)("border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted",e),...a}));s.displayName="TableRow";let u=a.forwardRef(({className:e,...a},r)=>(0,t.jsx)("th",{ref:r,"data-slot":"table-head",className:(0,n.cn)("h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",e),...a}));u.displayName="TableHead";let d=a.forwardRef(({className:e,...a},r)=>(0,t.jsx)("td",{ref:r,"data-slot":"table-cell",className:(0,n.cn)("p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",e),...a}));d.displayName="TableCell",a.forwardRef(({className:e,...a},r)=>(0,t.jsx)("caption",{ref:r,"data-slot":"table-caption",className:(0,n.cn)("mt-4 text-sm text-muted-foreground",e),...a})).displayName="TableCaption",e.s(["Table",0,r,"TableBody",0,o,"TableCell",0,d,"TableFooter",0,i,"TableHead",0,u,"TableHeader",0,l,"TableRow",0,s])},302747,e=>{"use strict";var t=e.i(843476),a=e.i(271645),n=e.i(115504);let r=a.forwardRef(({className:e,...a},r)=>(0,t.jsx)("div",{ref:r,"data-slot":"skeleton",className:(0,n.cn)("animate-pulse rounded-md bg-accent",e),...a}));r.displayName="Skeleton",e.s(["Skeleton",0,r])},108821,e=>{"use strict";var t=e.i(733332),a=e.i(271645);let n=a.createContext(!1),r=a.createContext(void 0);e.s(["DialogRootContext",0,r,"IsDrawerContext",0,n,"useDialogRootContext",0,function(e){let n=a.useContext(r);if(!1===e&&void 0===n)throw Error((0,t.default)(27));return n}])},402820,156736,209793,625834,784324,264951,e=>{"use strict";e.i(247167);var t,a,n=e.i(271645),r=e.i(108821),l=e.i(552245),o=e.i(405005),i=e.i(209407);let s={...o.popupStateMapping,...i.transitionStatusMapping},u=n.forwardRef(function(e,t){let{render:a,className:n,style:o,forceRender:i=!1,...u}=e,{store:d}=(0,r.useDialogRootContext)(),c=d.useState("open"),p=d.useState("nested"),g=d.useState("mounted"),m=d.useState("transitionStatus");return(0,l.useRenderElement)("div",e,{state:{open:c,transitionStatus:m},ref:[d.context.backdropRef,t],stateAttributesMapping:s,props:[{role:"presentation",hidden:!g,style:{userSelect:"none",WebkitUserSelect:"none"}},u],enabled:i||!p})});e.s(["DialogBackdrop",0,u],402820);var d=e.i(540886),c=e.i(675606),p=e.i(56434);let g=n.forwardRef(function(e,t){let{render:a,className:n,style:o,disabled:i=!1,nativeButton:s=!0,...u}=e,{store:g}=(0,r.useDialogRootContext)(),m=g.useState("open"),{getButtonProps:f,buttonRef:b}=(0,d.useButton)({disabled:i,native:s});return(0,l.useRenderElement)("button",e,{state:{disabled:i},ref:[t,b],props:[{onClick:function(e){m&&g.setOpen(!1,(0,c.createChangeEventDetails)(p.REASONS.closePress,e.nativeEvent))}},u,f]})});e.s(["DialogClose",0,g],156736);var m=e.i(788015);let f=n.forwardRef(function(e,t){let{render:a,className:n,style:o,id:i,...s}=e,{store:u}=(0,r.useDialogRootContext)(),d=(0,m.useBaseUiId)(i);return u.useSyncedValueWithCleanup("descriptionElementId",d),(0,l.useRenderElement)("p",e,{ref:t,props:[{id:d},s]})});e.s(["DialogDescription",0,f],209793);var b=e.i(61487);let x=((t={}).nestedDialogs="--nested-dialogs",t),h=((a={})[a.open=o.CommonPopupDataAttributes.open]="open",a[a.closed=o.CommonPopupDataAttributes.closed]="closed",a[a.startingStyle=o.CommonPopupDataAttributes.startingStyle]="startingStyle",a[a.endingStyle=o.CommonPopupDataAttributes.endingStyle]="endingStyle",a.nested="data-nested",a.nestedDialogOpen="data-nested-dialog-open",a);var y=e.i(733332);let C=n.createContext(void 0);function v(){let e=n.useContext(C);if(void 0===e)throw Error((0,y.default)(26));return e}e.s(["DialogPortalContext",0,C,"useDialogPortalContext",0,v],625834);var S=e.i(137584),w=e.i(673327),$=e.i(264111),D=e.i(843476);let j={...o.popupStateMapping,...i.transitionStatusMapping,nestedDialogOpen:e=>e?{[h.nestedDialogOpen]:""}:null},R=n.forwardRef(function(e,t){let{render:a,className:n,style:o,finalFocus:i,initialFocus:s,...u}=e,{store:d}=(0,r.useDialogRootContext)(),c=d.useState("descriptionElementId"),p=d.useState("disablePointerDismissal"),g=d.useState("floatingRootContext"),m=d.useState("popupProps"),f=d.useState("modal"),h=d.useState("mounted"),y=d.useState("nested"),C=d.useState("nestedOpenDialogCount"),R=d.useState("open"),O=d.useState("openMethod"),N=d.useState("titleElementId"),k=d.useState("transitionStatus"),E=d.useState("role"),M=g.useState("floatingId"),P=u.id??M;v(),(0,S.useOpenChangeComplete)({open:R,ref:d.context.popupRef,onComplete(){R&&d.context.onOpenChangeComplete?.(!0)}});let T=void 0===s?(0,$.createDefaultInitialFocus)(d.context.popupRef):s,A=d.useStateSetter("popupElement"),I=(0,l.useRenderElement)("div",e,{state:{open:R,nested:y,transitionStatus:k,nestedDialogOpen:C>0},props:[m,{id:P,"aria-labelledby":N??void 0,"aria-describedby":c??void 0,role:E,...$.FOCUSABLE_POPUP_PROPS,hidden:!h,onKeyDown(e){w.COMPOSITE_KEYS.has(e.key)&&e.stopPropagation()},style:{[x.nestedDialogs]:C}},u],ref:[t,d.context.popupRef,A],stateAttributesMapping:j});return(0,D.jsx)(b.FloatingFocusManager,{context:g,openInteractionType:O,disabled:!h,closeOnFocusOut:!p,initialFocus:T,returnFocus:i,modal:!1!==f,restoreFocus:"popup",children:I})});e.s(["DialogPopup",0,R],784324);var O=e.i(144394),N=e.i(726674),k=e.i(426);let E=n.forwardRef(function(e,t){let{keepMounted:a=!1,...n}=e,{store:l}=(0,r.useDialogRootContext)(),o=l.useState("mounted"),i=l.useState("modal"),s=l.useState("open");return o||a?(0,D.jsx)(C.Provider,{value:a,children:(0,D.jsxs)(N.FloatingPortal,{ref:t,...n,children:[o&&!0===i&&(0,D.jsx)(k.InternalBackdrop,{ref:l.context.internalBackdropRef,inert:(0,O.inertValue)(!s)}),e.children]})}):null});e.s(["DialogPortal",0,E],264951)},67530,e=>{"use strict";var t=e.i(271645),a=e.i(145484),n=e.i(956789),r=e.i(17989),l=e.i(647554),o=e.i(675606),i=e.i(56434),s=e.i(264111);e.s(["DialogInteractions",0,function({store:e,parentContext:o,isDrawer:i}){let u=e.useState("open"),d=e.useState("disablePointerDismissal"),c=e.useState("modal"),p=e.useState("popupElement"),g=e.useState("floatingRootContext"),[m,f]=t.useState(0),[b,x]=t.useState(0),h=0===m,y=(0,r.useDismiss)(g,{outsidePressEvent:()=>e.context.internalBackdropRef.current||e.context.backdropRef.current?"intentional":{mouse:"trap-focus"===c?"sloppy":"intentional",touch:"sloppy"},outsidePress(t){if(!e.context.outsidePressEnabledRef.current||"button"in t&&0!==t.button||"touches"in t&&1!==t.touches.length)return!1;let a=(0,l.getTarget)(t);return!!h&&!d&&(!c||!e.context.internalBackdropRef.current&&!e.context.backdropRef.current||e.context.internalBackdropRef.current===a||e.context.backdropRef.current===a||(0,l.contains)(a,p)&&!a?.hasAttribute("data-base-ui-portal"))},escapeKey:h});(0,a.useScrollLock)(u&&!0===c,p),e.useContextCallback("onNestedDialogOpen",(e,t)=>{f(e),x(t)}),e.useContextCallback("onNestedDialogClose",()=>{f(0),x(0)}),t.useEffect(()=>(o?.onNestedDialogOpen&&u&&o.onNestedDialogOpen(m+1,b+ +!!i),o?.onNestedDialogClose&&!u&&o.onNestedDialogClose(),()=>{o?.onNestedDialogClose&&u&&o.onNestedDialogClose()}),[i,u,m,b,o]);let C=y.reference??n.EMPTY_OBJECT,v=y.trigger??n.EMPTY_OBJECT,S=y.floating??n.EMPTY_OBJECT;return(0,s.usePopupInteractionProps)(e,{activeTriggerProps:C,inactiveTriggerProps:v,popupProps:S,nestedOpenDialogCount:m,nestedOpenDrawerCount:b}),null},"useDialogRoot",0,function(e){let{store:a,actionsRef:n}=e,r=a.useState("open");(0,s.usePopupRootSync)(a,r),(0,s.useImplicitActiveTrigger)(a);let{forceUnmount:l}=(0,s.useOpenStateTransitions)(r,a),u=t.useCallback(()=>{a.setOpen(!1,(0,o.createChangeEventDetails)(i.REASONS.imperativeAction))},[a]);t.useImperativeHandle(n,()=>({unmount:l,close:u}),[l,u])}])},366250,301807,e=>{"use strict";var t=e.i(271645),a=e.i(713203),n=e.i(67530),r=e.i(108821),l=e.i(616269),o=e.i(301252),i=e.i(116786),s=e.i(990627),u=e.i(264111);let d={...i.popupStoreSelectors,modal:(0,l.createSelector)(e=>e.modal),nested:(0,l.createSelector)(e=>e.nested),nestedOpenDialogCount:(0,l.createSelector)(e=>e.nestedOpenDialogCount),nestedOpenDrawerCount:(0,l.createSelector)(e=>e.nestedOpenDrawerCount),disablePointerDismissal:(0,l.createSelector)(e=>e.disablePointerDismissal),openMethod:(0,l.createSelector)(e=>e.openMethod),descriptionElementId:(0,l.createSelector)(e=>e.descriptionElementId),titleElementId:(0,l.createSelector)(e=>e.titleElementId),viewportElement:(0,l.createSelector)(e=>e.viewportElement),role:(0,l.createSelector)(e=>e.role)};class c extends o.ReactStore{constructor(e,a,n=!1){const r=new s.PopupTriggerMap,l=function(e={}){return{...(0,i.createInitialPopupStoreState)(),modal:!0,disablePointerDismissal:!1,popupElement:null,viewportElement:null,descriptionElementId:void 0,titleElementId:void 0,openMethod:null,nested:!1,nestedOpenDialogCount:0,nestedOpenDrawerCount:0,role:"dialog",...e}}(e);l.floatingRootContext=(0,i.createPopupFloatingRootContext)(r,a,n),super(l,{popupRef:t.createRef(),backdropRef:t.createRef(),internalBackdropRef:t.createRef(),outsidePressEnabledRef:{current:!0},triggerElements:r,onOpenChange:void 0,onOpenChangeComplete:void 0},d)}setOpen=(e,t)=>{if(t.preventUnmountOnClose=()=>{this.set("preventUnmountingOnClose",!0)},e||null!=t.trigger||null==this.state.activeTriggerId||(t.trigger=this.state.activeTriggerElement??void 0),this.context.onOpenChange?.(e,t),t.isCanceled)return;this.state.floatingRootContext.dispatchOpenChange(e,t);let a={open:e};(0,u.setPopupOpenState)(a,e,t.trigger),this.update(a)};static useStore(e,t){return(0,u.usePopupStore)(e,(e,a)=>new c(t,e,a),!0).store}}e.s(["DialogStore",0,c],301807);var p=e.i(843476);e.s(["useRenderDialogRoot",0,function(e,l="dialog"){let{children:o,open:i,defaultOpen:s=!1,onOpenChange:u,onOpenChangeComplete:d,disablePointerDismissal:g=!1,modal:m=!0,actionsRef:f,handle:b,triggerId:x,defaultTriggerId:h=null}=e,y="alert-dialog"===l,C=(0,r.useDialogRootContext)(!0),v={modal:!!y||m,disablePointerDismissal:y||g,nested:!!C,role:y?"alertdialog":"dialog"},S=c.useStore(b?.store,{open:s,openProp:i,activeTriggerId:h,triggerIdProp:x,...v});(0,a.useOnFirstRender)(()=>{let e=void 0===i&&!1===S.state.open&&!0===s?{open:!0,activeTriggerId:h}:null;y?S.update(e?{...v,...e}:v):e&&S.update(e)}),S.useControlledProp("openProp",i),S.useControlledProp("triggerIdProp",x),S.useSyncedValues(v),S.useContextCallback("onOpenChange",u),S.useContextCallback("onOpenChangeComplete",d);let w=S.useState("open"),$=S.useState("mounted"),D=S.useState("payload");(0,n.useDialogRoot)({store:S,actionsRef:f});let j=t.useMemo(()=>({store:S}),[S]);return(0,p.jsx)(r.IsDrawerContext.Provider,{value:!1,children:(0,p.jsxs)(r.DialogRootContext.Provider,{value:j,children:[(w||$)&&(0,p.jsx)(n.DialogInteractions,{store:S,parentContext:C?.store.context,isDrawer:"drawer"===l}),"function"==typeof o?o({payload:D}):o]})})}],366250)},974217,e=>{"use strict";e.i(247167);var t,a=e.i(271645),n=e.i(552245),r=e.i(405005),l=e.i(209407),o=e.i(108821),i=e.i(625834);let s=((t={})[t.open=r.CommonPopupDataAttributes.open]="open",t[t.closed=r.CommonPopupDataAttributes.closed]="closed",t[t.startingStyle=r.CommonPopupDataAttributes.startingStyle]="startingStyle",t[t.endingStyle=r.CommonPopupDataAttributes.endingStyle]="endingStyle",t.nested="data-nested",t.nestedDialogOpen="data-nested-dialog-open",t),u={...r.popupStateMapping,...l.transitionStatusMapping,nested:e=>e?{[s.nested]:""}:null,nestedDialogOpen:e=>e?{[s.nestedDialogOpen]:""}:null},d=a.forwardRef(function(e,t){let{render:a,className:r,style:l,children:s,...d}=e,c=(0,i.useDialogPortalContext)(),{store:p}=(0,o.useDialogRootContext)(),g=p.useState("open"),m=p.useState("nested"),f=p.useState("transitionStatus"),b=p.useState("nestedOpenDialogCount"),x=p.useState("mounted"),h=p.useStateSetter("viewportElement");return(0,n.useRenderElement)("div",e,{enabled:c||x,state:{open:g,nested:m,transitionStatus:f,nestedDialogOpen:b>0},ref:[t,h],stateAttributesMapping:u,props:[{role:"presentation",hidden:!x,style:{pointerEvents:g?void 0:"none"},children:s},d]})});e.s(["DialogViewport",0,d],974217)},77173,313488,e=>{"use strict";e.i(247167);var t=e.i(271645),a=e.i(108821),n=e.i(552245),r=e.i(788015);let l=t.forwardRef(function(e,t){let{render:l,className:o,style:i,id:s,...u}=e,{store:d}=(0,a.useDialogRootContext)(),c=(0,r.useBaseUiId)(s);return d.useSyncedValueWithCleanup("titleElementId",c),(0,n.useRenderElement)("h2",e,{ref:t,props:[{id:c},u]})});e.s(["DialogTitle",0,l],77173);var o=e.i(733332),i=e.i(540886),s=e.i(405005),u=e.i(638396),d=e.i(264111),c=e.i(385689),p=e.i(32199);let g=t.forwardRef(function(e,l){let{render:g,className:m,style:f,disabled:b=!1,nativeButton:x=!0,id:h,payload:y,handle:C,...v}=e,S=(0,a.useDialogRootContext)(!0),w=C?.store??S?.store;if(!w)throw Error((0,o.default)(79));let $=(0,r.useBaseUiId)(h),D=w.useState("floatingRootContext"),j=w.useState("isOpenedByTrigger",$),R=w.useState("triggerPopupId",$),O=t.useRef(null),{registerTrigger:N,isMountedByThisTrigger:k}=(0,d.useTriggerDataForwarding)($,O,w,{payload:y}),{getButtonProps:E,buttonRef:M}=(0,i.useButton)({disabled:b,native:x}),P=(0,c.useClick)(D,{enabled:null!=D}),T=(0,p.useOpenMethodTriggerProps)(()=>w.select("open"),e=>{w.set("openMethod",e)}),A=w.useState("triggerProps",k);return(0,n.useRenderElement)("button",e,{state:{disabled:b,open:j},ref:[M,l,N,O],props:[P.reference,A,T,{[u.CLICK_TRIGGER_IDENTIFIER]:"",id:$,"aria-haspopup":"dialog","aria-expanded":j,"aria-controls":R},v,E],stateAttributesMapping:s.triggerOpenStateMapping})});e.s(["DialogTrigger",0,g],313488)},325326,e=>{"use strict";var t=e.i(301807),a=e.i(675606),n=e.i(56434);class r{constructor(e){this.store=e??new t.DialogStore}open(e){let t=e?this.store.context.triggerElements.getById(e):void 0;this.store.setOpen(!0,(0,a.createChangeEventDetails)(n.REASONS.imperativeAction,void 0,t))}openWithPayload(e){this.store.set("payload",e),this.store.setOpen(!0,(0,a.createChangeEventDetails)(n.REASONS.imperativeAction,void 0,void 0))}close(){this.store.setOpen(!1,(0,a.createChangeEventDetails)(n.REASONS.imperativeAction,void 0,void 0))}get isOpen(){return this.store.select("open")}}e.s(["DialogHandle",0,r,"createDialogHandle",0,function(){return new r}])},353753,e=>{"use strict";e.s([],914651),e.i(914651);var t=e.i(402820),a=e.i(156736),n=e.i(209793),r=e.i(784324),l=e.i(264951),o=e.i(271645),i=e.i(108821),s=e.i(366250),u=e.i(974217),d=e.i(77173),c=e.i(313488),p=e.i(325326);e.s(["Backdrop",()=>t.DialogBackdrop,"Close",()=>a.DialogClose,"Description",()=>n.DialogDescription,"Handle",()=>p.DialogHandle,"Popup",()=>r.DialogPopup,"Portal",()=>l.DialogPortal,"Root",0,function(e){let t=o.useContext(i.IsDrawerContext)?"drawer":"dialog";return(0,s.useRenderDialogRoot)(e,t)},"Title",()=>d.DialogTitle,"Trigger",()=>c.DialogTrigger,"Viewport",()=>u.DialogViewport,"createHandle",()=>p.createDialogHandle],828376);var g=e.i(828376);e.s(["Dialog",0,g],353753)},16715,e=>{"use strict";let t=(0,e.i(475254).default)("refresh-cw",[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]]);e.s(["RefreshCw",0,t],16715)},110204,e=>{"use strict";var t=e.i(843476),a=e.i(271645),n=e.i(115504);let r=a.forwardRef(({className:e,...a},r)=>(0,t.jsx)("label",{ref:r,"data-slot":"label",className:(0,n.cn)("flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",e),...a}));r.displayName="Label",e.s(["Label",0,r])},37727,e=>{"use strict";var t=e.i(841947);e.s(["X",()=>t.default])},185793,e=>{"use strict";e.i(247167);var t=e.i(271645),a=e.i(343794),n=e.i(242064),r=e.i(529681);let l=e=>{let{prefixCls:n,className:r,style:l,size:o,shape:i}=e,s=(0,a.default)({[`${n}-lg`]:"large"===o,[`${n}-sm`]:"small"===o}),u=(0,a.default)({[`${n}-circle`]:"circle"===i,[`${n}-square`]:"square"===i,[`${n}-round`]:"round"===i}),d=t.useMemo(()=>"number"==typeof o?{width:o,height:o,lineHeight:`${o}px`}:{},[o]);return t.createElement("span",{className:(0,a.default)(n,s,u,r),style:Object.assign(Object.assign({},d),l)})};e.i(296059);var o=e.i(694758),i=e.i(915654),s=e.i(246422),u=e.i(838378);let d=new o.Keyframes("ant-skeleton-loading",{"0%":{backgroundPosition:"100% 50%"},"100%":{backgroundPosition:"0 50%"}}),c=e=>({height:e,lineHeight:(0,i.unit)(e)}),p=e=>Object.assign({width:e},c(e)),g=(e,t)=>Object.assign({width:t(e).mul(5).equal(),minWidth:t(e).mul(5).equal()},c(e)),m=e=>Object.assign({width:e},c(e)),f=(e,t,a)=>{let{skeletonButtonCls:n}=e;return{[`${a}${n}-circle`]:{width:t,minWidth:t,borderRadius:"50%"},[`${a}${n}-round`]:{borderRadius:t}}},b=(e,t)=>Object.assign({width:t(e).mul(2).equal(),minWidth:t(e).mul(2).equal()},c(e)),x=(0,s.genStyleHooks)("Skeleton",e=>{let{componentCls:t,calc:a}=e;return(e=>{let{componentCls:t,skeletonAvatarCls:a,skeletonTitleCls:n,skeletonParagraphCls:r,skeletonButtonCls:l,skeletonInputCls:o,skeletonImageCls:i,controlHeight:s,controlHeightLG:u,controlHeightSM:c,gradientFromColor:x,padding:h,marginSM:y,borderRadius:C,titleHeight:v,blockRadius:S,paragraphLiHeight:w,controlHeightXS:$,paragraphMarginTop:D}=e;return{[t]:{display:"table",width:"100%",[`${t}-header`]:{display:"table-cell",paddingInlineEnd:h,verticalAlign:"top",[a]:Object.assign({display:"inline-block",verticalAlign:"top",background:x},p(s)),[`${a}-circle`]:{borderRadius:"50%"},[`${a}-lg`]:Object.assign({},p(u)),[`${a}-sm`]:Object.assign({},p(c))},[`${t}-content`]:{display:"table-cell",width:"100%",verticalAlign:"top",[n]:{width:"100%",height:v,background:x,borderRadius:S,[`+ ${r}`]:{marginBlockStart:c}},[r]:{padding:0,"> li":{width:"100%",height:w,listStyle:"none",background:x,borderRadius:S,"+ li":{marginBlockStart:$}}},[`${r}> li:last-child:not(:first-child):not(:nth-child(2))`]:{width:"61%"}},[`&-round ${t}-content`]:{[`${n}, ${r} > li`]:{borderRadius:C}}},[`${t}-with-avatar ${t}-content`]:{[n]:{marginBlockStart:y,[`+ ${r}`]:{marginBlockStart:D}}},[`${t}${t}-element`]:Object.assign(Object.assign(Object.assign(Object.assign({display:"inline-block",width:"auto"},(e=>{let{borderRadiusSM:t,skeletonButtonCls:a,controlHeight:n,controlHeightLG:r,controlHeightSM:l,gradientFromColor:o,calc:i}=e;return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({[a]:Object.assign({display:"inline-block",verticalAlign:"top",background:o,borderRadius:t,width:i(n).mul(2).equal(),minWidth:i(n).mul(2).equal()},b(n,i))},f(e,n,a)),{[`${a}-lg`]:Object.assign({},b(r,i))}),f(e,r,`${a}-lg`)),{[`${a}-sm`]:Object.assign({},b(l,i))}),f(e,l,`${a}-sm`))})(e)),(e=>{let{skeletonAvatarCls:t,gradientFromColor:a,controlHeight:n,controlHeightLG:r,controlHeightSM:l}=e;return{[t]:Object.assign({display:"inline-block",verticalAlign:"top",background:a},p(n)),[`${t}${t}-circle`]:{borderRadius:"50%"},[`${t}${t}-lg`]:Object.assign({},p(r)),[`${t}${t}-sm`]:Object.assign({},p(l))}})(e)),(e=>{let{controlHeight:t,borderRadiusSM:a,skeletonInputCls:n,controlHeightLG:r,controlHeightSM:l,gradientFromColor:o,calc:i}=e;return{[n]:Object.assign({display:"inline-block",verticalAlign:"top",background:o,borderRadius:a},g(t,i)),[`${n}-lg`]:Object.assign({},g(r,i)),[`${n}-sm`]:Object.assign({},g(l,i))}})(e)),(e=>{let{skeletonImageCls:t,imageSizeBase:a,gradientFromColor:n,borderRadiusSM:r,calc:l}=e;return{[t]:Object.assign(Object.assign({display:"inline-flex",alignItems:"center",justifyContent:"center",verticalAlign:"middle",background:n,borderRadius:r},m(l(a).mul(2).equal())),{[`${t}-path`]:{fill:"#bfbfbf"},[`${t}-svg`]:Object.assign(Object.assign({},m(a)),{maxWidth:l(a).mul(4).equal(),maxHeight:l(a).mul(4).equal()}),[`${t}-svg${t}-svg-circle`]:{borderRadius:"50%"}}),[`${t}${t}-circle`]:{borderRadius:"50%"}}})(e)),[`${t}${t}-block`]:{width:"100%",[l]:{width:"100%"},[o]:{width:"100%"}},[`${t}${t}-active`]:{[` + ${n}, + ${r} > li, + ${a}, + ${l}, + ${o}, + ${i} + `]:Object.assign({},{background:e.skeletonLoadingBackground,backgroundSize:"400% 100%",animationName:d,animationDuration:e.skeletonLoadingMotionDuration,animationTimingFunction:"ease",animationIterationCount:"infinite"})}}})((0,u.mergeToken)(e,{skeletonAvatarCls:`${t}-avatar`,skeletonTitleCls:`${t}-title`,skeletonParagraphCls:`${t}-paragraph`,skeletonButtonCls:`${t}-button`,skeletonInputCls:`${t}-input`,skeletonImageCls:`${t}-image`,imageSizeBase:a(e.controlHeight).mul(1.5).equal(),borderRadius:100,skeletonLoadingBackground:`linear-gradient(90deg, ${e.gradientFromColor} 25%, ${e.gradientToColor} 37%, ${e.gradientFromColor} 63%)`,skeletonLoadingMotionDuration:"1.4s"}))},e=>{let{colorFillContent:t,colorFill:a}=e;return{color:t,colorGradientEnd:a,gradientFromColor:t,gradientToColor:a,titleHeight:e.controlHeight/2,blockRadius:e.borderRadiusSM,paragraphMarginTop:e.marginLG+e.marginXXS,paragraphLiHeight:e.controlHeight/2}},{deprecatedTokens:[["color","gradientFromColor"],["colorGradientEnd","gradientToColor"]]}),h=e=>{let{prefixCls:n,className:r,style:l,rows:o=0}=e,i=Array.from({length:o}).map((a,n)=>t.createElement("li",{key:n,style:{width:((e,t)=>{let{width:a,rows:n=2}=t;return Array.isArray(a)?a[e]:n-1===e?a:void 0})(n,e)}}));return t.createElement("ul",{className:(0,a.default)(n,r),style:l},i)},y=({prefixCls:e,className:n,width:r,style:l})=>t.createElement("h3",{className:(0,a.default)(e,n),style:Object.assign({width:r},l)});function C(e){return e&&"object"==typeof e?e:{}}let v=e=>{let{prefixCls:r,loading:o,className:i,rootClassName:s,style:u,children:d,avatar:c=!1,title:p=!0,paragraph:g=!0,active:m,round:f}=e,{getPrefixCls:b,direction:v,className:S,style:w}=(0,n.useComponentConfig)("skeleton"),$=b("skeleton",r),[D,j,R]=x($);if(o||!("loading"in e)){let e,n,r=!!c,o=!!p,d=!!g;if(r){let a=Object.assign(Object.assign({prefixCls:`${$}-avatar`},o&&!d?{size:"large",shape:"square"}:{size:"large",shape:"circle"}),C(c));e=t.createElement("div",{className:`${$}-header`},t.createElement(l,Object.assign({},a)))}if(o||d){let e,a;if(o){let a=Object.assign(Object.assign({prefixCls:`${$}-title`},!r&&d?{width:"38%"}:r&&d?{width:"50%"}:{}),C(p));e=t.createElement(y,Object.assign({},a))}if(d){let e,n=Object.assign(Object.assign({prefixCls:`${$}-paragraph`},(e={},r&&o||(e.width="61%"),!r&&o?e.rows=3:e.rows=2,e)),C(g));a=t.createElement(h,Object.assign({},n))}n=t.createElement("div",{className:`${$}-content`},e,a)}let b=(0,a.default)($,{[`${$}-with-avatar`]:r,[`${$}-active`]:m,[`${$}-rtl`]:"rtl"===v,[`${$}-round`]:f},S,i,s,j,R);return D(t.createElement("div",{className:b,style:Object.assign(Object.assign({},w),u)},e,n))}return null!=d?d:null};v.Button=e=>{let{prefixCls:o,className:i,rootClassName:s,active:u,block:d=!1,size:c="default"}=e,{getPrefixCls:p}=t.useContext(n.ConfigContext),g=p("skeleton",o),[m,f,b]=x(g),h=(0,r.default)(e,["prefixCls"]),y=(0,a.default)(g,`${g}-element`,{[`${g}-active`]:u,[`${g}-block`]:d},i,s,f,b);return m(t.createElement("div",{className:y},t.createElement(l,Object.assign({prefixCls:`${g}-button`,size:c},h))))},v.Avatar=e=>{let{prefixCls:o,className:i,rootClassName:s,active:u,shape:d="circle",size:c="default"}=e,{getPrefixCls:p}=t.useContext(n.ConfigContext),g=p("skeleton",o),[m,f,b]=x(g),h=(0,r.default)(e,["prefixCls","className"]),y=(0,a.default)(g,`${g}-element`,{[`${g}-active`]:u},i,s,f,b);return m(t.createElement("div",{className:y},t.createElement(l,Object.assign({prefixCls:`${g}-avatar`,shape:d,size:c},h))))},v.Input=e=>{let{prefixCls:o,className:i,rootClassName:s,active:u,block:d,size:c="default"}=e,{getPrefixCls:p}=t.useContext(n.ConfigContext),g=p("skeleton",o),[m,f,b]=x(g),h=(0,r.default)(e,["prefixCls"]),y=(0,a.default)(g,`${g}-element`,{[`${g}-active`]:u,[`${g}-block`]:d},i,s,f,b);return m(t.createElement("div",{className:y},t.createElement(l,Object.assign({prefixCls:`${g}-input`,size:c},h))))},v.Image=e=>{let{prefixCls:r,className:l,rootClassName:o,style:i,active:s}=e,{getPrefixCls:u}=t.useContext(n.ConfigContext),d=u("skeleton",r),[c,p,g]=x(d),m=(0,a.default)(d,`${d}-element`,{[`${d}-active`]:s},l,o,p,g);return c(t.createElement("div",{className:m},t.createElement("div",{className:(0,a.default)(`${d}-image`,l),style:i},t.createElement("svg",{viewBox:"0 0 1098 1024",xmlns:"http://www.w3.org/2000/svg",className:`${d}-image-svg`},t.createElement("title",null,"Image placeholder"),t.createElement("path",{d:"M365.714286 329.142857q0 45.714286-32.036571 77.677714t-77.677714 32.036571-77.677714-32.036571-32.036571-77.677714 32.036571-77.677714 77.677714-32.036571 77.677714 32.036571 32.036571 77.677714zM950.857143 548.571429l0 256-804.571429 0 0-109.714286 182.857143-182.857143 91.428571 91.428571 292.571429-292.571429zM1005.714286 146.285714l-914.285714 0q-7.460571 0-12.873143 5.412571t-5.412571 12.873143l0 694.857143q0 7.460571 5.412571 12.873143t12.873143 5.412571l914.285714 0q7.460571 0 12.873143-5.412571t5.412571-12.873143l0-694.857143q0-7.460571-5.412571-12.873143t-12.873143-5.412571zM1097.142857 164.571429l0 694.857143q0 37.741714-26.843429 64.585143t-64.585143 26.843429l-914.285714 0q-37.741714 0-64.585143-26.843429t-26.843429-64.585143l0-694.857143q0-37.741714 26.843429-64.585143t64.585143-26.843429l914.285714 0q37.741714 0 64.585143 26.843429t26.843429 64.585143z",className:`${d}-image-path`})))))},v.Node=e=>{let{prefixCls:r,className:l,rootClassName:o,style:i,active:s,children:u}=e,{getPrefixCls:d}=t.useContext(n.ConfigContext),c=d("skeleton",r),[p,g,m]=x(c),f=(0,a.default)(c,`${c}-element`,{[`${c}-active`]:s},g,l,o,m);return p(t.createElement("div",{className:f},t.createElement("div",{className:(0,a.default)(`${c}-image`,l),style:i},u)))},e.s(["default",0,v],185793)},922611,e=>{"use strict";var t=e.i(271645),a=e.i(175066);function n(){}let r=t.createContext({add:n,remove:n});e.s(["usePanelRef",0,function(e){let n=t.useContext(r),l=t.useRef(null);return(0,a.default)(t=>{if(t){let a=e?t.querySelector(e):t;a&&(n.add(a),l.current=a)}else n.remove(l.current)})}])},500330,e=>{"use strict";var t=e.i(727749);let a=(e,t=0,a=!1,n=!0)=>{if(null==e||!Number.isFinite(e)||0===e&&!n)return"-";let r={minimumFractionDigits:t,maximumFractionDigits:t};if(!a)return e.toLocaleString("en-US",r);let l=e<0?"-":"",o=Math.abs(e),i=o,s="";return o>=1e6?(i=o/1e6,s="M"):o>=1e3&&(i=o/1e3,s="K"),`${l}${i.toLocaleString("en-US",r)}${s}`},n=async(e,a="Copied to clipboard")=>{if(!e)return!1;if(!navigator||!navigator.clipboard||!navigator.clipboard.writeText)return r(e,a);try{return await navigator.clipboard.writeText(e),t.default.success(a),!0}catch(t){return console.error("Clipboard API failed: ",t),r(e,a)}},r=(e,a)=>{try{let n=document.createElement("textarea");n.value=e,n.style.position="fixed",n.style.left="-999999px",n.style.top="-999999px",n.setAttribute("readonly",""),document.body.appendChild(n),n.focus(),n.select();let r=document.execCommand("copy");if(document.body.removeChild(n),r)return t.default.success(a),!0;throw Error("execCommand failed")}catch(e){return t.default.fromBackend("Failed to copy to clipboard"),console.error("Failed to copy: ",e),!1}};e.s(["copyToClipboard",0,n,"formatNumberWithCommas",0,a,"getSpendString",0,(e,t=6)=>{if(null==e||!Number.isFinite(e)||0===e)return"-";let n=a(e,t,!1,!1);if(0===Number(n.replace(/,/g,""))){let e=(1/10**t).toFixed(t);return`< $${e}`}return`$${n}`},"updateExistingKeys",0,function(e,t){let a=structuredClone(e);for(let[e,n]of Object.entries(t))e in a&&(a[e]=n);return a}])},112179,581070,e=>{"use strict";var t=e.i(843476),a=e.i(487486),n=e.i(115504),r=e.i(746798);function l({content:e,trigger:a}){return(0,t.jsx)(r.TooltipProvider,{delay:300,children:(0,t.jsxs)(r.Tooltip,{children:[(0,t.jsx)(r.TooltipTrigger,{render:a}),(0,t.jsx)(r.TooltipContent,{children:e})]})})}e.s(["CellTooltip",0,l],581070);let o={success:"border-green-200 bg-green-50 text-green-600",error:"border-red-200 bg-red-50 text-red-600",warning:"border-amber-200 bg-amber-50 text-amber-600",neutral:"border-gray-200 bg-gray-50 text-gray-600",info:"border-blue-200 bg-blue-50 text-blue-600"};e.s(["StatusBadge",0,function({tone:e,label:r,tooltip:i,dataTestId:s}){let u=(0,t.jsx)(a.Badge,{variant:"outline","data-testid":s,className:(0,n.cn)("whitespace-nowrap font-normal",o[e]),children:r});return i?(0,t.jsx)(l,{content:i,trigger:u}):u}],112179)},199931,e=>{"use strict";let t=(0,e.i(475254).default)("waypoints",[["circle",{cx:"12",cy:"4.5",r:"2.5",key:"r5ysbb"}],["path",{d:"m10.2 6.3-3.9 3.9",key:"1nzqf6"}],["circle",{cx:"4.5",cy:"12",r:"2.5",key:"jydg6v"}],["path",{d:"M7 12h10",key:"b7w52i"}],["circle",{cx:"19.5",cy:"12",r:"2.5",key:"1piiel"}],["path",{d:"m13.8 17.7 3.9-3.9",key:"1wyg1y"}],["circle",{cx:"12",cy:"19.5",r:"2.5",key:"13o1pw"}]]);e.s(["Waypoints",0,t],199931)},625901,e=>{"use strict";var t=e.i(266027),a=e.i(621482),n=e.i(912598),r=e.i(243652),l=e.i(602869),o=e.i(135214);let i=(0,r.createQueryKeys)("models"),s=(0,r.createQueryKeys)("modelHub"),u=(0,r.createQueryKeys)("allProxyModels");(0,r.createQueryKeys)("selectedTeamModels");let d=(0,r.createQueryKeys)("infiniteModels"),c=(0,r.createQueryKeys)("userModels"),p=new Set,g=e=>!!e?.litellm_params?.model?.startsWith("auto_router/"),m=e=>new Set(e.filter(g).map(e=>e.model_name).filter(e=>!!e)),f=e=>e.filter(g),b=async(e,t,a)=>{let n=await (0,l.modelInfoCall)(e,t,a,1,1e3),r=n?.total_pages??1;return[n,...await Promise.all(Array.from({length:Math.max(0,r-1)},(n,r)=>(0,l.modelInfoCall)(e,t,a,r+2,1e3)))].flatMap(e=>e?.data??[])},x=(e,t)=>i.list({filters:{scope:"autoRouters",...e&&{userId:e},...t&&{userRole:t}}});e.s(["useAllProxyModels",0,()=>{let{accessToken:e,userId:a,userRole:n}=(0,o.default)();return(0,t.useQuery)({queryKey:u.list({}),queryFn:async()=>await (0,l.modelAvailableCall)(e,a,n,!0,null,!0,!1,"expand"),enabled:!!(e&&a&&n)})},"useAutoRouterModelGroups",0,()=>{let{accessToken:e,userId:a,userRole:n}=(0,o.default)(),{data:r}=(0,t.useQuery)({queryKey:x(a,n),queryFn:async()=>await b(e,a,n),enabled:!!(e&&a&&n),select:m});return r??p},"useAutoRouters",0,()=>{let{accessToken:e,userId:a,userRole:n}=(0,o.default)();return(0,t.useQuery)({queryKey:x(a,n),queryFn:async()=>await b(e,a,n),enabled:!!(e&&a&&n),select:f})},"useInfiniteModelInfo",0,(e=50,t)=>{let{accessToken:n,userId:r,userRole:i}=(0,o.default)();return(0,a.useInfiniteQuery)({queryKey:d.list({filters:{...r&&{userId:r},...i&&{userRole:i},size:e,...t&&{search:t}}}),queryFn:async({pageParam:a})=>await (0,l.modelInfoCall)(n,r,i,a,e,t),initialPageParam:1,getNextPageParam:e=>{if(e.current_page{let e=(0,n.useQueryClient)();return async()=>{await e.invalidateQueries({queryKey:i.lists()})}},"useModelHub",0,()=>{let{accessToken:e}=(0,o.default)();return(0,t.useQuery)({queryKey:s.list({}),queryFn:async()=>await (0,l.modelHubCall)(e),enabled:!!e})},"useModelsInfo",0,(e=1,a=50,n,r,s,u,d,c=!1)=>{let{accessToken:p,userId:g,userRole:m}=(0,o.default)();return(0,t.useQuery)({queryKey:i.list({filters:{...g&&{userId:g},...m&&{userRole:m},page:e,size:a,...n&&{search:n},...r&&{modelId:r},...s&&{teamId:s},...u&&{sortBy:u},...d&&{sortOrder:d},...c&&{excludeAutoRouters:"true"}}}),queryFn:async()=>await (0,l.modelInfoCall)(p,g,m,e,a,n,r,s,u,d,c),enabled:!!(p&&g&&m)})},"useUserModels",0,()=>{let{accessToken:e,userId:a,userRole:n}=(0,o.default)();return(0,t.useQuery)({queryKey:c.list({}),queryFn:async()=>(await (0,l.modelAvailableCall)(e,a,n)).data.map(e=>e.id),enabled:!!(e&&a&&n)})}])},548151,200208,399536,997422,146512,e=>{"use strict";var t=e.i(843476),a=e.i(271645),n=e.i(199931),r=e.i(625901),l=e.i(487486),o=e.i(115504);let i=new Set,s=(0,a.createContext)(i);function u(e){let t=(0,a.useContext)(s);return!!e&&t.has(e)}e.s(["AutoRouterIcon",0,function({size:e=12,className:a}){return(0,t.jsx)(n.Waypoints,{size:e,className:a,"aria-hidden":!0})},"AutoRouterModelGroupsProvider",0,function({children:e}){let a=(0,r.useAutoRouterModelGroups)();return(0,t.jsx)(s.Provider,{value:a,children:e})},"AutoRouterTag",0,function({modelGroup:e,className:a}){return u(e)?(0,t.jsxs)(l.Badge,{variant:"secondary",title:`Routed by auto-router "${e}"`,className:(0,o.cn)("gap-1.5 px-2.5 py-1 text-sm font-normal text-foreground",a),children:[(0,t.jsx)(n.Waypoints,{"aria-hidden":!0}),e]}):null},"useIsAutoRoutedModelGroup",0,u],548151);var d=e.i(581070);let c=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],p=e=>String(e).padStart(2,"0"),g=(e,t)=>"date"===t?`${c[e.getMonth()]} ${e.getDate()}, ${e.getFullYear()}`:`${c[e.getMonth()]} ${e.getDate()}, ${p(e.getHours())}:${p(e.getMinutes())}:${p(e.getSeconds())}`;e.s(["DateCell",0,function({value:e,precision:a="datetime",fallback:n="-"}){let r,l,o,i=e?new Date(e):null;return!i||Number.isNaN(i.getTime())?(0,t.jsx)("span",{className:"text-muted-foreground",children:n}):(0,t.jsx)(d.CellTooltip,{content:(r=Intl.DateTimeFormat().resolvedOptions().timeZone,l=`${c[i.getMonth()]} ${i.getDate()}, ${i.getFullYear()}`,o=`${p(i.getHours())}:${p(i.getMinutes())}:${p(i.getSeconds())}`,`${l}, ${o} (${r})`),trigger:(0,t.jsx)("span",{className:"whitespace-nowrap",children:g(i,a)})})},"formatCellDate",0,g],200208);var m=e.i(174886),f=e.i(500330);let b={pill:{base:"font-mono text-xs font-normal px-2 py-0.5 rounded-md text-left bg-blue-50 text-blue-500",clickable:"hover:bg-blue-100 cursor-pointer"},plain:{base:"font-mono text-xs text-left",clickable:"hover:text-blue-600 cursor-pointer"}};e.s(["IdCell",0,function({value:e,variant:a="pill",onClick:n,copyable:r=!1,truncate:l=!0,fallback:i="-",tooltip:s,disabled:u=!1,dataTestId:c,className:p}){if(!e)return(0,t.jsx)("span",{className:"text-muted-foreground",children:i});let g=!!n&&!u,x=(0,o.cn)(b[a].base,g&&b[a].clickable,l&&"block max-w-[15ch] truncate",u&&"opacity-50",p),h=g?(0,t.jsx)("button",{type:"button",className:x,"data-testid":c,onClick:()=>n(e),children:e}):(0,t.jsx)("span",{className:x,"data-testid":c,children:e}),y=(0,t.jsx)(d.CellTooltip,{content:s??e,trigger:h});return r?(0,t.jsxs)("span",{className:"inline-flex max-w-full items-center gap-1",children:[y,(0,t.jsx)("button",{type:"button","aria-label":"Copy ID",className:"shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",onClick:t=>{t.stopPropagation(),(0,f.copyToClipboard)(e)},children:(0,t.jsx)(m.Copy,{className:"size-3"})})]}):y}],399536);var x=e.i(463059);e.s(["IdentityCell",0,function({title:e,subtitle:a,badge:n,onClick:r,className:l,titleClassName:i}){let s=(0,t.jsxs)("div",{className:"flex min-w-0 flex-col gap-0.5",children:[(0,t.jsx)("span",{className:(0,o.cn)("truncate text-sm font-medium text-foreground",i),children:e}),(null!=a&&""!==a||null!=n)&&(0,t.jsxs)("span",{className:"flex min-w-0 items-center gap-2",children:[null!=a&&""!==a&&(0,t.jsx)("span",{className:"truncate font-mono text-xs text-muted-foreground",children:a}),n]})]});return null!=r?(0,t.jsxs)("button",{type:"button",onClick:r,className:(0,o.cn)("group -mx-2 flex w-[calc(100%+1rem)] cursor-pointer items-center gap-2 rounded-md px-2 py-1 text-left transition-colors hover:bg-muted",l),children:[s,(0,t.jsx)(x.ChevronRight,{className:"ml-auto size-4 shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100"})]}):(0,t.jsx)("div",{className:(0,o.cn)("min-w-0",l),children:s})}],997422);let h={hasModelAccess:!1,label:"Management"},y={hasModelAccess:!1,label:"Read-only"},C={hasModelAccess:!1,label:"SCIM"},v={hasModelAccess:!0,label:null},S=e=>e.startsWith("/scim"),w=(e,t)=>1===e.length&&e[0]===t;e.s(["deriveKeyModelScope",0,(e,t)=>"management"===t?h:"read_only"===t?y:Array.isArray(e)&&0!==e.length?e.every(S)?C:w(e,"management_routes")?h:w(e,"info_routes")?y:v:v],146512)},355619,e=>{"use strict";var t=e.i(602869);let a=async(e,a,n)=>{try{if(null===e||null===a)return;if(null!==n){let r=(await (0,t.modelAvailableCall)(n,e,a,!0,null,!0)).data.map(e=>e.id),l=[],o=[];return r.forEach(e=>{e.endsWith("/*")?l.push(e):o.push(e)}),[...l,...o]}}catch(e){console.error("Error fetching user models:",e)}};e.s(["excludeProxyWideSentinel",0,e=>e.filter(e=>"all-proxy-models"!==e),"fetchAvailableModelsForTeamOrKey",0,a,"getModelDisplayName",0,e=>{if("all-proxy-models"===e)return"All Proxy Models";if(e.endsWith("/*")){let t=e.replace("/*","");return`All ${t} models`}return e},"hasAllModelsSentinel",0,e=>e.includes("all-proxy-models")||e.includes("all-team-models"),"unfurlWildcardModelsInList",0,(e,t)=>{let a=[],n=[];return e.forEach(e=>{if(e.endsWith("/*")){let r=e.replace("/*",""),l=t.filter(e=>e.startsWith(r+"/"));n.push(...l),a.push(e)}else n.push(e)}),[...a,...n].filter((e,t,a)=>a.indexOf(e)===t)}])},622826,547227,964471,630500,e=>{"use strict";e.i(548151);var t=e.i(581070);e.i(200208),e.i(399536),e.i(997422);var a=e.i(843476),n=e.i(146512),r=e.i(355619),l=e.i(487486);let o="all-proxy-models",i=e=>{if(e===o)return"All Proxy Models";let t=(0,r.getModelDisplayName)(e);return t.length>30?`${t.slice(0,30)}...`:t};e.s(["ModelsCell",0,function({models:e,maxVisible:r=3,allowedRoutes:s,keyType:u}){if(!Array.isArray(e)||0===e.length){let e=(0,n.deriveKeyModelScope)(s,u);return e.hasModelAccess?(0,a.jsx)(l.Badge,{variant:"secondary",children:"All Proxy Models"}):(0,a.jsx)(t.CellTooltip,{content:`Scoped to ${e.label} routes; this key cannot call any models`,trigger:(0,a.jsx)(l.Badge,{variant:"secondary",className:"cursor-default",children:"No model access"})})}let d=e.slice(0,r),c=e.slice(r);return(0,a.jsxs)("div",{className:"flex flex-wrap items-center gap-1",children:[d.map((e,t)=>(0,a.jsx)(l.Badge,{variant:e===o?"secondary":"outline",children:i(e)},t)),c.length>0&&(0,a.jsx)(t.CellTooltip,{content:(0,a.jsx)("div",{className:"flex max-w-[280px] flex-col gap-0.5",children:c.map((e,t)=>(0,a.jsx)("span",{children:i(e)},t))}),trigger:(0,a.jsxs)(l.Badge,{variant:"outline",className:"cursor-default",children:["+",c.length," more"]})})]})}],547227);var s=e.i(500330);e.s(["MoneyCell",0,function({value:e,decimals:t=4,emptyText:n="-",showZero:r=!1}){return null==e||Number.isNaN(e)?(0,a.jsx)("span",{className:"text-muted-foreground",children:n}):0===e?r?(0,a.jsx)("span",{className:"whitespace-nowrap",children:`$${(0,s.formatNumberWithCommas)(0,t,!1,!0)}`}):(0,a.jsx)("span",{className:"text-muted-foreground",children:"-"}):(0,a.jsx)("span",{className:"whitespace-nowrap",children:(0,s.getSpendString)(e,t)})}],964471);var u=e.i(944835);e.s(["SpendBudgetCell",0,function({spend:e,maxBudget:t,teamMaxBudget:n}){let r="number"!=typeof e||Number.isNaN(e)?0:e,l=t??n??null,o=null==t&&null!=n,i="number"==typeof l&&l>0,d=i?r/l*100:0,c=r>0?(0,s.getSpendString)(r,4):"$0.00",p=null===l?"· Unlimited":`of $${(0,s.formatNumberWithCommas)(l)}${o?" (Team)":""}`;return(0,a.jsxs)("div",{className:"flex min-w-[130px] flex-col gap-1",children:[(0,a.jsxs)("div",{className:"whitespace-nowrap text-xs",children:[(0,a.jsx)("span",{className:"font-medium tabular-nums text-foreground",children:c})," ",(0,a.jsx)("span",{className:"text-muted-foreground",children:p})]}),i&&(0,a.jsx)(u.Meter,{value:r,max:l,"aria-valuetext":`${c} of $${(0,s.formatNumberWithCommas)(l)}`,children:(0,a.jsx)(u.MeterTrack,{children:(0,a.jsx)(u.MeterIndicator,{tone:d>100?"over":d>=80?"warning":"default"})})})]})}],630500),e.i(112179),e.s([],622826)},233565,e=>{"use strict";var t=e.i(246349);e.s(["ChevronRightIcon",()=>t.default])}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/08goggic_ad66.js b/litellm/proxy/_experimental/out/_next/static/chunks/08goggic_ad66.js deleted file mode 100644 index e15232235db..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/08goggic_ad66.js +++ /dev/null @@ -1,2 +0,0 @@ -(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,233538,e=>{"use strict";e.s(["isDisabledReactIssue7711",0,function(e){let t=e.parentElement,n=null;for(;t&&!(t instanceof HTMLFieldSetElement);)t instanceof HTMLLegendElement&&(n=t),t=t.parentElement;let i=(null==t?void 0:t.getAttribute("disabled"))==="";return!(i&&function(e){if(!e)return!1;let t=e.previousElementSibling;for(;null!==t;){if(t instanceof HTMLLegendElement)return!1;t=t.previousElementSibling}return!0}(n))&&i}])},83733,233137,e=>{"use strict";let t,n;var i,s,r=e.i(247167),a=e.i(271645),l=e.i(544508),o=e.i(746725),d=e.i(835696);void 0!==r.default&&"u">typeof globalThis&&"u">typeof Element&&(null==(i=null==r.default?void 0:r.default.env)?void 0:i.NODE_ENV)==="test"&&void 0===(null==(s=null==Element?void 0:Element.prototype)?void 0:s.getAnimations)&&(Element.prototype.getAnimations=function(){return console.warn(["Headless UI has polyfilled `Element.prototype.getAnimations` for your tests.","Please install a proper polyfill e.g. `jsdom-testing-mocks`, to silence these warnings.","","Example usage:","```js","import { mockAnimationsApi } from 'jsdom-testing-mocks'","mockAnimationsApi()","```"].join(` -`)),[]});var u=((t=u||{})[t.None=0]="None",t[t.Closed=1]="Closed",t[t.Enter=2]="Enter",t[t.Leave=4]="Leave",t);e.s(["transitionDataAttributes",0,function(e){let t={};for(let n in e)!0===e[n]&&(t[`data-${n}`]="");return t},"useTransition",0,function(e,t,n,i){let[s,r]=(0,a.useState)(n),{hasFlag:u,addFlag:c,removeFlag:h}=function(e=0){let[t,n]=(0,a.useState)(e),i=(0,a.useCallback)(e=>n(e),[t]),s=(0,a.useCallback)(e=>n(t=>t|e),[t]),r=(0,a.useCallback)(e=>(t&e)===e,[t]);return{flags:t,setFlag:i,addFlag:s,hasFlag:r,removeFlag:(0,a.useCallback)(e=>n(t=>t&~e),[n]),toggleFlag:(0,a.useCallback)(e=>n(t=>t^e),[n])}}(e&&s?3:0),m=(0,a.useRef)(!1),f=(0,a.useRef)(!1),p=(0,o.useDisposables)();return(0,d.useIsoMorphicEffect)(()=>{var s;if(e){if(n&&r(!0),!t){n&&c(3);return}return null==(s=null==i?void 0:i.start)||s.call(i,n),function(e,{prepare:t,run:n,done:i,inFlight:s}){let r=(0,l.disposables)();return function(e,{inFlight:t,prepare:n}){if(null!=t&&t.current)return n();let i=e.style.transition;e.style.transition="none",n(),e.offsetHeight,e.style.transition=i}(e,{prepare:t,inFlight:s}),r.nextFrame(()=>{n(),r.requestAnimationFrame(()=>{r.add(function(e,t){var n,i;let s=(0,l.disposables)();if(!e)return s.dispose;let r=!1;s.add(()=>{r=!0});let a=null!=(i=null==(n=e.getAnimations)?void 0:n.call(e).filter(e=>e instanceof CSSTransition))?i:[];return 0===a.length?t():Promise.allSettled(a.map(e=>e.finished)).then(()=>{r||t()}),s.dispose}(e,i))})}),r.dispose}(t,{inFlight:m,prepare(){f.current?f.current=!1:f.current=m.current,m.current=!0,f.current||(n?(c(3),h(4)):(c(4),h(2)))},run(){f.current?n?(h(3),c(4)):(h(4),c(3)):n?h(1):c(1)},done(){var e;f.current&&"function"==typeof t.getAnimations&&t.getAnimations().length>0||(m.current=!1,h(7),n||r(!1),null==(e=null==i?void 0:i.end)||e.call(i,n))}})}},[e,n,t,p]),e?[s,{closed:u(1),enter:u(2),leave:u(4),transition:u(2)||u(4)}]:[n,{closed:void 0,enter:void 0,leave:void 0,transition:void 0}]}],83733);let c=(0,a.createContext)(null);c.displayName="OpenClosedContext";var h=((n=h||{})[n.Open=1]="Open",n[n.Closed=2]="Closed",n[n.Closing=4]="Closing",n[n.Opening=8]="Opening",n);e.s(["OpenClosedProvider",0,function({value:e,children:t}){return a.default.createElement(c.Provider,{value:e},t)},"ResetOpenClosedProvider",0,function({children:e}){return a.default.createElement(c.Provider,{value:null},e)},"State",0,h,"useOpenClosed",0,function(){return(0,a.useContext)(c)}],233137)},677667,674175,886148,543086,e=>{"use strict";let t,n;var i,s=e.i(290571),r=e.i(783222),a=e.i(433336),l=e.i(271645),o=e.i(394487),d=e.i(914189),u=e.i(144279),c=e.i(294316),h=e.i(83733);let m=(0,l.createContext)(()=>{});function f({value:e,children:t}){return l.default.createElement(m.Provider,{value:e},t)}e.s(["CloseProvider",0,f],674175);var p=e.i(233137),g=e.i(233538),v=e.i(397701),x=e.i(402155),b=e.i(700020);let y=null!=(i=l.default.startTransition)?i:function(e){e()};var _=e.i(998348),j=((t=j||{})[t.Open=0]="Open",t[t.Closed=1]="Closed",t),E=((n=E||{})[n.ToggleDisclosure=0]="ToggleDisclosure",n[n.CloseDisclosure=1]="CloseDisclosure",n[n.SetButtonId=2]="SetButtonId",n[n.SetPanelId=3]="SetPanelId",n[n.SetButtonElement=4]="SetButtonElement",n[n.SetPanelElement=5]="SetPanelElement",n);let w={0:e=>({...e,disclosureState:(0,v.match)(e.disclosureState,{0:1,1:0})}),1:e=>1===e.disclosureState?e:{...e,disclosureState:1},2:(e,t)=>e.buttonId===t.buttonId?e:{...e,buttonId:t.buttonId},3:(e,t)=>e.panelId===t.panelId?e:{...e,panelId:t.panelId},4:(e,t)=>e.buttonElement===t.element?e:{...e,buttonElement:t.element},5:(e,t)=>e.panelElement===t.element?e:{...e,panelElement:t.element}},C=(0,l.createContext)(null);function k(e){let t=(0,l.useContext)(C);if(null===t){let t=Error(`<${e} /> is missing a parent component.`);throw Error.captureStackTrace&&Error.captureStackTrace(t,k),t}return t}C.displayName="DisclosureContext";let S=(0,l.createContext)(null);S.displayName="DisclosureAPIContext";let T=(0,l.createContext)(null);function N(e,t){return(0,v.match)(t.type,w,e,t)}T.displayName="DisclosurePanelContext";let O=l.Fragment,I=b.RenderFeatures.RenderStrategy|b.RenderFeatures.Static,R=Object.assign((0,b.forwardRefWithAs)(function(e,t){let{defaultOpen:n=!1,...i}=e,s=(0,l.useRef)(null),r=(0,c.useSyncRefs)(t,(0,c.optionalRef)(e=>{s.current=e},void 0===e.as||e.as===l.Fragment)),a=(0,l.useReducer)(N,{disclosureState:+!n,buttonElement:null,panelElement:null,buttonId:null,panelId:null}),[{disclosureState:o,buttonId:u},h]=a,m=(0,d.useEvent)(e=>{h({type:1});let t=(0,x.getOwnerDocument)(s);if(!t||!u)return;let n=e?e instanceof HTMLElement?e:e.current instanceof HTMLElement?e.current:t.getElementById(u):t.getElementById(u);null==n||n.focus()}),g=(0,l.useMemo)(()=>({close:m}),[m]),y=(0,l.useMemo)(()=>({open:0===o,close:m}),[o,m]),_=(0,b.useRender)();return l.default.createElement(C.Provider,{value:a},l.default.createElement(S.Provider,{value:g},l.default.createElement(f,{value:m},l.default.createElement(p.OpenClosedProvider,{value:(0,v.match)(o,{0:p.State.Open,1:p.State.Closed})},_({ourProps:{ref:r},theirProps:i,slot:y,defaultTag:O,name:"Disclosure"})))))}),{Button:(0,b.forwardRefWithAs)(function(e,t){let n=(0,l.useId)(),{id:i=`headlessui-disclosure-button-${n}`,disabled:s=!1,autoFocus:h=!1,...m}=e,[f,p]=k("Disclosure.Button"),v=(0,l.useContext)(T),x=null!==v&&v===f.panelId,y=(0,l.useRef)(null),j=(0,c.useSyncRefs)(y,t,(0,d.useEvent)(e=>{if(!x)return p({type:4,element:e})}));(0,l.useEffect)(()=>{if(!x)return p({type:2,buttonId:i}),()=>{p({type:2,buttonId:null})}},[i,p,x]);let E=(0,d.useEvent)(e=>{var t;if(x){if(1===f.disclosureState)return;switch(e.key){case _.Keys.Space:case _.Keys.Enter:e.preventDefault(),e.stopPropagation(),p({type:0}),null==(t=f.buttonElement)||t.focus()}}else switch(e.key){case _.Keys.Space:case _.Keys.Enter:e.preventDefault(),e.stopPropagation(),p({type:0})}}),w=(0,d.useEvent)(e=>{e.key===_.Keys.Space&&e.preventDefault()}),C=(0,d.useEvent)(e=>{var t;(0,g.isDisabledReactIssue7711)(e.currentTarget)||s||(x?(p({type:0}),null==(t=f.buttonElement)||t.focus()):p({type:0}))}),{isFocusVisible:S,focusProps:N}=(0,r.useFocusRing)({autoFocus:h}),{isHovered:O,hoverProps:I}=(0,a.useHover)({isDisabled:s}),{pressed:R,pressProps:P}=(0,o.useActivePress)({disabled:s}),L=(0,l.useMemo)(()=>({open:0===f.disclosureState,hover:O,active:R,disabled:s,focus:S,autofocus:h}),[f,O,R,S,s,h]),D=(0,u.useResolveButtonType)(e,f.buttonElement),A=x?(0,b.mergeProps)({ref:j,type:D,disabled:s||void 0,autoFocus:h,onKeyDown:E,onClick:C},N,I,P):(0,b.mergeProps)({ref:j,id:i,type:D,"aria-expanded":0===f.disclosureState,"aria-controls":f.panelElement?f.panelId:void 0,disabled:s||void 0,autoFocus:h,onKeyDown:E,onKeyUp:w,onClick:C},N,I,P);return(0,b.useRender)()({ourProps:A,theirProps:m,slot:L,defaultTag:"button",name:"Disclosure.Button"})}),Panel:(0,b.forwardRefWithAs)(function(e,t){let n=(0,l.useId)(),{id:i=`headlessui-disclosure-panel-${n}`,transition:s=!1,...r}=e,[a,o]=k("Disclosure.Panel"),{close:u}=function e(t){let n=(0,l.useContext)(S);if(null===n){let n=Error(`<${t} /> is missing a parent component.`);throw Error.captureStackTrace&&Error.captureStackTrace(n,e),n}return n}("Disclosure.Panel"),[m,f]=(0,l.useState)(null),g=(0,c.useSyncRefs)(t,(0,d.useEvent)(e=>{y(()=>o({type:5,element:e}))}),f);(0,l.useEffect)(()=>(o({type:3,panelId:i}),()=>{o({type:3,panelId:null})}),[i,o]);let v=(0,p.useOpenClosed)(),[x,_]=(0,h.useTransition)(s,m,null!==v?(v&p.State.Open)===p.State.Open:0===a.disclosureState),j=(0,l.useMemo)(()=>({open:0===a.disclosureState,close:u}),[a.disclosureState,u]),E={ref:g,id:i,...(0,h.transitionDataAttributes)(_)},w=(0,b.useRender)();return l.default.createElement(p.ResetOpenClosedProvider,null,l.default.createElement(T.Provider,{value:a.panelId},w({ourProps:E,theirProps:r,slot:j,defaultTag:"div",features:I,visible:x,name:"Disclosure.Panel"})))})});e.s(["Disclosure",0,R],886148);let P=(0,l.createContext)(void 0);var L=e.i(444755);let D=(0,e.i(673706).makeClassName)("Accordion"),A=(0,l.createContext)({isOpen:!1}),F=l.default.forwardRef((e,t)=>{var n;let{defaultOpen:i=!1,children:r,className:a}=e,o=(0,s.__rest)(e,["defaultOpen","children","className"]),d=null!=(n=(0,l.useContext)(P))?n:(0,L.tremorTwMerge)("rounded-tremor-default border");return l.default.createElement(R,Object.assign({as:"div",ref:t,className:(0,L.tremorTwMerge)(D("root"),"overflow-hidden","bg-tremor-background border-tremor-border","dark:bg-dark-tremor-background dark:border-dark-tremor-border",d,a),defaultOpen:i},o),({open:e})=>l.default.createElement(A.Provider,{value:{isOpen:e}},r))});F.displayName="Accordion",e.s(["OpenContext",0,A,"default",0,F],543086),e.s(["Accordion",0,F],677667)},130643,e=>{"use strict";var t=e.i(290571),n=e.i(271645),i=e.i(886148),s=e.i(444755);let r=(0,e.i(673706).makeClassName)("AccordionBody"),a=n.default.forwardRef((e,a)=>{let{children:l,className:o}=e,d=(0,t.__rest)(e,["children","className"]);return n.default.createElement(i.Disclosure.Panel,Object.assign({ref:a,className:(0,s.tremorTwMerge)(r("root"),"w-full text-tremor-default px-4 pb-3","text-tremor-content","dark:text-dark-tremor-content",o)},d),l)});a.displayName="AccordionBody",e.s(["AccordionBody",0,a],130643)},898667,e=>{"use strict";var t=e.i(290571),n=e.i(271645),i=e.i(886148);let s=e=>{var i=(0,t.__rest)(e,[]);return n.default.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},i),n.default.createElement("path",{d:"M11.9999 10.8284L7.0502 15.7782L5.63599 14.364L11.9999 8L18.3639 14.364L16.9497 15.7782L11.9999 10.8284Z"}))};var r=e.i(543086),a=e.i(444755);let l=(0,e.i(673706).makeClassName)("AccordionHeader"),o=n.default.forwardRef((e,o)=>{let{children:d,className:u}=e,c=(0,t.__rest)(e,["children","className"]),{isOpen:h}=(0,n.useContext)(r.OpenContext);return n.default.createElement(i.Disclosure.Button,Object.assign({ref:o,className:(0,a.tremorTwMerge)(l("root"),"w-full flex items-center justify-between px-4 py-3","text-tremor-content-emphasis","dark:text-dark-tremor-content-emphasis",u)},c),n.default.createElement("div",{className:(0,a.tremorTwMerge)(l("children"),"flex flex-1 text-inherit mr-4")},d),n.default.createElement("div",null,n.default.createElement(s,{className:(0,a.tremorTwMerge)(l("arrowIcon"),"h-5 w-5 -mr-1","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle",h?"transition-all":"transition-all -rotate-180")})))});o.displayName="AccordionHeader",e.s(["AccordionHeader",0,o],898667)},743151,(e,t,n)=>{"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.CopyToClipboard=void 0;var i=a(e.r(844343)),s=a(e.r(271645)),r=["text","onCopy","options","children"];function a(e){return e&&e.__esModule?e:{default:e}}function l(e){return(l="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 o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,i)}return n}function d(e){for(var t=1;t{"use strict";var i=e.r(743151).CopyToClipboard;i.CopyToClipboard=i,t.exports=i},285027,e=>{"use strict";e.i(247167);var t=e.i(931067),n=e.i(271645);let 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"};var s=e.i(9583),r=n.forwardRef(function(e,r){return n.createElement(s.default,(0,t.default)({},e,{ref:r,icon:i}))});e.s(["WarningOutlined",0,r],285027)},540626,e=>{"use strict";let t;var n,i=e.i(271645);let s=(0,i.createContext)(null);function r(e,t){if(Object.is(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;if(e instanceof Map&&t instanceof Map){if(e.size!==t.size)return!1;for(let[n,i]of e)if(!t.has(n)||!Object.is(i,t.get(n)))return!1;return!0}if(e instanceof Set&&t instanceof Set){if(e.size!==t.size)return!1;for(let n of e)if(!t.has(n))return!1;return!0}if(e instanceof Date&&t instanceof Date)return e.getTime()===t.getTime();let n=a(e);if(n.length!==a(t).length)return!1;for(let i=0;ie,n){let s=n?.compare??o,r=(0,i.useCallback)(t=>{let{unsubscribe:n}=e.subscribe(t);return n},[e]),a=(0,i.useCallback)(()=>e.get(),[e]);return(0,l.useSyncExternalStoreWithSelector)(r,a,a,t,s)}function u(e,...t){return"function"==typeof e?e(...t):e}var c=class{#e=!0;#t;#n;#i;#s;#r;#a;#l;#o=0;#d=5;#u=!1;#c=!1;#h=null;#m=()=>{this.debugLog("Connected to event bus"),this.#r=!0,this.#u=!1,this.debugLog("Emitting queued events",this.#s),this.#s.forEach(e=>this.emitEventToBus(e)),this.#s=[],this.stopConnectLoop(),this.#n().removeEventListener("tanstack-connect-success",this.#m)};#f=()=>{if(this.#o{this.#u||(this.#u=!0,this.#n().addEventListener("tanstack-connect-success",this.#m),this.#f())};constructor({pluginId:e,debug:t=!1,enabled:n=!0,reconnectEveryMs:i=300}){this.#t=e,this.#e=n,this.#n=this.getGlobalTarget,this.#i=t,this.debugLog(" Initializing event subscription for plugin",this.#t),this.#s=[],this.#r=!1,this.#c=!1,this.#a=null,this.#l=i}startConnectLoop(){null!==this.#a||this.#r||(this.debugLog(`Starting connect loop (every ${this.#l}ms)`),this.#a=setInterval(this.#f,this.#l))}stopConnectLoop(){this.#u=!1,null!==this.#a&&(clearInterval(this.#a),this.#a=null,this.#s=[],this.debugLog("Stopped connect loop"))}debugLog(...e){this.#i&&console.log(`🌴 [tanstack-devtools:${this.#t}-plugin]`,...e)}getGlobalTarget(){if("u">typeof globalThis&&globalThis.__TANSTACK_EVENT_TARGET__)return this.debugLog("Using global event target"),globalThis.__TANSTACK_EVENT_TARGET__;if("u">typeof window&&void 0!==window.addEventListener)return this.debugLog("Using window as event target"),window;let e="u">typeof EventTarget?new EventTarget:void 0;return void 0===e||void 0===e.addEventListener?(this.debugLog("No event mechanism available, running in non-web environment"),{addEventListener:()=>{},removeEventListener:()=>{},dispatchEvent:()=>!1}):(this.debugLog("Using new EventTarget as fallback"),e)}getPluginId(){return this.#t}dispatchCustomEventShim(e,t){try{let n=new Event(e,{detail:t});this.#n().dispatchEvent(n)}catch(e){this.debugLog("Failed to dispatch shim event")}}dispatchCustomEvent(e,t){try{this.#n().dispatchEvent(new CustomEvent(e,{detail:t}))}catch(n){this.dispatchCustomEventShim(e,t)}}emitEventToBus(e){this.debugLog("Emitting event to client bus",e),this.dispatchCustomEvent("tanstack-dispatch-event",e)}createEventPayload(e,t){return{type:`${this.#t}:${e}`,payload:t,pluginId:this.#t}}emit(e,t){if(!this.#e)return void this.debugLog("Event bus client is disabled, not emitting event",e,t);if(this.#h&&(this.debugLog("Emitting event to internal event target",e,t),this.#h.dispatchEvent(new CustomEvent(`${this.#t}:${e}`,{detail:this.createEventPayload(e,t)}))),this.#c)return void this.debugLog("Previously failed to connect, not emitting to bus");if(!this.#r){this.debugLog("Bus not available, will be pushed as soon as connected"),this.#s.push(this.createEventPayload(e,t)),"u">typeof CustomEvent&&!this.#u&&(this.#p(),this.startConnectLoop());return}return this.emitEventToBus(this.createEventPayload(e,t))}on(e,t,n){let i=n?.withEventTarget??!1,s=`${this.#t}:${e}`;if(i&&(this.#h||(this.#h=new EventTarget),this.#h.addEventListener(s,e=>{t(e.detail)})),!this.#e)return this.debugLog("Event bus client is disabled, not registering event",s),()=>{};let r=e=>{this.debugLog("Received event from bus",e.detail),t(e.detail)};return this.#n().addEventListener(s,r),this.debugLog("Registered event to bus",s),()=>{i&&this.#h?.removeEventListener(s,r),this.#n().removeEventListener(s,r)}}onAll(e){if(!this.#e)return this.debugLog("Event bus client is disabled, not registering event"),()=>{};let t=t=>{e(t.detail)};return this.#n().addEventListener("tanstack-devtools-global",t),()=>this.#n().removeEventListener("tanstack-devtools-global",t)}onAllPluginEvents(e){if(!this.#e)return this.debugLog("Event bus client is disabled, not registering event"),()=>{};let t=t=>{let n=t.detail;this.#t&&n.pluginId!==this.#t||e(n)};return this.#n().addEventListener("tanstack-devtools-global",t),()=>this.#n().removeEventListener("tanstack-devtools-global",t)}};let h=new Map;function m(e){if(void 0!==e)try{return JSON.parse(JSON.stringify(e))}catch{return null}}let f=new class extends c{constructor(e){super({pluginId:"pacer",debug:e?.debug,reconnectEveryMs:1e3})}},p=((n={})[n.None=0]="None",n[n.Mutable=1]="Mutable",n[n.Watching=2]="Watching",n[n.RecursedCheck=4]="RecursedCheck",n[n.Recursed=8]="Recursed",n[n.Dirty=16]="Dirty",n[n.Pending=32]="Pending",n);function g(e,t,n){let i="object"==typeof e,s=i?e:void 0;return{next:(i?e.next:e)?.bind(s),error:(i?e.error:t)?.bind(s),complete:(i?e.complete:n)?.bind(s)}}let v=[],x=0,{link:b,unlink:y,propagate:_,checkDirty:j,shallowPropagate:E}=function({update:e,notify:t,unwatched:n}){return{link:function(e,t,n){let i=t.depsTail;if(void 0!==i&&i.dep===e)return;let s=void 0!==i?i.nextDep:t.deps;if(void 0!==s&&s.dep===e){s.version=n,t.depsTail=s;return}let r=e.subsTail;if(void 0!==r&&r.version===n&&r.sub===t)return;let a=t.depsTail=e.subsTail={version:n,dep:e,sub:t,prevDep:i,nextDep:s,prevSub:r,nextSub:void 0};void 0!==s&&(s.prevDep=a),void 0!==i?i.nextDep=a:t.deps=a,void 0!==r?r.nextSub=a:e.subs=a},unlink:function(e,t=e.sub){let i=e.dep,s=e.prevDep,r=e.nextDep,a=e.nextSub,l=e.prevSub;return void 0!==r?r.prevDep=s:t.depsTail=s,void 0!==s?s.nextDep=r:t.deps=r,void 0!==a?a.prevSub=l:i.subsTail=l,void 0!==l?l.nextSub=a:void 0===(i.subs=a)&&n(i),r},propagate:function(e){let n,i=e.nextSub;e:for(;;){let s=e.sub,r=s.flags;if(r&(p.RecursedCheck|p.Recursed|p.Dirty|p.Pending)?r&(p.RecursedCheck|p.Recursed)?r&p.RecursedCheck?!(r&(p.Dirty|p.Pending))&&function(e,t){let n=t.depsTail;for(;void 0!==n;){if(n===e)return!0;n=n.prevDep}return!1}(e,s)?(s.flags=r|(p.Recursed|p.Pending),r&=p.Mutable):r=p.None:s.flags=r&~p.Recursed|p.Pending:r=p.None:s.flags=r|p.Pending,r&p.Watching&&t(s),r&p.Mutable){let t=s.subs;if(void 0!==t){let s=(e=t).nextSub;void 0!==s&&(n={value:i,prev:n},i=s);continue}}if(void 0!==(e=i)){i=e.nextSub;continue}for(;void 0!==n;)if(e=n.value,n=n.prev,void 0!==e){i=e.nextSub;continue e}break}},checkDirty:function(t,n){let s,r=0,a=!1;e:for(;;){let l=t.dep,o=l.flags;if(n.flags&p.Dirty)a=!0;else if((o&(p.Mutable|p.Dirty))==(p.Mutable|p.Dirty)){if(e(l)){let e=l.subs;void 0!==e.nextSub&&i(e),a=!0}}else if((o&(p.Mutable|p.Pending))==(p.Mutable|p.Pending)){(void 0!==t.nextSub||void 0!==t.prevSub)&&(s={value:t,prev:s}),t=l.deps,n=l,++r;continue}if(!a){let e=t.nextDep;if(void 0!==e){t=e;continue}}for(;r--;){let r=n.subs,l=void 0!==r.nextSub;if(l?(t=s.value,s=s.prev):t=r,a){if(e(n)){l&&i(r),n=t.sub;continue}a=!1}else n.flags&=~p.Pending;n=t.sub;let o=t.nextDep;if(void 0!==o){t=o;continue e}}return a}},shallowPropagate:i};function i(e){do{let n=e.sub,i=n.flags;(i&(p.Pending|p.Dirty))===p.Pending&&(n.flags=i|p.Dirty,(i&(p.Watching|p.RecursedCheck))===p.Watching&&t(n))}while(void 0!==(e=e.nextSub))}}({update:e=>e._update(),notify(e){v[C++]=e,e.flags&=~p.Watching},unwatched(e){void 0!==e.depsTail&&(e.depsTail=void 0,e.flags=p.Mutable|p.Dirty,k(e))}}),w=0,C=0;function k(e){let t=e.depsTail,n=void 0!==t?t.nextDep:e.deps;for(;void 0!==n;)n=y(n,e)}var S=class{constructor(e,n){this.atom=function(e){let n="function"==typeof e,i={_snapshot:n?void 0:e,subs:void 0,subsTail:void 0,deps:void 0,depsTail:void 0,flags:n?p.None:p.Mutable,get:()=>(void 0!==t&&b(i,t,x),i._snapshot),subscribe(e){var n;let s,r,a=g(e),l={current:!1},o=(n=()=>{i.get(),l.current?a.next?.(i._snapshot):l.current=!0},s=()=>{let e=t;t=r,++x,r.depsTail=void 0,r.flags=p.Watching|p.RecursedCheck;try{return n()}finally{t=e,r.flags&=~p.RecursedCheck,k(r)}},r={deps:void 0,depsTail:void 0,subs:void 0,subsTail:void 0,flags:p.Watching|p.RecursedCheck,notify(){let e=this.flags;e&p.Dirty||e&p.Pending&&j(this.deps,this)?s():this.flags=p.Watching},stop(){this.flags=p.None,this.depsTail=void 0,k(this)}},s(),r);return{unsubscribe:()=>{o.stop()}}},_update(s){let r=t,a=(void 0)??Object.is;if(n)t=i,++x,i.depsTail=void 0;else if(void 0===s)return!1;n&&(i.flags=p.Mutable|p.RecursedCheck);try{let t=i._snapshot,r="function"==typeof s?s(t):void 0===s&&n?e(t):s;if(void 0===t||!a(t,r))return i._snapshot=r,!0;return!1}finally{t=r,n&&(i.flags&=~p.RecursedCheck),k(i)}}};return n?(i.flags=p.Mutable|p.Dirty,i.get=function(){let e=i.flags;if(e&p.Dirty||e&p.Pending&&j(i.deps,i)){if(i._update()){let e=i.subs;void 0!==e&&E(e)}}else e&p.Pending&&(i.flags=e&~p.Pending);return void 0!==t&&b(i,t,x),i._snapshot}):i.set=function(e){if(i._update(e)){let e=i.subs;if(void 0!==e&&(_(e),E(e),1)){for(;w{this.options={...this.options,...e},this.#v()||this.cancel()},this.#x=e=>{this.store.setState(t=>{let n={...t,...e},{isPending:i}=n;return{...n,status:this.#v()?i?"pending":"idle":"disabled"}}),((e,t)=>{let n=t.key;if(n){var i,s;h.set(n,t),f.emit(e,{key:(i={...t,key:n}).key,store:{state:m("function"==typeof(s=i.store).get?s.get():s.state)},options:m(i.options)})}})("Debouncer",this)},this.#v=()=>!!u(this.options.enabled,this),this.#b=()=>u(this.options.wait,this),this.maybeExecute=(...e)=>{if(!this.#v())return;this.#x({maybeExecuteCount:this.store.state.maybeExecuteCount+1});let t=!1;this.options.leading&&this.store.state.canLeadingExecute&&(this.#x({canLeadingExecute:!1}),t=!0,this.#y(...e)),this.options.trailing&&this.#x({isPending:!0,lastArgs:e}),this.#g&&clearTimeout(this.#g),this.#g=setTimeout(()=>{this.#x({canLeadingExecute:!0}),this.options.trailing&&!t&&this.#y(...e)},this.#b())},this.#y=(...e)=>{this.#v()&&(this.fn(...e),this.#x({executionCount:this.store.state.executionCount+1,isPending:!1,lastArgs:void 0}),this.options.onExecute?.(e,this))},this.flush=()=>{this.store.state.isPending&&this.store.state.lastArgs&&(this.#_(),this.#y(...this.store.state.lastArgs))},this.#_=()=>{this.#g&&(clearTimeout(this.#g),this.#g=void 0)},this.cancel=()=>{this.#_(),this.#x({canLeadingExecute:!0,isPending:!1})},this.reset=()=>{this.#x(T())},this.key=t.key,this.options={...N,...t},this.#x(this.options.initialState??{}),this.key&&f.on("d-Debouncer",e=>{e.payload.key===this.key&&(this.#x(e.payload.store.state),this.setOptions(e.payload.options))})}#x;#v;#b;#y;#_};e.s(["useDebouncer",0,function(e,t,n=()=>({})){let a={...((0,i.useContext)(s)?.defaultOptions??{}).debouncer,...t},[l]=(0,i.useState)(()=>{let t=new O(e,a);return t.Subscribe=function(e){let n=d(t.store,e.selector,{compare:r});return"function"==typeof e.children?e.children(n):e.children},t});l.fn=e,l.setOptions(a),(0,i.useEffect)(()=>()=>{a.onUnmount?a.onUnmount(l):l.cancel()},[]);let o=d(l.store,n,{compare:r});return(0,i.useMemo)(()=>({...l,state:o}),[l,o])}],540626)},741466,e=>{"use strict";e.s(["DEBOUNCE_WAIT_MS",0,300])},399029,e=>{"use strict";var t=e.i(540626),n=e.i(271645);e.s(["useDebouncedState",0,function(e,i,s){let[r,a]=(0,n.useState)(e),l=(0,t.useDebouncer)(a,i,s);return[r,l.maybeExecute,l]}])},663435,e=>{"use strict";var t=e.i(843476),n=e.i(271645),i=e.i(199133),s=e.i(898586),r=e.i(56456),a=e.i(399029),l=e.i(785242),o=e.i(741466);let{Text:d}=s.Typography;e.s(["default",0,({value:e,onChange:s,onTeamSelect:u,disabled:c,organizationId:h,pageSize:m=20})=>{let[f,p]=(0,n.useState)(""),[g,v]=(0,a.useDebouncedState)("",{wait:o.DEBOUNCE_WAIT_MS}),{data:x,fetchNextPage:b,hasNextPage:y,isFetchingNextPage:_,isLoading:j}=(0,l.useInfiniteTeams)(m,g||void 0,h),E=(0,n.useMemo)(()=>{if(!x?.pages)return[];let e=new Set,t=[];for(let n of x.pages)for(let i of n.teams)e.has(i.team_id)||(e.add(i.team_id),t.push(i));return t},[x]);return(0,t.jsx)(i.Select,{showSearch:!0,placeholder:"Search or select a team",value:e||void 0,onChange:e=>{s?.(e??""),u&&u(e?E.find(t=>t.team_id===e)??null:null)},disabled:c,allowClear:!0,filterOption:!1,onSearch:e=>{p(e),v(e)},searchValue:f,onPopupScroll:e=>{let t=e.currentTarget;(t.scrollTop+t.clientHeight)/t.scrollHeight>=.8&&y&&!_&&b()},loading:j,notFoundContent:j?(0,t.jsx)(r.LoadingOutlined,{spin:!0}):"No teams found","data-testid":"team-dropdown",popupRender:e=>(0,t.jsxs)(t.Fragment,{children:[e,_&&(0,t.jsx)("div",{style:{textAlign:"center",padding:8},children:(0,t.jsx)(r.LoadingOutlined,{spin:!0})})]}),children:E.map(e=>(0,t.jsxs)(i.Select.Option,{value:e.team_id,children:[(0,t.jsx)("span",{className:"font-medium",children:e.team_alias})," ",(0,t.jsxs)(d,{type:"secondary",children:["(",e.team_id,")"]})]},e.team_id))})}])},737434,e=>{"use strict";var t=e.i(184163);e.s(["DownloadOutlined",()=>t.default])},59935,(e,t,n)=>{var i;let s;e.e,i=function e(){var t,n="u">typeof self?self:"u">typeof window?window:void 0!==n?n:{},i=!n.document&&!!n.postMessage,s=n.IS_PAPA_WORKER||!1,r={},a=0,l={};function o(e){this._handle=null,this._finished=!1,this._completed=!1,this._halted=!1,this._input=null,this._baseIndex=0,this._partialLine="",this._rowCount=0,this._start=0,this._nextChunk=null,this.isFirstChunk=!0,this._completeResults={data:[],errors:[],meta:{}},(function(e){var t=b(e);t.chunkSize=parseInt(t.chunkSize),e.step||e.chunk||(t.chunkSize=null),this._handle=new m(t),(this._handle.streamer=this)._config=t}).call(this,e),this.parseChunk=function(e,t){var i=parseInt(this._config.skipFirstNLines)||0;if(this.isFirstChunk&&0=this._config.preview,s)n.postMessage({results:r,workerId:l.WORKER_ID,finished:i});else if(_(this._config.chunk)&&!t){if(this._config.chunk(r,this._handle),this._handle.paused()||this._handle.aborted())return void(this._halted=!0);this._completeResults=r=void 0}return this._config.step||this._config.chunk||(this._completeResults.data=this._completeResults.data.concat(r.data),this._completeResults.errors=this._completeResults.errors.concat(r.errors),this._completeResults.meta=r.meta),this._completed||!i||!_(this._config.complete)||r&&r.meta.aborted||(this._config.complete(this._completeResults,this._input),this._completed=!0),i||r&&r.meta.paused||this._nextChunk(),r}this._halted=!0},this._sendError=function(e){_(this._config.error)?this._config.error(e):s&&this._config.error&&n.postMessage({workerId:l.WORKER_ID,error:e,finished:!1})}}function d(e){var t;(e=e||{}).chunkSize||(e.chunkSize=l.RemoteChunkSize),o.call(this,e),this._nextChunk=i?function(){this._readChunk(),this._chunkLoaded()}:function(){this._readChunk()},this.stream=function(e){this._input=e,this._nextChunk()},this._readChunk=function(){if(this._finished)this._chunkLoaded();else{if(t=new XMLHttpRequest,this._config.withCredentials&&(t.withCredentials=this._config.withCredentials),i||(t.onload=y(this._chunkLoaded,this),t.onerror=y(this._chunkError,this)),t.open(this._config.downloadRequestBody?"POST":"GET",this._input,!i),this._config.downloadRequestHeaders){var e,n,s=this._config.downloadRequestHeaders;for(n in s)t.setRequestHeader(n,s[n])}this._config.chunkSize&&(e=this._start+this._config.chunkSize-1,t.setRequestHeader("Range","bytes="+this._start+"-"+e));try{t.send(this._config.downloadRequestBody)}catch(e){this._chunkError(e.message)}i&&0===t.status&&this._chunkError()}},this._chunkLoaded=function(){let e;4===t.readyState&&(t.status<200||400<=t.status?this._chunkError():(this._start+=this._config.chunkSize||t.responseText.length,this._finished=!this._config.chunkSize||this._start>=(null!==(e=(e=t).getResponseHeader("Content-Range"))?parseInt(e.substring(e.lastIndexOf("/")+1)):-1),this.parseChunk(t.responseText)))},this._chunkError=function(e){e=t.statusText||e,this._sendError(Error(e))}}function u(e){(e=e||{}).chunkSize||(e.chunkSize=l.LocalChunkSize),o.call(this,e);var t,n,i="u">typeof FileReader;this.stream=function(e){this._input=e,n=e.slice||e.webkitSlice||e.mozSlice,i?((t=new FileReader).onload=y(this._chunkLoaded,this),t.onerror=y(this._chunkError,this)):t=new FileReaderSync,this._nextChunk()},this._nextChunk=function(){this._finished||this._config.preview&&!(this._rowCount=this._input.size,this.parseChunk(e.target.result)},this._chunkError=function(){this._sendError(t.error)}}function c(e){var t;o.call(this,e=e||{}),this.stream=function(e){return t=e,this._nextChunk()},this._nextChunk=function(){var e,n;if(!this._finished)return t=(e=this._config.chunkSize)?(n=t.substring(0,e),t.substring(e)):(n=t,""),this._finished=!t,this.parseChunk(n)}}function h(e){o.call(this,e=e||{});var t=[],n=!0,i=!1;this.pause=function(){o.prototype.pause.apply(this,arguments),this._input.pause()},this.resume=function(){o.prototype.resume.apply(this,arguments),this._input.resume()},this.stream=function(e){this._input=e,this._input.on("data",this._streamData),this._input.on("end",this._streamEnd),this._input.on("error",this._streamError)},this._checkIsFinished=function(){i&&1===t.length&&(this._finished=!0)},this._nextChunk=function(){this._checkIsFinished(),t.length?this.parseChunk(t.shift()):n=!0},this._streamData=y(function(e){try{t.push("string"==typeof e?e:e.toString(this._config.encoding)),n&&(n=!1,this._checkIsFinished(),this.parseChunk(t.shift()))}catch(e){this._streamError(e)}},this),this._streamError=y(function(e){this._streamCleanUp(),this._sendError(e)},this),this._streamEnd=y(function(){this._streamCleanUp(),i=!0,this._streamData("")},this),this._streamCleanUp=y(function(){this._input.removeListener("data",this._streamData),this._input.removeListener("end",this._streamEnd),this._input.removeListener("error",this._streamError)},this)}function m(e){var t,n,i,s,r=/^\s*-?(\d+\.?|\.\d+|\d+\.\d+)([eE][-+]?\d+)?\s*$/,a=/^((\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)))$/,o=this,d=0,u=0,c=!1,h=!1,m=[],g={data:[],errors:[],meta:{}};function v(t){return"greedy"===e.skipEmptyLines?""===t.join("").trim():1===t.length&&0===t[0].length}function x(){if(g&&i&&(j("Delimiter","UndetectableDelimiter","Unable to auto-detect delimiting character; defaulted to '"+l.DefaultDelimiter+"'"),i=!1),e.skipEmptyLines&&(g.data=g.data.filter(function(e){return!v(e)})),y()){if(g)if(Array.isArray(g.data[0])){for(var t,n=0;y()&&n(e.dynamicTypingFunction&&void 0===e.dynamicTyping[t]&&(e.dynamicTyping[t]=e.dynamicTypingFunction(t)),!0===(e.dynamicTyping[t]||e.dynamicTyping))?"true"===n||"TRUE"===n||"false"!==n&&"FALSE"!==n&&((e=>{if(r.test(e)&&-0x20000000000000<(e=parseFloat(e))&&e<0x20000000000000)return 1})(n)?parseFloat(n):a.test(n)?new Date(n):""===n?null:n):n)(l=e.header?s>=m.length?"__parsed_extra":m[s]:l,o=e.transform?e.transform(o,l):o);"__parsed_extra"===l?(i[l]=i[l]||[],i[l].push(o)):i[l]=o}return e.header&&(s>m.length?j("FieldMismatch","TooManyFields","Too many fields: expected "+m.length+" fields but parsed "+s,u+n):se.preview?n.abort():(g.data=g.data[0],s(g,o))))}),this.parse=function(s,r,a){var o=e.quoteChar||'"',o=(e.newline||(e.newline=this.guessLineEndings(s,o)),i=!1,e.delimiter?_(e.delimiter)&&(e.delimiter=e.delimiter(s),g.meta.delimiter=e.delimiter):((o=((t,n,i,s,r)=>{var a,o,d,u;r=r||[","," ","|",";",l.RECORD_SEP,l.UNIT_SEP];for(var c=0;c=n.length/2?"\r\n":"\r"}}function f(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function p(e){var t=(e=e||{}).delimiter,n=e.newline,i=e.comments,s=e.step,r=e.preview,a=e.fastMode,o=null,d=!1,u=null==e.quoteChar?'"':e.quoteChar,c=u;if(void 0!==e.escapeChar&&(c=e.escapeChar),("string"!=typeof t||-1=r)return M(!0);break}w.push({type:"Quotes",code:"InvalidQuotes",message:"Trailing quote on quoted field is malformed",row:E.length,index:h}),R++}}else if(i&&0===C.length&&l.substring(h,h+y)===i){if(-1===O)return M();h=O+b,O=l.indexOf(n,h),N=l.indexOf(t,h)}else if(-1!==N&&(N=r)return M(!0)}return A();function L(e){E.push(e),k=h}function D(e){return -1!==e&&(e=l.substring(R+1,e))&&""===e.trim()?e.length:0}function A(e){return g||(void 0===e&&(e=l.substring(h)),C.push(e),h=v,L(C),j&&B()),M()}function F(e){h=e,L(C),C=[],O=l.indexOf(n,h)}function M(i){if(e.header&&!p&&E.length&&!d){var s=E[0],r=Object.create(null),a=new Set(s);let t=!1;for(let n=0;n{if("object"==typeof t){if("string"!=typeof t.delimiter||l.BAD_DELIMITERS.filter(function(e){return -1!==t.delimiter.indexOf(e)}).length||(s=t.delimiter),("boolean"==typeof t.quotes||"function"==typeof t.quotes||Array.isArray(t.quotes))&&(n=t.quotes),"boolean"!=typeof t.skipEmptyLines&&"string"!=typeof t.skipEmptyLines||(d=t.skipEmptyLines),"string"==typeof t.newline&&(r=t.newline),"string"==typeof t.quoteChar&&(a=t.quoteChar),"boolean"==typeof t.header&&(i=t.header),Array.isArray(t.columns)){if(0===t.columns.length)throw Error("Option columns is empty");u=t.columns}void 0!==t.escapeChar&&(o=t.escapeChar+a),t.escapeFormulae instanceof RegExp?c=t.escapeFormulae:"boolean"==typeof t.escapeFormulae&&t.escapeFormulae&&(c=/^[=+\-@\t\r].*$/)}})(),RegExp(f(a),"g"));if("string"==typeof e&&(e=JSON.parse(e)),Array.isArray(e)){if(!e.length||Array.isArray(e[0]))return m(null,e,d);if("object"==typeof e[0])return m(u||Object.keys(e[0]),e,d)}else if("object"==typeof e)return"string"==typeof e.data&&(e.data=JSON.parse(e.data)),Array.isArray(e.data)&&(e.fields||(e.fields=e.meta&&e.meta.fields||u),e.fields||(e.fields=Array.isArray(e.data[0])?e.fields:"object"==typeof e.data[0]?Object.keys(e.data[0]):[]),Array.isArray(e.data[0])||"object"==typeof e.data[0]||(e.data=[e.data])),m(e.fields||[],e.data||[],d);throw Error("Unable to serialize unrecognized input");function m(e,t,n){var a="",l=("string"==typeof e&&(e=JSON.parse(e)),"string"==typeof t&&(t=JSON.parse(t)),Array.isArray(e)&&0{for(var n=0;n{"use strict";var t=e.i(271645),n=e.i(914189);e.s(["useControllable",0,function(e,i,s){let[r,a]=(0,t.useState)(s),l=void 0!==e,o=(0,t.useRef)(l),d=(0,t.useRef)(!1),u=(0,t.useRef)(!1);return!l||o.current||d.current?l||!o.current||u.current||(u.current=!0,o.current=l,console.error("A component is changing from controlled to uncontrolled. This may be caused by the value changing from a defined value to undefined, which should not happen.")):(d.current=!0,o.current=l,console.error("A component is changing from uncontrolled to controlled. This may be caused by the value changing from undefined to a defined value, which should not happen.")),[l?e:r,(0,n.useEvent)(e=>(l||a(e),null==i?void 0:i(e)))]}],503269),e.s(["useDefaultValue",0,function(e){let[n]=(0,t.useState)(e);return n}],214520);let i=(0,t.createContext)(void 0);function s(){return(0,t.useContext)(i)}e.s(["useDisabled",0,s],601893);var r=e.i(174080),a=e.i(746725);function l(e={},t=null,n=[]){for(let[i,s]of Object.entries(e))!function e(t,n,i){if(Array.isArray(i))for(let[s,r]of i.entries())e(t,o(n,s.toString()),r);else i instanceof Date?t.push([n,i.toISOString()]):"boolean"==typeof i?t.push([n,i?"1":"0"]):"string"==typeof i?t.push([n,i]):"number"==typeof i?t.push([n,`${i}`]):null==i?t.push([n,""]):l(i,n,t)}(n,o(t,i),s);return n}function o(e,t){return e?e+"["+t+"]":t}e.s(["attemptSubmit",0,function(e){var t,n;let i=null!=(t=null==e?void 0:e.form)?t:e.closest("form");if(i){for(let t of i.elements)if(t!==e&&("INPUT"===t.tagName&&"submit"===t.type||"BUTTON"===t.tagName&&"submit"===t.type||"INPUT"===t.nodeName&&"image"===t.type))return void t.click();null==(n=i.requestSubmit)||n.call(i)}},"objectToFormEntries",0,l],694421);var d=e.i(700020),u=e.i(2788);let c=(0,t.createContext)(null);function h({children:e}){let n=(0,t.useContext)(c);if(!n)return t.default.createElement(t.default.Fragment,null,e);let{target:i}=n;return i?(0,r.createPortal)(t.default.createElement(t.default.Fragment,null,e),i):null}function m({setForm:e,formId:n}){return(0,t.useEffect)(()=>{if(n){let t=document.getElementById(n);t&&e(t)}},[e,n]),n?null:t.default.createElement(u.Hidden,{features:u.HiddenFeatures.Hidden,as:"input",type:"hidden",hidden:!0,readOnly:!0,ref:t=>{if(!t)return;let n=t.closest("form");n&&e(n)}})}e.s(["FormFields",0,function({data:e,form:n,disabled:i,onReset:s,overrides:r}){let[o,c]=(0,t.useState)(null),f=(0,a.useDisposables)();return(0,t.useEffect)(()=>{if(s&&o)return f.addEventListener(o,"reset",s)},[o,n,s]),t.default.createElement(h,null,t.default.createElement(m,{setForm:c,formId:n}),l(e).map(([e,s])=>t.default.createElement(u.Hidden,{features:u.HiddenFeatures.Hidden,...(0,d.compact)({key:e,as:"input",type:"hidden",hidden:!0,readOnly:!0,form:n,disabled:i,name:e,value:s,...r})})))}],140721);let f=(0,t.createContext)(void 0);function p(){return(0,t.useContext)(f)}e.s(["useProvidedId",0,p],942803);var g=e.i(835696),v=e.i(294316);let x=(0,t.createContext)(null);x.displayName="DescriptionContext";let b=Object.assign((0,d.forwardRefWithAs)(function(e,n){let i=(0,t.useId)(),r=s(),{id:a=`headlessui-description-${i}`,...l}=e,o=function e(){let n=(0,t.useContext)(x);if(null===n){let t=Error("You used a component, but it is not inside a relevant parent.");throw Error.captureStackTrace&&Error.captureStackTrace(t,e),t}return n}(),u=(0,v.useSyncRefs)(n);(0,g.useIsoMorphicEffect)(()=>o.register(a),[a,o.register]);let c=r||!1,h=(0,t.useMemo)(()=>({...o.slot,disabled:c}),[o.slot,c]),m={ref:u,...o.props,id:a};return(0,d.useRender)()({ourProps:m,theirProps:l,slot:h,defaultTag:"p",name:o.name||"Description"})}),{});e.s(["Description",0,b,"useDescribedBy",0,function(){var e,n;return null!=(n=null==(e=(0,t.useContext)(x))?void 0:e.value)?n:void 0},"useDescriptions",0,function(){let[e,i]=(0,t.useState)([]);return[e.length>0?e.join(" "):void 0,(0,t.useMemo)(()=>function(e){let s=(0,n.useEvent)(e=>(i(t=>[...t,e]),()=>i(t=>{let n=t.slice(),i=n.indexOf(e);return -1!==i&&n.splice(i,1),n}))),r=(0,t.useMemo)(()=>({register:s,slot:e.slot,name:e.name,props:e.props,value:e.value}),[s,e.slot,e.name,e.props,e.value]);return t.default.createElement(x.Provider,{value:r},e.children)},[i])]}],35889);let y=(0,t.createContext)(null);function _(e){var n,i,s;let r=null!=(i=null==(n=(0,t.useContext)(y))?void 0:n.value)?i:void 0;return(null!=(s=null==e?void 0:e.length)?s:0)>0?[r,...e].filter(Boolean).join(" "):r}y.displayName="LabelContext";let j=Object.assign((0,d.forwardRefWithAs)(function(e,i){var r;let a=(0,t.useId)(),l=function e(){let n=(0,t.useContext)(y);if(null===n){let t=Error("You used a