From 1fbb78d2a4d8abe12bd67cf9ce3acf8710bab273 Mon Sep 17 00:00:00 2001 From: Shivam Rawat Date: Sat, 6 Jun 2026 12:35:18 -0700 Subject: [PATCH 01/40] Title: Fix managed batch cancel credential resolution (#29734) * Fix managed batch cancel credential resolution Decode unified batch IDs before cancel routing and resolve litellm_credential_name to api_key in Router._acancel_batch so JWT team-scoped deployments cancel with the same credentials used at create time Co-authored-by: Cursor * fix batch cancellation credential cleanup Co-authored-by: Cursor --------- Co-authored-by: Cursor --- litellm/proxy/batches_endpoints/endpoints.py | 49 ++++---- .../openai_files_endpoints/common_utils.py | 5 +- litellm/router.py | 7 ++ .../test_batch_x_litellm_model_encoding.py | 107 +++++++++++++++++- .../test_router_acancel_batch.py | 77 +++++++++++++ 5 files changed, 221 insertions(+), 24 deletions(-) diff --git a/litellm/proxy/batches_endpoints/endpoints.py b/litellm/proxy/batches_endpoints/endpoints.py index 85165709957..ea479a5721b 100644 --- a/litellm/proxy/batches_endpoints/endpoints.py +++ b/litellm/proxy/batches_endpoints/endpoints.py @@ -13,11 +13,9 @@ import litellm from litellm._logging import verbose_proxy_logger from litellm.batches.main import CancelBatchRequest, RetrieveBatchRequest from litellm.proxy._types import * +from litellm.proxy.common_utils.callback_utils import sanitize_openai_provider_metadata from litellm.proxy.auth.user_api_key_auth import user_api_key_auth from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing -from litellm.proxy.common_utils.callback_utils import ( - sanitize_openai_provider_metadata, -) from litellm.proxy.common_utils.http_parsing_utils import _read_request_body from litellm.proxy.common_utils.openai_endpoint_utils import ( get_custom_llm_provider_from_request_headers, @@ -28,6 +26,7 @@ from litellm.proxy.openai_files_endpoints.common_utils import ( decode_model_from_file_id, encode_batch_response_ids, encode_file_id_with_model, + get_batch_id_from_unified_batch_id, get_batch_from_database, get_credentials_for_model, get_model_id_from_unified_batch_id, @@ -109,6 +108,7 @@ async def create_batch( # noqa: PLR0915 proxy_config=proxy_config, route_type="acreate_batch", ) + data["metadata"] = sanitize_openai_provider_metadata(data.get("metadata")) ## check if model is a loadbalanced model router_model: Optional[str] = None @@ -123,9 +123,6 @@ async def create_batch( # noqa: PLR0915 or get_custom_llm_provider_from_request_headers(request=request) or "openai" ) - if isinstance(data.get("metadata"), dict): - data["metadata"] = sanitize_openai_provider_metadata(data["metadata"]) - _create_batch_data = LiteLLMBatchCreateRequest(**data) # Apply team-level batch output expiry enforcement @@ -529,10 +526,6 @@ async def retrieve_batch( # noqa: PLR0915 custom_llm_provider=custom_llm_provider, **data # type: ignore ) - response = await proxy_logging_obj.post_call_success_hook( - data=data, user_api_key_dict=user_api_key_dict, response=response - ) - # FIX: Update the database with the latest state from provider await update_batch_in_database( batch_id=batch_id, @@ -543,9 +536,19 @@ async def retrieve_batch( # noqa: PLR0915 verbose_proxy_logger=verbose_proxy_logger, db_batch_object=db_batch_object, operation="retrieve", - user_api_key_dict=user_api_key_dict, ) + ### CALL HOOKS ### - modify outgoing data + response = await proxy_logging_obj.post_call_success_hook( + data=data, user_api_key_dict=user_api_key_dict, response=response + ) + + # Fix: bug_feb14_batch_retrieve_returns_raw_input_file_id + # Resolve raw provider file IDs (input, output, error) to unified IDs. + if unified_batch_id: + await resolve_input_file_id_to_unified(response, prisma_client) + await resolve_output_file_ids_to_unified(response, prisma_client) + ### ALERTING ### asyncio.create_task( proxy_logging_obj.update_request_status( @@ -891,11 +894,19 @@ async def cancel_batch( }, ) - # Hook has already extracted model and unwrapped batch_id into data dict + model_id_from_batch = get_model_id_from_unified_batch_id(unified_batch_id) + if model_id_from_batch is None: + raise HTTPException( + status_code=400, + detail={ + "error": "Invalid LiteLLM managed batch ID. Missing model_id." + }, + ) + data["model"] = model_id_from_batch + data["batch_id"] = get_batch_id_from_unified_batch_id(unified_batch_id) response = await llm_router.acancel_batch(**data) # type: ignore response._hidden_params["unified_batch_id"] = unified_batch_id - # Ensure model_id is set for the post_call_success_hook to re-encode IDs if not response._hidden_params.get("model_id") and data.get("model"): response._hidden_params["model_id"] = data["model"] @@ -917,14 +928,10 @@ async def cancel_batch( **_cancel_batch_data, ) + # FIX: Update the database with the new cancelled state managed_files_obj = proxy_logging_obj.get_proxy_hook("managed_files") from litellm.proxy.proxy_server import prisma_client - response = await proxy_logging_obj.post_call_success_hook( - data=data, user_api_key_dict=user_api_key_dict, response=response - ) - - # FIX: Update the database with the new cancelled state await update_batch_in_database( batch_id=batch_id, unified_batch_id=unified_batch_id, @@ -933,7 +940,11 @@ async def cancel_batch( prisma_client=prisma_client, verbose_proxy_logger=verbose_proxy_logger, operation="cancel", - user_api_key_dict=user_api_key_dict, + ) + + ### CALL HOOKS ### - modify outgoing data + response = await proxy_logging_obj.post_call_success_hook( + data=data, user_api_key_dict=user_api_key_dict, response=response ) ### ALERTING ### diff --git a/litellm/proxy/openai_files_endpoints/common_utils.py b/litellm/proxy/openai_files_endpoints/common_utils.py index 0415bb456ec..cc0d06e4f40 100644 --- a/litellm/proxy/openai_files_endpoints/common_utils.py +++ b/litellm/proxy/openai_files_endpoints/common_utils.py @@ -79,9 +79,10 @@ def get_batch_id_from_unified_batch_id(file_id: str) -> str: if not isinstance(file_id, str): return "" if "llm_batch_id" in file_id: - return file_id.split("llm_batch_id:")[1].split(",")[0] + batch_id = file_id.split("llm_batch_id:", 1)[1] else: - return file_id.split("generic_response_id:")[1].split(",")[0] + batch_id = file_id.split("generic_response_id:", 1)[1] + return re.split(r"[;,]", batch_id, maxsplit=1)[0] def encode_file_id_with_model( diff --git a/litellm/router.py b/litellm/router.py index a92590d3dba..d0f4e5ff44d 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -5561,7 +5561,14 @@ class Router: request_kwargs=kwargs, ) + selected_deployment_id = (deployment.get("model_info") or {}).get("id") data = deployment["litellm_params"].copy() + resolved_credentials = self.get_deployment_credentials_with_provider( + model_id=selected_deployment_id or model + ) + if resolved_credentials is not None: + data.update(resolved_credentials) + data.pop("litellm_credential_name", None) model_name = data["model"] self._update_kwargs_with_deployment( deployment=deployment, kwargs=kwargs, function_name="_acancel_batch" diff --git a/tests/litellm/proxy/test_batch_x_litellm_model_encoding.py b/tests/litellm/proxy/test_batch_x_litellm_model_encoding.py index 1d498b48ca0..49e0498f140 100644 --- a/tests/litellm/proxy/test_batch_x_litellm_model_encoding.py +++ b/tests/litellm/proxy/test_batch_x_litellm_model_encoding.py @@ -5,14 +5,15 @@ Verifies that create_batch encodes response IDs with model info so that retrieve_batch can route back to the correct provider/credentials. """ +import base64 from typing import Optional from unittest.mock import AsyncMock, MagicMock, patch import pytest -import litellm from litellm.proxy.openai_files_endpoints.common_utils import ( decode_model_from_file_id, + get_batch_id_from_unified_batch_id, get_original_file_id, ) from litellm.types.utils import LiteLLMBatch @@ -30,6 +31,11 @@ def _make_mock_request(headers: dict) -> MagicMock: return mock_request +def _make_unified_batch_id(model_id: str, batch_id: str) -> str: + decoded_id = f"litellm_proxy;model_id:{model_id};llm_batch_id:{batch_id}" + return base64.urlsafe_b64encode(decoded_id.encode()).decode().rstrip("=") + + def _make_batch_response( batch_id: str = "batch_abc123", input_file_id: str = "file-input456", @@ -51,6 +57,15 @@ def _make_batch_response( ) +def test_get_batch_id_from_unified_batch_id_handles_appended_fields(): + decoded_id = ( + "litellm_proxy;model_id:deployment-123;" + "llm_batch_id:batch_openai_123;llm_output_file_id:file-output" + ) + + assert get_batch_id_from_unified_batch_id(decoded_id) == "batch_openai_123" + + @pytest.mark.asyncio async def test_create_batch_with_x_litellm_model_encodes_batch_id(): """ @@ -68,6 +83,7 @@ async def test_create_batch_with_x_litellm_model_encodes_batch_id(): mock_user_api_key_dict = MagicMock() mock_user_api_key_dict.parent_otel_span = None mock_user_api_key_dict.user_id = "test_user" + mock_user_api_key_dict.team_metadata = {} mock_credentials = { "api_key": "sk-test", @@ -83,6 +99,11 @@ async def test_create_batch_with_x_litellm_model_encodes_batch_id(): "input_file_id": "file-input456", "endpoint": "/v1/chat/completions", "completion_window": "24h", + "metadata": { + "customer_id": "cust-123", + "applied_guardrails": ["pii"], + "attempt": 1, + }, } ), ), @@ -98,8 +119,8 @@ async def test_create_batch_with_x_litellm_model_encodes_batch_id(): ), patch( "litellm.acreate_batch", - new=AsyncMock(return_value=mock_response), - ), + new_callable=AsyncMock, + ) as mock_create_batch, patch( "litellm.proxy.batches_endpoints.endpoints.is_known_model", return_value=False, @@ -116,6 +137,7 @@ async def test_create_batch_with_x_litellm_model_encodes_batch_id(): ), ), ): + mock_create_batch.return_value = mock_response # Setup the mock processor to return data and logging obj mock_processor = MagicMock() mock_processor.common_processing_pre_call_logic = AsyncMock( @@ -124,6 +146,11 @@ async def test_create_batch_with_x_litellm_model_encodes_batch_id(): "input_file_id": "file-input456", "endpoint": "/v1/chat/completions", "completion_window": "24h", + "metadata": { + "customer_id": "cust-123", + "applied_guardrails": ["pii"], + "attempt": 1, + }, }, MagicMock(), ) @@ -155,6 +182,7 @@ async def test_create_batch_with_x_litellm_model_encodes_batch_id(): assert ( original_id == raw_batch_id ), f"Expected original ID '{raw_batch_id}', got: {original_id}" + assert mock_create_batch.call_args.kwargs["metadata"] == {"customer_id": "cust-123"} @pytest.mark.asyncio @@ -180,6 +208,7 @@ async def test_create_batch_with_x_litellm_model_encodes_output_and_error_file_i mock_user_api_key_dict = MagicMock() mock_user_api_key_dict.parent_otel_span = None mock_user_api_key_dict.user_id = "test_user" + mock_user_api_key_dict.team_metadata = {} mock_credentials = { "api_key": "sk-test", @@ -272,6 +301,7 @@ async def test_create_batch_without_x_litellm_model_returns_raw_ids(): mock_user_api_key_dict = MagicMock() mock_user_api_key_dict.parent_otel_span = None mock_user_api_key_dict.user_id = "test_user" + mock_user_api_key_dict.team_metadata = {} with ( patch( @@ -384,3 +414,74 @@ class TestBatchIdRoundTripWithRetrieve: assert encoded.startswith("batch_") assert decode_model_from_file_id(encoded) == model assert get_original_file_id(encoded) == raw_id + + +@pytest.mark.asyncio +async def test_cancel_batch_with_unified_id_routes_with_decoded_model_and_batch_id(): + from litellm.proxy.batches_endpoints.endpoints import cancel_batch + + model_id = "deployment-123" + raw_batch_id = "batch_openai_123" + unified_batch_id = _make_unified_batch_id( + model_id=model_id, batch_id=raw_batch_id + ) + mock_response = _make_batch_response(batch_id=raw_batch_id, status="cancelled") + mock_response._hidden_params = {} + mock_router = MagicMock() + mock_router.acancel_batch = AsyncMock(return_value=mock_response) + mock_request = _make_mock_request(headers={}) + mock_request.url.path = f"/v1/batches/{unified_batch_id}/cancel" + mock_fastapi_response = MagicMock() + mock_fastapi_response.headers = {} + mock_user_api_key_dict = MagicMock() + mock_user_api_key_dict.parent_otel_span = None + mock_user_api_key_dict.user_id = "test_user" + mock_user_api_key_dict.allowed_model_region = None + mock_user_api_key_dict.team_metadata = {} + + with ( + patch( + "litellm.proxy.batches_endpoints.endpoints.ProxyBaseLLMRequestProcessing" + ) as mock_processor_cls, + patch( + "litellm.proxy.batches_endpoints.endpoints.update_batch_in_database", + new=AsyncMock(), + ), + patch( + "litellm.proxy.proxy_server.add_litellm_data_to_request", + new=AsyncMock(side_effect=lambda data, **_: data), + ), + patch("litellm.proxy.proxy_server.general_settings", {}), + patch("litellm.proxy.proxy_server.llm_router", mock_router), + patch("litellm.proxy.proxy_server.proxy_config", MagicMock()), + patch("litellm.proxy.proxy_server.version", "1.0.0"), + patch("litellm.proxy.proxy_server.prisma_client", None), + patch( + "litellm.proxy.proxy_server.proxy_logging_obj", + MagicMock( + get_proxy_hook=MagicMock(return_value=None), + post_call_success_hook=AsyncMock(return_value=mock_response), + post_call_failure_hook=AsyncMock(), + update_request_status=AsyncMock(), + ), + ), + ): + mock_processor = MagicMock() + mock_processor.common_processing_pre_call_logic = AsyncMock( + return_value=({"batch_id": unified_batch_id}, MagicMock()) + ) + mock_processor_cls.return_value = mock_processor + + response = await cancel_batch( + request=mock_request, + batch_id=unified_batch_id, + fastapi_response=mock_fastapi_response, + provider=None, + user_api_key_dict=mock_user_api_key_dict, + ) + + mock_router.acancel_batch.assert_awaited_once() + cancel_kwargs = mock_router.acancel_batch.await_args.kwargs + assert cancel_kwargs["model"] == model_id + assert cancel_kwargs["batch_id"] == raw_batch_id + assert response._hidden_params["model_id"] == model_id diff --git a/tests/router_unit_tests/test_router_acancel_batch.py b/tests/router_unit_tests/test_router_acancel_batch.py index b364a667529..016da592e94 100644 --- a/tests/router_unit_tests/test_router_acancel_batch.py +++ b/tests/router_unit_tests/test_router_acancel_batch.py @@ -13,6 +13,7 @@ import pytest from unittest.mock import patch, AsyncMock, MagicMock from litellm import Router import litellm +from litellm.types.utils import CredentialItem @pytest.fixture @@ -52,3 +53,79 @@ async def test_router_acancel_batch(router): assert mock_cancel.called assert response.id == "batch_123" assert response.status == "cancelled" + + +@pytest.mark.asyncio +async def test_router_acancel_batch_resolves_credential_name(): + litellm.credential_list = [ + CredentialItem( + credential_name="openai-test-credential", + credential_info={"custom_llm_provider": "openai"}, + credential_values={"api_key": "resolved-openai-key"}, + ) + ] + router = Router( + model_list=[ + { + "model_name": "gpt-5.5", + "litellm_params": { + "model": "openai/gpt-5.5", + "litellm_credential_name": "openai-test-credential", + }, + } + ] + ) + mock_response = MagicMock() + mock_response.id = "batch_123" + mock_response.status = "cancelled" + + try: + with patch.object( + litellm, "acancel_batch", new_callable=AsyncMock + ) as mock_cancel: + mock_cancel.return_value = mock_response + + await router.acancel_batch( + model="gpt-5.5", + batch_id="batch_123", + ) + + call_kwargs = mock_cancel.call_args.kwargs + assert call_kwargs["api_key"] == "resolved-openai-key" + assert "litellm_credential_name" not in call_kwargs + finally: + litellm.credential_list = [] + + +@pytest.mark.asyncio +async def test_router_acancel_batch_removes_unresolved_credential_name(): + router = Router( + model_list=[ + { + "model_name": "gpt-5.5", + "litellm_params": { + "model": "openai/gpt-5.5", + "litellm_credential_name": "missing-openai-credential", + }, + } + ] + ) + mock_response = MagicMock() + mock_response.id = "batch_123" + mock_response.status = "cancelled" + + with ( + patch.object( + router, "get_deployment_credentials_with_provider", return_value=None + ), + patch.object(litellm, "acancel_batch", new_callable=AsyncMock) as mock_cancel, + ): + mock_cancel.return_value = mock_response + + await router.acancel_batch( + model="gpt-5.5", + batch_id="batch_123", + ) + + call_kwargs = mock_cancel.call_args.kwargs + assert "litellm_credential_name" not in call_kwargs From fdade8a84ed2d5f06d18a558a2404aa84c1aa0e3 Mon Sep 17 00:00:00 2001 From: Shivam Rawat Date: Sat, 6 Jun 2026 12:36:05 -0700 Subject: [PATCH 02/40] Title: fix(proxy): resolve vector store file list credentials from team deployments (#29739) * fix(proxy): resolve vector store file list credentials from team deployments GET /v1/vector_stores/{id}/files now uses the same router credential routing as POST, including JWT team model hints and wildcard model selectors, so list requests no longer call OpenAI with Bearer None. Co-authored-by: Cursor * fix(proxy): authorize model hints and fix credential routing for vector store file list Resolves three review findings on the vector store file list path. Authorize user-controlled model hints (?model= query param and the x-litellm-model header) against the key's and team's allowed models via can_key_call_model / _can_object_call_model before any deployment credentials are resolved, closing a model access bypass where a normal key could file-list using a restricted deployment's provider credentials. Run the managed vector store registry resolution before the model routing hint so the managed store sets the routing model first; the hint resolver then selects credentials matching that model instead of a team fallback deployment, avoiding a credential/model mismatch across deployments. Skip team-fallback deployments whose provider cannot be determined instead of treating them as OpenAI, so a deployment without an explicit custom_llm_provider or "openai/" prefix no longer has its credentials injected. * fix(proxy): enforce vector store file model auth Ensure vector store file listing routes authorize explicit and inferred model routing before resolving deployment credentials. Co-authored-by: Cursor * fix(proxy): type guard vector store model hints Keep vector store model hint authorization typed to string-only values so static checks pass. Co-authored-by: Cursor --------- Co-authored-by: Cursor --- .../vector_store_files_endpoints/endpoints.py | 150 +++++++++ .../test_vector_store_endpoints.py | 306 ++++++++++++++++++ .../test_vector_store_tenant_guard.py | 69 ++++ 3 files changed, 525 insertions(+) diff --git a/litellm/proxy/vector_store_files_endpoints/endpoints.py b/litellm/proxy/vector_store_files_endpoints/endpoints.py index 346a847c5dd..f6ceae39779 100644 --- a/litellm/proxy/vector_store_files_endpoints/endpoints.py +++ b/litellm/proxy/vector_store_files_endpoints/endpoints.py @@ -5,6 +5,7 @@ from fastapi.responses import ORJSONResponse import litellm from litellm.proxy._types import UserAPIKeyAuth +from litellm.proxy.auth.auth_checks import _can_object_call_model, can_key_call_model from litellm.proxy.auth.user_api_key_auth import user_api_key_auth from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing from litellm.proxy.common_utils.openai_endpoint_utils import ( @@ -191,6 +192,148 @@ def _replace_file_id_in_response(response, original_file_id: str): return response +async def _authorize_model_routing_hint( + *, + model: str, + llm_router: Optional["Router"], + user_api_key_dict: Optional[UserAPIKeyAuth], +) -> None: + if user_api_key_dict is None: + return + + key_models = getattr(user_api_key_dict, "models", None) + if not (isinstance(key_models, list) and "all-team-models" in key_models): + await can_key_call_model( + model=model, + llm_model_list=None, + valid_token=user_api_key_dict, + llm_router=llm_router, + ) + + team_models = getattr(user_api_key_dict, "team_models", None) + if isinstance(team_models, list) and len(team_models) > 0: + _can_object_call_model( + model=model, + llm_router=llm_router, + models=team_models, + team_model_aliases=user_api_key_dict.team_model_aliases, + team_id=user_api_key_dict.team_id, + object_type="team", + ) + + +async def _update_request_data_with_model_routing_hint( + data: Dict, + request: Request, + llm_router: Optional["Router"] = None, + user_api_key_dict: Optional[UserAPIKeyAuth] = None, +) -> Dict: + if data.get("api_key") is not None or data.get("api_base") is not None: + return data + + user_controlled_model_hint = request.query_params.get( + "model" + ) or request.headers.get("x-litellm-model") + model_hint = data.get("model") or user_controlled_model_hint + should_authorize_model_hint = ( + isinstance(model_hint, str) and model_hint == user_controlled_model_hint + ) + + should_route = False + credentials = None + if isinstance(model_hint, str) and "*" in model_hint: + if llm_router is not None: + if should_authorize_model_hint: + await _authorize_model_routing_hint( + model=model_hint, + llm_router=llm_router, + user_api_key_dict=user_api_key_dict, + ) + credentials = llm_router.get_deployment_credentials_with_provider( + model_id=model_hint + ) + should_route = credentials is not None + else: + if isinstance(model_hint, str) and should_authorize_model_hint: + await _authorize_model_routing_hint( + model=model_hint, + llm_router=llm_router, + user_api_key_dict=user_api_key_dict, + ) + ( + should_route, + _model_used, + _original_file_id, + credentials, + ) = handle_model_based_routing( + file_id="", + request=request, + llm_router=llm_router, + data=data, + check_file_id_encoding=False, + ) + + if should_route and credentials is not None: + prepare_data_with_credentials( + data=data, + credentials=credentials, + ) + return data + + if llm_router is None or user_api_key_dict is None: + return data + + team_models = getattr(user_api_key_dict, "team_models", None) or [] + if not isinstance(team_models, list): + return data + + model_names_to_check = [] + for model_name in team_models: + if not isinstance(model_name, str) or model_name in { + "all-team-models", + "all-proxy-models", + "no-default-models", + }: + continue + model_names_to_check.append(model_name) + + openai_credentials = None + for model_name in model_names_to_check: + credentials = llm_router.get_deployment_credentials_with_provider( + model_id=model_name + ) + if credentials is None: + continue + + provider = credentials.get("custom_llm_provider") + model = credentials.get("model") + if provider is None and isinstance(model, str) and "/" in model: + provider = model.split("/", 1)[0] + if provider != LlmProviders.OPENAI.value: + continue + + await _authorize_model_routing_hint( + model=model_name, + llm_router=llm_router, + user_api_key_dict=user_api_key_dict, + ) + if openai_credentials is not None: + return data + openai_credentials = credentials + + if openai_credentials is not None: + prepare_data_with_credentials(data=data, credentials=openai_credentials) + elif len(model_names_to_check) == 1: + await _authorize_model_routing_hint( + model=model_names_to_check[0], + llm_router=llm_router, + user_api_key_dict=user_api_key_dict, + ) + data["model"] = model_names_to_check[0] + + return data + + def _update_request_data_with_litellm_managed_vector_store_registry( data: Dict, vector_store_id: str, @@ -488,6 +631,13 @@ async def vector_store_file_list( should_lookup_registry=False, ) + data = await _update_request_data_with_model_routing_hint( + data=data, + request=request, + llm_router=llm_router, + user_api_key_dict=user_api_key_dict, + ) + provider_enum = await _resolve_provider(data=data, request=request) _maybe_check_permissions( diff --git a/tests/test_litellm/proxy/vector_store_endpoints/test_vector_store_endpoints.py b/tests/test_litellm/proxy/vector_store_endpoints/test_vector_store_endpoints.py index 882081b33c3..1434dd6b1b2 100644 --- a/tests/test_litellm/proxy/vector_store_endpoints/test_vector_store_endpoints.py +++ b/tests/test_litellm/proxy/vector_store_endpoints/test_vector_store_endpoints.py @@ -21,6 +21,9 @@ from litellm.proxy.vector_store_endpoints.endpoints import ( _update_request_data_with_litellm_managed_vector_store_registry, index_create, ) +from litellm.proxy.vector_store_files_endpoints.endpoints import ( + _update_request_data_with_model_routing_hint, +) from litellm.proxy.vector_store_endpoints.management_endpoints import ( _check_vector_store_access, _resolve_embedding_config, @@ -144,6 +147,309 @@ def test_router_vector_store_file_delete_passes_correct_args(): assert call_kwargs["custom_llm_provider"] == "openai" +@pytest.mark.asyncio +async def test_vector_store_file_list_resolves_credentials_from_model_query_param(): + request = MagicMock(spec=Request) + request.query_params = {"model": "team-openai"} + request.headers = {} + + llm_router = MagicMock() + llm_router.get_deployment_credentials_with_provider.return_value = { + "api_key": "sk-team-openai", + "api_base": "https://api.openai.com/v1", + "custom_llm_provider": "openai", + "model": "openai/gpt-4o-mini", + } + + data = { + "vector_store_id": "vs_123", + "limit": "20", + } + + result = await _update_request_data_with_model_routing_hint( + data=data, + request=request, + llm_router=llm_router, + ) + + assert result["api_key"] == "sk-team-openai" + assert result["api_base"] == "https://api.openai.com/v1" + assert result["model"] == "openai/gpt-4o-mini" + assert "custom_llm_provider" not in result + llm_router.get_deployment_credentials_with_provider.assert_called_once_with( + model_id="team-openai" + ) + + +@pytest.mark.asyncio +async def test_vector_store_file_list_resolves_single_openai_team_deployment(): + request = MagicMock(spec=Request) + request.query_params = {} + request.headers = {} + + llm_router = MagicMock() + llm_router.get_deployment_credentials_with_provider.return_value = { + "api_key": "sk-team-openai", + "api_base": "https://api.openai.com/v1", + "custom_llm_provider": "openai", + "model": "openai/gpt-4o-mini", + } + + data = {"vector_store_id": "vs_123"} + user_api_key_dict = UserAPIKeyAuth(team_models=["team-openai"]) + + result = await _update_request_data_with_model_routing_hint( + data=data, + request=request, + llm_router=llm_router, + user_api_key_dict=user_api_key_dict, + ) + + assert result["api_key"] == "sk-team-openai" + assert result["api_base"] == "https://api.openai.com/v1" + assert result["model"] == "openai/gpt-4o-mini" + assert "custom_llm_provider" not in result + llm_router.get_deployment_credentials_with_provider.assert_called_once_with( + model_id="team-openai" + ) + + +@pytest.mark.asyncio +async def test_vector_store_file_list_wildcard_model_hint_falls_back_to_team_deployment(): + request = MagicMock(spec=Request) + request.query_params = {"model": "openai/*"} + request.headers = {} + + llm_router = MagicMock() + llm_router.model_group_alias = {} + llm_router.get_deployment_credentials_with_provider.side_effect = [ + None, + None, + { + "api_key": "sk-team-openai", + "api_base": "https://api.openai.com/v1", + "custom_llm_provider": "openai", + "model": "openai/gpt-4o-mini", + }, + ] + + data = {"vector_store_id": "vs_123", "model": "openai/*"} + user_api_key_dict = UserAPIKeyAuth(team_models=["openai/*", "team-openai"]) + + result = await _update_request_data_with_model_routing_hint( + data=data, + request=request, + llm_router=llm_router, + user_api_key_dict=user_api_key_dict, + ) + + assert result["api_key"] == "sk-team-openai" + assert result["api_base"] == "https://api.openai.com/v1" + assert result["model"] == "openai/gpt-4o-mini" + assert "custom_llm_provider" not in result + assert llm_router.get_deployment_credentials_with_provider.call_count == 3 + + +@pytest.mark.asyncio +async def test_vector_store_file_list_authorizes_wildcard_query_param_before_credentials(): + from litellm.proxy.auth.auth_checks import ProxyException + + request = MagicMock(spec=Request) + request.query_params = {"model": "openai/*"} + request.headers = {} + + llm_router = MagicMock() + llm_router.model_group_alias = {} + data = {"vector_store_id": "vs_123"} + user_api_key_dict = UserAPIKeyAuth( + models=["restricted-deployment"], + team_models=["openai/*"], + ) + + with pytest.raises(ProxyException): + await _update_request_data_with_model_routing_hint( + data=data, + request=request, + llm_router=llm_router, + user_api_key_dict=user_api_key_dict, + ) + + llm_router.get_deployment_credentials_with_provider.assert_not_called() + + +@pytest.mark.asyncio +async def test_vector_store_file_list_uses_single_team_model_for_router_routing(): + request = MagicMock(spec=Request) + request.query_params = {} + request.headers = {} + + llm_router = MagicMock() + llm_router.get_model_access_groups.return_value = {} + llm_router.get_deployment_credentials_with_provider.return_value = None + + data = {"vector_store_id": "vs_123"} + user_api_key_dict = UserAPIKeyAuth( + team_id="team-123", + team_models=["provider/*", "all-proxy-models"], + ) + + result = await _update_request_data_with_model_routing_hint( + data=data, + request=request, + llm_router=llm_router, + user_api_key_dict=user_api_key_dict, + ) + + assert result["model"] == "provider/*" + assert "api_key" not in result + assert "api_base" not in result + + +@pytest.mark.asyncio +async def test_vector_store_file_list_authorizes_inferred_team_model(): + from litellm.proxy.auth.auth_checks import ProxyException + + request = MagicMock(spec=Request) + request.query_params = {} + request.headers = {} + + llm_router = MagicMock() + llm_router.model_group_alias = {} + llm_router.get_deployment_credentials_with_provider.return_value = { + "api_key": "sk-team-openai", + "api_base": "https://api.openai.com/v1", + "custom_llm_provider": "openai", + "model": "openai/gpt-4o-mini", + } + + data = {"vector_store_id": "vs_123"} + user_api_key_dict = UserAPIKeyAuth( + models=["restricted-deployment"], + team_models=["team-openai"], + ) + + with pytest.raises(ProxyException): + await _update_request_data_with_model_routing_hint( + data=data, + request=request, + llm_router=llm_router, + user_api_key_dict=user_api_key_dict, + ) + + +@pytest.mark.asyncio +async def test_vector_store_file_list_does_not_guess_ambiguous_team_deployment(): + request = MagicMock(spec=Request) + request.query_params = {} + request.headers = {} + + llm_router = MagicMock() + llm_router.get_deployment_credentials_with_provider.side_effect = [ + { + "api_key": "sk-team-openai-1", + "custom_llm_provider": "openai", + "model": "openai/gpt-4o-mini", + }, + { + "api_key": "sk-team-openai-2", + "custom_llm_provider": "openai", + "model": "openai/gpt-4.1-mini", + }, + ] + + data = {"vector_store_id": "vs_123"} + user_api_key_dict = UserAPIKeyAuth(team_models=["team-openai-1", "team-openai-2"]) + + result = await _update_request_data_with_model_routing_hint( + data=data, + request=request, + llm_router=llm_router, + user_api_key_dict=user_api_key_dict, + ) + + assert "api_key" not in result + assert "api_base" not in result + assert llm_router.get_deployment_credentials_with_provider.call_count == 2 + + +@pytest.mark.asyncio +async def test_vector_store_file_list_does_not_override_existing_credentials(): + request = MagicMock(spec=Request) + request.query_params = {"model": "team-openai"} + request.headers = {} + + llm_router = MagicMock() + data = { + "vector_store_id": "vs_123", + "api_key": "sk-explicit", + "api_base": "https://example.com/v1", + } + + result = await _update_request_data_with_model_routing_hint( + data=data, + request=request, + llm_router=llm_router, + ) + + assert result["api_key"] == "sk-explicit" + assert result["api_base"] == "https://example.com/v1" + llm_router.get_deployment_credentials_with_provider.assert_not_called() + + +@pytest.mark.asyncio +async def test_vector_store_file_list_requires_explicit_openai_provider_for_team_fallback(): + request = MagicMock(spec=Request) + request.query_params = {} + request.headers = {} + + llm_router = MagicMock() + llm_router.get_deployment_credentials_with_provider.return_value = { + "api_key": "sk-unknown-provider", + "api_base": "https://example.com/v1", + "model": "gpt-4o", + } + + data = {"vector_store_id": "vs_123"} + user_api_key_dict = UserAPIKeyAuth(team_models=["team-deployment"]) + + result = await _update_request_data_with_model_routing_hint( + data=data, + request=request, + llm_router=llm_router, + user_api_key_dict=user_api_key_dict, + ) + + assert "api_key" not in result + assert "api_base" not in result + + +@pytest.mark.asyncio +async def test_vector_store_file_list_authorizes_model_query_param_before_credentials(): + from litellm.proxy.auth.auth_checks import ProxyException + + request = MagicMock(spec=Request) + request.query_params = {"model": "restricted-deployment"} + request.headers = {} + + llm_router = MagicMock() + llm_router.model_group_alias = {} + data = {"vector_store_id": "vs_123"} + user_api_key_dict = UserAPIKeyAuth( + models=["allowed-deployment"], + team_models=["allowed-deployment"], + ) + + with pytest.raises(ProxyException): + await _update_request_data_with_model_routing_hint( + data=data, + request=request, + llm_router=llm_router, + user_api_key_dict=user_api_key_dict, + ) + + llm_router.get_deployment_credentials_with_provider.assert_not_called() + + @pytest.mark.asyncio async def test_update_request_data_with_litellm_managed_vector_store_registry(): """ diff --git a/tests/test_litellm/proxy/vector_store_endpoints/test_vector_store_tenant_guard.py b/tests/test_litellm/proxy/vector_store_endpoints/test_vector_store_tenant_guard.py index 48262afd363..b1bd7ccbf0f 100644 --- a/tests/test_litellm/proxy/vector_store_endpoints/test_vector_store_tenant_guard.py +++ b/tests/test_litellm/proxy/vector_store_endpoints/test_vector_store_tenant_guard.py @@ -106,6 +106,75 @@ async def test_vector_store_file_create_forces_path_id_over_body_id(): ) +@pytest.mark.asyncio +async def test_vector_store_file_list_resolves_managed_vector_store_before_team_fallback(): + import base64 + + from litellm.proxy.vector_store_files_endpoints.endpoints import ( + vector_store_file_list, + ) + + captured_data = {} + + async def fake_base_process(self, **kwargs): + captured_data.update(self.data) + return {"ok": True} + + raw_vector_store_id = ( + "litellm_proxy:vector_store;" + "unified_id,managed-vs;" + "target_model_names,managed-deployment;" + "provider_resource_id,vs_provider_native;" + "model_id,managed-deployment" + ) + vector_store_id = ( + base64.urlsafe_b64encode(raw_vector_store_id.encode()).decode().rstrip("=") + ) + + request = _mock_request() + request.method = "GET" + request.query_params = {"limit": "10"} + request.url.path = f"/v1/vector_stores/{vector_store_id}/files" + + llm_router = MagicMock() + + def get_credentials(model_id): + return { + "api_key": f"sk-{model_id}", + "api_base": "https://api.openai.com/v1", + "custom_llm_provider": "openai", + "model": f"openai/{model_id}", + } + + llm_router.get_deployment_credentials_with_provider.side_effect = get_credentials + + with ( + patch( + "litellm.proxy.vector_store_files_endpoints.endpoints.assert_user_can_access_vector_store_id", + new=AsyncMock(return_value=None), + ), + patch("litellm.proxy.proxy_server.llm_router", llm_router), + patch( + "litellm.proxy.vector_store_files_endpoints.endpoints.ProxyBaseLLMRequestProcessing.base_process_llm_request", + new=fake_base_process, + ), + ): + response = await vector_store_file_list( + vector_store_id=vector_store_id, + request=request, + fastapi_response=Response(), + user_api_key_dict=UserAPIKeyAuth(team_models=["team-openai"]), + ) + + assert response == {"ok": True} + assert captured_data["vector_store_id"] == "vs_provider_native" + assert captured_data["api_key"] == "sk-managed-deployment" + assert captured_data["model"] == "openai/managed-deployment" + llm_router.get_deployment_credentials_with_provider.assert_called_once_with( + model_id="managed-deployment" + ) + + @pytest.mark.asyncio async def test_vector_store_file_create_denies_other_team_path_store(): from litellm.proxy.vector_store_files_endpoints.endpoints import ( From 1cff02f50ebefbc43ab6c13542f27fc662fe7e38 Mon Sep 17 00:00:00 2001 From: Yassin Kortam Date: Sat, 6 Jun 2026 12:57:44 -0700 Subject: [PATCH 03/40] =?UTF-8?q?refactor:=20convert=20AWS=20and=20GCP=20T?= =?UTF-8?q?erraform=20stacks=20into=20reusable=20modules=20=E2=80=A6=20(#2?= =?UTF-8?q?8103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: convert AWS and GCP Terraform stacks into reusable modules with examples/default entry point - Remove `provider` blocks from both AWS and GCP stack roots so the modules can be consumed with `count`, `for_each`, `depends_on`, assumed-role or aliased providers — patterns that are forbidden when a module owns its own provider configuration - Add `examples/default/` thin-root wrappers for both stacks that wire the provider (AWS) / providers (google + google-beta) and call the module with a curated variable surface, preserving the one-command deploy experience - Move `terraform.tfvars.example` files into `examples/default/` alongside the new roots; update example comments to reflect the curated variable surface - Thread `local.tags` (containing `litellm:stack`, `managed-by`, and `var.tags`) explicitly onto every taggable AWS resource since the module no longer controls the provider's `default_tags`; GCP resource labels already flow through the module's `labels` input - Add `examples/default/variables.tf` and `outputs.tf` for both stacks, exposing the most-used knobs and re-exporting all module outputs - Commit provider lock files for both examples so `terraform init` is reproducible without a network fetch - Update top-level and per-stack READMEs to document the module-first design, the `for_each` multi-tenant pattern, and the `examples/default/` quick-start path * docs(terraform): address review — state-migration guide, tag dedupe, for_each note - Add 'Migrating an existing deployment' section to AWS & GCP READMEs documenting the required terraform state mv step (resource addresses now gain a module.litellm. prefix under the examples/default root) - Remove redundant managed-by tag from the AWS example providers.tf; reserve default_tags there for org-wide tags only - Document the for_each single-provider limitation for GCP (no configuration_aliases) in the README and example main.tf Resolves LIT-3504 * docs(terraform/gcp): note expected SSL cert replacement in state-migration guide The managed SSL cert is named with a hash of lb_domains, so TLS-enabled stacks that migrated from the old un-hashed name will see one create_before_destroy cert replacement after terraform state mv — not a clean 'No changes'. Document that this single replacement is expected and safe. * docs(terraform): drop state-migration guides The AWS/GCP stacks have never been published, so there are no existing deployments to migrate from the old root-module layout. Remove the 'Migrating an existing deployment' sections from both READMEs. * docs(terraform): call out image-registry override required for GCP 1-click The GCP stack's default image_registry points at ghcr.io, which Cloud Run won't authenticate against, so any real deploy (HCP Terraform no-code or otherwise) must override it. Document that as a hard requirement on the GCP README rather than a side note, and add a top-level HCP Terraform 1-click section enumerating the required inputs per stack and the migration-task caveat for HCP-hosted runners. * feat(terraform/aws): mount proxy_config from S3 and wire OpenTelemetry v2 proxy_config Drop the inline LITELLM_PROXY_CONFIG_B64 env var. Upload the YAML to S3 at config/litellm-config.yaml; gateway and backend container entrypoints download it to /tmp/litellm-config.yaml via boto3 before exec'ing uvicorn. The S3 object etag is wired into the task definition so a config edit produces a new task-def revision and a rolling redeploy. The existing s3_access policy already grants the task role s3:GetObject on this bucket, so no IAM changes were needed for the mount itself. OpenTelemetry v2 New variables otel_endpoint, otel_exporter, otel_service_name, and otel_headers_secret_arn. Setting otel_endpoint to a non-empty value adds LITELLM_OTEL_V2=true plus OTEL_EXPORTER / OTEL_ENDPOINT / OTEL_SERVICE_NAME / OTEL_ENVIRONMENT_NAME to the shared env block; an optional Secrets Manager ARN backs OTEL_HEADERS for collectors that need an auth header. Execution role auto-gains GetSecretValue on that ARN. Empty endpoint = nothing added, so existing deployments are unchanged. * feat(terraform/gcp): add DeployStack one-click installer Wires up a Cloud Shell "Open in Cloud Shell" badge backed by the GoogleCloudPlatform DeployStack flow so examples/default can be installed from a click in the README without a local terraform setup. - examples/default/deploystack.json drives project/region collection plus prompts for tenant, env, image_tag, and allow_plaintext_lb. Complex inputs (proxy_config, *_extra_secrets, lb_domains) and sensitive vars (litellm_master_key, litellm_license, ui_password) stay tfvars / env only so they never land in a committed file. - examples/default/TUTORIAL.md is a Cloud Shell walkthrough that enables required APIs, creates the GHCR-passthrough Artifact Registry repo, optionally exports the TF_VAR_* secrets, runs `deploystack install`, and shows how to fetch the master key plus migrate from plaintext LB to TLS. - Renames var.project to var.project_id across the module and the examples/default wrapper to match the variable DeployStack injects from `collect_project: true`. Breaking rename for anyone with a `project = ...` line in terraform.tfvars; the fix is one line. * feat(terraform/gcp): mount proxy_config from GCS and wire OpenTelemetry v2 proxy_config Drop the inline LITELLM_PROXY_CONFIG_B64 env var and the python-decode startup fragment. Upload the YAML to a dedicated GCS bucket as config.yaml, then mount it read-only into the gateway and backend at /etc/litellm via Cloud Run v2's gcsfuse volume. CONFIG_FILE_PATH points at the mount; an md5 of the YAML rides along as PROXY_CONFIG_HASH so a config-only edit forces a new Cloud Run revision (gcsfuse only surfaces new objects on container restart, so without the hash an updated proxy_config would sit in the bucket unread). The config bucket is separate from the data-plane bucket so the runtime SA can hold objectViewer here (read-only at runtime) while keeping objectAdmin on the data-plane bucket. Both bucket and IAM binding are gated on proxy_config != {}; an empty config skips bucket creation and mounts nothing. OpenTelemetry v2 LITELLM_OTEL_V2=true is now wired into shared_env_kv unconditionally so both the gateway and backend boot with the integration enabled. It's dormant until otel_endpoint is non-empty; setting it injects OTEL_EXPORTER / OTEL_ENDPOINT / OTEL_ENVIRONMENT_NAME plus a per-component OTEL_SERVICE_NAME (\${tenant}-litellm-\${env}-{gateway,backend}) so spans land tagged with the right hop. otel_headers_secret takes a Secret Manager resource ID for OTEL_HEADERS (collector auth); the runtime SA auto-gains roles/secretmanager.secretAccessor on it. otel_capture_message_content defaults to no_content matching the litellm default. Any OTEL_* key set in *_extra_env wins over the defaults so Cloud Run doesn't reject the apply on the duplicate-env-name check. * refactor(terraform): make AWS and GCP stacks behave identically Bring both modules to the same surface and the same runtime behavior so swapping clouds (or reading either README) is symmetric. Labels and tags. GCP previously stamped var.labels onto only the two GCS buckets, leaving Cloud Run, Cloud SQL, Memorystore, Secret Manager, and the LB resources unlabeled; the variable description claimed full coverage. Now the module computes local.labels (litellm-stack + managed-by + var.labels, mirroring AWS's local.tags) and threads it onto every label-supporting resource: Cloud Run services and the migrations job, Cloud SQL writer and reader (via user_labels), Memorystore, Secret Manager entries (master_key, license, ui_password, db_password), both GCS buckets, the global LB address, and the http/https forwarding rules. GCP keys use 'litellm-stack' instead of AWS's 'litellm:stack' because GCP label keys forbid colons; var.labels now defaults to {}. OpenTelemetry v2 is opt-in on both stacks. AWS already gated everything on otel_endpoint; GCP previously stamped LITELLM_OTEL_V2=true into shared_env unconditionally and only ungated the OTEL_* block. Both stacks now do the same thing: leave otel_endpoint empty and nothing OTel-related lands in the container env; set it and gateway and backend get LITELLM_OTEL_V2=true plus OTEL_EXPORTER, OTEL_ENDPOINT, OTEL_ENVIRONMENT_NAME, OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT, and a per-component OTEL_SERVICE_NAME (${tenant}-litellm-${env}-gateway or -backend) so spans land tagged with the right hop. AWS picks up the richer GCP surface: otel_environment_name (defaults to var.env), otel_capture_message_content (defaults to no_content), and *_extra_env override filtering so a caller-set OTEL_* key wins over the default for that service (ECS allows duplicates, but the filter gives the same predictable last-wins shape Cloud Run enforces). var.otel_service_name on AWS is gone, replaced by the per-component naming. uvicorn workers. GCP gains gateway_num_workers, matching AWS; threads into the gateway args as --workers ${var.gateway_num_workers}. Docs reflect the parity: each README's OTel section, the GCP 'Using as a module' Labels paragraph, and a new feature-parity table in the top-level README that lays out the AWS/GCP input mapping side by side. * fix(terraform/aws): expose skip_final_snapshot through the default example The example wrapper already exposed `s3_force_destroy` so ephemeral / CI stacks could destroy the S3 bucket without manual cleanup, but the matching Aurora knob (`skip_final_snapshot`) was hidden behind the module surface. That meant a `terraform destroy` on a trial stack still produced a `-final-` snapshot, with no opt-out short of editing the module call. Adds `var.skip_final_snapshot` to the example (default `false`, preserving the data-loss tripwire) and threads it through to the module input, mirroring the existing `s3_force_destroy` pattern. Documented alongside it in the tfvars example. Verified by deploying the example end-to-end against a clean AWS account (VPC + Aurora w/ IAM auth + Redis + ALB + 3 ECS services), confirming all services reach steady state and the data plane serves traffic, then running `terraform destroy` with `skip_final_snapshot = true` to a clean teardown (93 destroyed, no Aurora snapshot left behind, no leftover billable resources). --------- Co-authored-by: Yassin Kortam Co-authored-by: yassin-berriai Co-authored-by: Claude --- terraform/litellm/README.md | 107 ++++++++++++-- terraform/litellm/aws/README.md | 109 ++++++++++++-- terraform/litellm/aws/alb.tf | 18 +++ terraform/litellm/aws/bootstrap.tf | 6 + terraform/litellm/aws/ecs.tf | 81 +++++++++-- .../aws/examples/default/.terraform.lock.hcl | 46 ++++++ .../litellm/aws/examples/default/main.tf | 41 ++++++ .../litellm/aws/examples/default/outputs.tf | 54 +++++++ .../litellm/aws/examples/default/providers.tf | 24 ++++ .../default}/terraform.tfvars.example | 35 +++-- .../litellm/aws/examples/default/variables.tf | 104 ++++++++++++++ .../litellm/aws/examples/default/versions.tf | 14 ++ terraform/litellm/aws/iam.tf | 11 ++ terraform/litellm/aws/locals.tf | 14 ++ terraform/litellm/aws/migrations.tf | 2 + terraform/litellm/aws/network.tf | 24 ++-- terraform/litellm/aws/providers.tf | 13 -- terraform/litellm/aws/rds.tf | 10 ++ terraform/litellm/aws/redis.tf | 4 + terraform/litellm/aws/s3.tf | 23 +++ terraform/litellm/aws/secrets.tf | 8 ++ terraform/litellm/aws/variables.tf | 87 ++++++++++- terraform/litellm/gcp/README.md | 135 ++++++++++++++++-- terraform/litellm/gcp/bootstrap.tf | 2 +- terraform/litellm/gcp/cloudrun.tf | 104 +++++++++++--- terraform/litellm/gcp/cloudsql.tf | 20 +++ .../gcp/examples/default/.terraform.lock.hcl | 63 ++++++++ .../litellm/gcp/examples/default/TUTORIAL.md | 134 +++++++++++++++++ .../gcp/examples/default/deploystack.json | 37 +++++ .../litellm/gcp/examples/default/main.tf | 51 +++++++ .../litellm/gcp/examples/default/outputs.tf | 59 ++++++++ .../litellm/gcp/examples/default/providers.tf | 17 +++ .../default}/terraform.tfvars.example | 30 ++-- .../litellm/gcp/examples/default/variables.tf | 120 ++++++++++++++++ .../litellm/gcp/examples/default/versions.tf | 18 +++ terraform/litellm/gcp/gcs.tf | 44 +++++- terraform/litellm/gcp/iam.tf | 12 +- terraform/litellm/gcp/load_balancer.tf | 18 ++- terraform/litellm/gcp/locals.tf | 26 +++- terraform/litellm/gcp/outputs.tf | 2 +- terraform/litellm/gcp/providers.tf | 9 -- terraform/litellm/gcp/redis.tf | 2 + terraform/litellm/gcp/secrets.tf | 3 + terraform/litellm/gcp/variables.tf | 107 ++++++++++++-- 44 files changed, 1711 insertions(+), 137 deletions(-) create mode 100644 terraform/litellm/aws/examples/default/.terraform.lock.hcl create mode 100644 terraform/litellm/aws/examples/default/main.tf create mode 100644 terraform/litellm/aws/examples/default/outputs.tf create mode 100644 terraform/litellm/aws/examples/default/providers.tf rename terraform/litellm/aws/{ => examples/default}/terraform.tfvars.example (70%) create mode 100644 terraform/litellm/aws/examples/default/variables.tf create mode 100644 terraform/litellm/aws/examples/default/versions.tf delete mode 100644 terraform/litellm/aws/providers.tf create mode 100644 terraform/litellm/gcp/examples/default/.terraform.lock.hcl create mode 100644 terraform/litellm/gcp/examples/default/TUTORIAL.md create mode 100644 terraform/litellm/gcp/examples/default/deploystack.json create mode 100644 terraform/litellm/gcp/examples/default/main.tf create mode 100644 terraform/litellm/gcp/examples/default/outputs.tf create mode 100644 terraform/litellm/gcp/examples/default/providers.tf rename terraform/litellm/gcp/{ => examples/default}/terraform.tfvars.example (70%) create mode 100644 terraform/litellm/gcp/examples/default/variables.tf create mode 100644 terraform/litellm/gcp/examples/default/versions.tf delete mode 100644 terraform/litellm/gcp/providers.tf diff --git a/terraform/litellm/README.md b/terraform/litellm/README.md index 5ca704b96dd..8f09cb53407 100644 --- a/terraform/litellm/README.md +++ b/terraform/litellm/README.md @@ -1,18 +1,34 @@ # LiteLLM Terraform stacks -Two self-contained Terraform root modules that deploy the **componentized** -LiteLLM proxy — the gateway, backend, and UI as three independent containers -(see `helm/litellm/` for the canonical chart with the same split). +Two self-contained, reusable Terraform **modules** that deploy the +**componentized** LiteLLM proxy — the gateway, backend, and UI as three +independent containers (see `helm/litellm/` for the canonical chart with the +same split). + +Each module declares **no `provider` block of its own**, so it can be called +with `count` / `for_each` / `depends_on` and the caller controls region, +assume-role / impersonation, aliases, and `default_tags`. A ready-to-run root +that wires the provider lives at `/examples/default/` — that's the +one-command deploy path. To embed a stack in your own config, call the module +by source: + +```hcl +module "litellm" { + source = "github.com/BerriAI/litellm//terraform/litellm/aws?ref=" + # ... inputs ... +} +``` | Stack | Compute | Database (writer + reader) | Cache | Object store | Public entrypoint | | ------ | ----------- | ---------------------------------- | ----------- | ------------ | ------------------ | | `aws/` | ECS Fargate | Aurora Postgres (IAM auth) | ElastiCache | S3 | Application LB | | `gcp/` | Cloud Run | Cloud SQL Postgres (password auth) | Memorystore | GCS | External HTTPS LB | -Each stack creates its own VPC and managed data stores — drop in a tfvars -file and run `terraform apply`. Both stacks support a typed `proxy_config` -input (mirrors `helm/litellm`'s `gateway.config.proxy_config`) and per-component -extra env vars / secret-manager refs. +Each stack creates its own VPC and managed data stores — from +`/examples/default/`, drop in a tfvars file and run `terraform apply`. +Both stacks support a typed `proxy_config` input (mirrors `helm/litellm`'s +`gateway.config.proxy_config`) and per-component extra env vars / +secret-manager refs. ## Components @@ -147,6 +163,39 @@ against the backend image: Run the migration job once after the first `terraform apply` and before the gateway/backend services start serving traffic. +## Feature parity between stacks + +The two modules expose the same conceptual surface; concrete inputs differ +only where the underlying cloud forces it. + +| Capability | AWS input(s) | GCP input(s) | +| -------------------------------- | ------------------------------------------------------- | --------------------------------------------------------- | +| Tenant + env naming | `tenant`, `env` | `tenant`, `env` | +| Pre-shared master key / license | `litellm_master_key`, `litellm_license` | `litellm_master_key`, `litellm_license` | +| UI admin password | `ui_password` | `ui_password` | +| Per-deployment tags / labels | `tags` (`map(string)`) | `labels` (`map(string)`) | +| TLS posture | `acm_certificate_arn`, `allow_plaintext_alb` | `lb_domains`, `allow_plaintext_lb` | +| Force destroy of object store | `s3_force_destroy` | `gcs_force_destroy` | +| Database deletion protection | `skip_final_snapshot` | `cloudsql_deletion_protection` | +| `proxy_config` (typed YAML map) | `proxy_config` | `proxy_config` | +| Extra plain env per component | `gateway_extra_env`, `backend_extra_env` | `gateway_extra_env`, `backend_extra_env` | +| Extra secret-backed env | `gateway_extra_secrets`, `backend_extra_secrets` (ARNs) | `gateway_extra_secrets`, `backend_extra_secrets` (resource IDs) | +| Uvicorn `--workers` on gateway | `gateway_num_workers` | `gateway_num_workers` | +| OpenTelemetry v2 (opt-in) | `otel_endpoint`, `otel_exporter`, `otel_environment_name`, `otel_capture_message_content`, `otel_headers_secret_arn` | `otel_endpoint`, `otel_exporter`, `otel_environment_name`, `otel_capture_message_content`, `otel_headers_secret` | + +Each module stamps its own stack-identity tag (`litellm:stack` on AWS, +`litellm-stack` on GCP — GCP label keys forbid colons) plus +`managed-by = "terraform"` onto every taggable / labelable resource and +merges `var.tags` / `var.labels` on top. Provider `default_tags` on AWS +merge on top of all of these. + +OTel is opt-in on both clouds: leave `otel_endpoint` empty and nothing +OTel-related is added to the container env; set it and both gateway and +backend get `LITELLM_OTEL_V2=true` plus the full `OTEL_*` block, with +`OTEL_SERVICE_NAME` stamped per component +(`-litellm--gateway` and `-backend`). Any `OTEL_*` key set +in `gateway_extra_env` / `backend_extra_env` wins for that service. + ## What's not included - TLS certificates / custom domains. Both stacks expose plain-HTTP load @@ -156,4 +205,46 @@ gateway/backend services start serving traffic. backend block to `versions.tf` when graduating to a team environment. - Observability beyond the cloud provider's defaults (CloudWatch logs on AWS, Cloud Logging on GCP). Wire your own Prometheus / Datadog / Langfuse - via the `*_extra_env` variables. + via the `*_extra_env` variables, or turn on OTel v2 (see the parity + table above). + +## HCP Terraform no-code (1-click) deploy + +Both stacks are publishable as no-code modules in HCP Terraform's private +registry. The end-user flow is: open the no-code launch URL, fill in a +few inputs, hit *Create workspace*, and HCP runs plan/apply against your +cloud account using a variable-set of credentials (static keys or +dynamic-credentials OIDC). + +Required overrides the launcher must supply per stack: + +- **AWS** (`terraform/litellm/aws`): `region`, `azs`, `tenant`, `env`. + The image vars (`gateway_image`, `backend_image`, `ui_image`, + `migrations_image`) can be left at their defaults — the GHCR images + are anonymous-readable and ECS Fargate pulls them without extra + credentials. + +- **GCP** (`terraform/litellm/gcp`): `project`, `tenant`, `env`, **and + one of**: + - `image_registry` pointed at an Artifact Registry **remote** repository + backed by `https://ghcr.io` (e.g. + `us-central1-docker.pkg.dev//litellm/berriai`), so Cloud Run + pulls the four upstream `litellm-*` images through it; or + - all four per-component `*_image` URIs pointing at images mirrored + into a regular Artifact Registry repo. + + The defaults (`ghcr.io/berriai`) cause Cloud Run admission to reject + the service spec — Cloud Run only authenticates against Artifact + Registry, `[region.]gcr.io`, or `docker.io`. See + `terraform/litellm/gcp/README.md#image-pulls` for the + `gcloud artifacts repositories create … --mode=remote-repository` + command that sets up the passthrough repo (one-time, per project). + +What still requires a manual step regardless of HCP no-code: + +- The one-off migration task. The stacks auto-run it via `local-exec` + during `terraform apply`, but that requires the `aws` / `gcloud` CLI + on the runner. HCP-hosted runners don't have them; use an HCP agent + pool with a custom image that includes the relevant CLI, or run the + command printed in the `migration_run_command` output by hand after + the first apply. diff --git a/terraform/litellm/aws/README.md b/terraform/litellm/aws/README.md index 8638ea800ec..7d4ef0a14fb 100644 --- a/terraform/litellm/aws/README.md +++ b/terraform/litellm/aws/README.md @@ -44,9 +44,12 @@ needs the `aws` CLI installed and authenticated. ### `proxy_config` (preferred) Mirrors the helm chart's `gateway.config.proxy_config`. The map is YAML-encoded -and base64-passed to gateway, backend, and the migration task; each container -decodes it to `/tmp/litellm-config.yaml` at startup and sets `CONFIG_FILE_PATH` -to match. +and uploaded to S3 (`config/litellm-config.yaml` in the stack's bucket); the +gateway and backend container entrypoints download it to +`/tmp/litellm-config.yaml` at task start via boto3 and set `CONFIG_FILE_PATH` +to match. The S3 object's etag is wired into the task definition, so editing +`proxy_config` produces a new task-def revision and a rolling redeploy of both +services. ```hcl proxy_config = { @@ -119,6 +122,42 @@ aws secretsmanager create-secret \ --secret-string "sk-proj-..." ``` +### Observability (OpenTelemetry v2) + +OTel v2 (https://docs.litellm.ai/docs/observability/opentelemetry_v2) is +opt-in and gated entirely on `otel_endpoint`. Empty (default) and nothing +OTel-related is added to the container env. Set it and both gateway and +backend gain `LITELLM_OTEL_V2=true` plus the `OTEL_*` block, with +`OTEL_SERVICE_NAME` stamped per component (`${tenant}-litellm-${env}-gateway` +and `-backend`) so spans land tagged with the right hop. Any `OTEL_*` key +set in `gateway_extra_env` / `backend_extra_env` overrides the default for +that service. + +```hcl +otel_endpoint = "http://otel-collector.internal:4318" +otel_exporter = "otlp_http" # otlp_grpc, console +otel_environment_name = "prod" # defaults to var.env +``` + +For collectors that require an auth header, store the comma-separated +`key=value` string in Secrets Manager and reference it via +`otel_headers_secret_arn`. The execution role auto-gains +`secretsmanager:GetSecretValue` on that ARN. + +```hcl +otel_headers_secret_arn = "arn:aws:secretsmanager:us-west-2:111122223333:secret:honeycomb-otel-headers-AbCdEf" +``` + +`OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` defaults to +`no_content`; flip `otel_capture_message_content = "prompt_and_completion"` +only after auditing what lands in the backend, since prompts and +completions are typically sensitive. + +Vendor presets (Arize, Phoenix, Langfuse OTel, Weave, Langtrace, Levo, +AgentOps) live under `proxy_config.litellm_settings.callbacks` and are +orthogonal to the OTLP variables above; their credentials still go in +`*_extra_secrets`. + ## Tenant deployment Every resource the stack creates is named `${tenant}-litellm-${env}` (or @@ -132,10 +171,11 @@ pair differs: | `acme` | `prod` | `acme-litellm-prod-master-key` | | `globex` | `dev` | `globex-litellm-dev-license` | -For a per-tenant instance, the only inputs that change are the tenant -slug, env, and the two pre-issued secrets: +For a per-tenant instance via the example root, the only inputs that +change are the tenant slug, env, and the two pre-issued secrets: ```bash +cd terraform/litellm/aws/examples/default export TF_VAR_litellm_master_key="sk-..." # the tenant's master key export TF_VAR_litellm_license="lic-..." # their LITELLM_LICENSE @@ -146,6 +186,22 @@ terraform apply \ -var "env=stage" ``` +To run *many* tenants from a single config, call the module with +`for_each` instead of one root per tenant (see "Using as a module"): + +```hcl +module "litellm" { + for_each = toset(["acme", "globex"]) + source = "github.com/BerriAI/litellm//terraform/litellm/aws?ref=" + tenant = each.key + env = "prod" + region = "us-west-2" + azs = ["us-west-2a", "us-west-2b"] +} +``` +(This `for_each` form is only possible because the module declares no +provider block — the original root-with-provider layout forbade it.) + Both `litellm_master_key` and `litellm_license` are optional: - Omit `litellm_master_key` → the stack auto-generates a random `sk-…` value (trial/dev path). @@ -159,14 +215,21 @@ example files. ## Quick start ```bash -cd terraform/litellm/aws +cd terraform/litellm/aws/examples/default cp terraform.tfvars.example terraform.tfvars -# Edit: region, tenant, env, azs, *_image, proxy_config, gateway_extra_secrets. +# Edit: region, tenant, env, azs, proxy_config, gateway_extra_secrets. terraform init terraform apply ``` +`examples/default/` is a thin root that configures the `aws` provider and +calls the module (`../../`). It exposes a curated variable surface; for +advanced knobs (per-component CPU/memory/workers, autoscaling, RDS/Redis +sizing, per-component image pins) set them on the `module "litellm"` block +in `examples/default/main.tf`, or call the module from your own config — +see "Using as a module" below. + That single apply provisions everything, runs the DB user bootstrap, runs the schema migration, and only then starts the gateway/backend services. When it returns, the stack is serving traffic. @@ -179,6 +242,34 @@ aws secretsmanager get-secret-value \ --query SecretString --output text ``` +## Using as a module + +The directory itself is a module with **no `provider` block** — the caller +owns provider config. That means you can call it directly with `for_each` +(many tenants from one config), `count` (conditional stacks), `depends_on`, +an assume-role / aliased provider, etc.: + +```hcl +provider "aws" { + region = "us-west-2" + assume_role { role_arn = "arn:aws:iam::111122223333:role/deployer" } +} + +module "litellm" { + source = "github.com/BerriAI/litellm//terraform/litellm/aws?ref=" + + region = "us-west-2" + tenant = "acme" + env = "prod" + azs = ["us-west-2a", "us-west-2b"] + # ...any of the inputs in variables.tf... +} +``` + +Tags: the module threads its own `litellm:stack` / `managed-by` / `var.tags` +onto every taggable resource. Any `default_tags` on your provider merge on +top — set org-wide tags there, per-deployment tags via the `tags` input. + ## Image pulls The defaults pull from `ghcr.io/berriai/litellm-:v1.86.0-dev`, @@ -238,8 +329,8 @@ losing the contents. | File | What's in it | | ----------------- | --------------------------------------------------------------------- | -| `versions.tf` | Terraform + provider version constraints | -| `providers.tf` | AWS provider (region + default tags) | +| `versions.tf` | Terraform + `required_providers` constraints (module declares no provider config) | +| `examples/default/` | Thin root: `aws` provider (with an optional `default_tags` slot for org-wide tags) + a call to the module. The one-command deploy path. | | `variables.tf` | All input variables | | `locals.tf` | Path-prefix lists for ALB routing (mirror of `helm/.../ingress.yaml`) | | `network.tf` | VPC, subnets, IGW, NAT, route tables, security groups | diff --git a/terraform/litellm/aws/alb.tf b/terraform/litellm/aws/alb.tf index de0d9c2310f..786b9d9a5b9 100644 --- a/terraform/litellm/aws/alb.tf +++ b/terraform/litellm/aws/alb.tf @@ -6,6 +6,8 @@ resource "aws_lb" "this" { subnets = aws_subnet.public[*].id idle_timeout = 120 + + tags = local.tags } locals { @@ -35,6 +37,8 @@ resource "aws_lb_target_group" "gateway" { } deregistration_delay = 30 + + tags = local.tags } resource "aws_lb_target_group" "backend" { @@ -54,6 +58,8 @@ resource "aws_lb_target_group" "backend" { } deregistration_delay = 30 + + tags = local.tags } resource "aws_lb_target_group" "ui" { @@ -73,6 +79,8 @@ resource "aws_lb_target_group" "ui" { } deregistration_delay = 30 + + tags = local.tags } # HTTP listener. When TLS is enabled this only serves a permanent @@ -106,6 +114,8 @@ resource "aws_lb_listener" "http" { error_message = "ALB has no HTTPS listener. Either set `acm_certificate_arn` to enable TLS, or set `allow_plaintext_alb = true` to opt into HTTP-only (trial / dev only)." } } + + tags = local.tags } # HTTPS listener. Only created when an ACM cert ARN is supplied — terminates @@ -122,6 +132,8 @@ resource "aws_lb_listener" "https" { type = "forward" target_group_arn = aws_lb_target_group.backend.arn } + + tags = local.tags } # UI exact paths (/, /favicon.ico, /ui) — priority 10. @@ -139,6 +151,8 @@ resource "aws_lb_listener_rule" "ui_exact" { values = local.ui_exact_paths } } + + tags = local.tags } # UI prefix paths (/_next/*, /litellm-asset-prefix/*, /assets/*, /ui/*) — priority 20. @@ -156,6 +170,8 @@ resource "aws_lb_listener_rule" "ui_prefix" { values = local.ui_path_prefixes } } + + tags = local.tags } # Gateway prefix rules — one per chunk-of-5 because ALB caps a path-pattern @@ -176,4 +192,6 @@ resource "aws_lb_listener_rule" "gateway" { values = each.value } } + + tags = local.tags } diff --git a/terraform/litellm/aws/bootstrap.tf b/terraform/litellm/aws/bootstrap.tf index e9a56dedbb5..b0bc38d44fb 100644 --- a/terraform/litellm/aws/bootstrap.tf +++ b/terraform/litellm/aws/bootstrap.tf @@ -32,6 +32,8 @@ resource "aws_iam_policy" "bootstrap_secrets" { Resource = [aws_secretsmanager_secret.db_master_password.arn] }] }) + + tags = local.tags } resource "aws_iam_role_policy_attachment" "task_execution_bootstrap_secrets" { @@ -43,6 +45,8 @@ resource "aws_iam_role_policy_attachment" "task_execution_bootstrap_secrets" { resource "aws_cloudwatch_log_group" "bootstrap_db" { name = "/ecs/${local.name}/bootstrap-db" retention_in_days = var.log_retention_days + + tags = local.tags } locals { @@ -101,6 +105,8 @@ resource "aws_ecs_task_definition" "bootstrap_db" { } } }]) + + tags = local.tags } # ---------- Bootstrap trigger ---------- diff --git a/terraform/litellm/aws/ecs.tf b/terraform/litellm/aws/ecs.tf index a6d2350c681..54ab80de9f4 100644 --- a/terraform/litellm/aws/ecs.tf +++ b/terraform/litellm/aws/ecs.tf @@ -5,26 +5,36 @@ resource "aws_ecs_cluster" "this" { name = "containerInsights" value = "enabled" } + + tags = local.tags } resource "aws_cloudwatch_log_group" "gateway" { name = "/ecs/${local.name}/gateway" retention_in_days = var.log_retention_days + + tags = local.tags } resource "aws_cloudwatch_log_group" "backend" { name = "/ecs/${local.name}/backend" retention_in_days = var.log_retention_days + + tags = local.tags } resource "aws_cloudwatch_log_group" "ui" { name = "/ecs/${local.name}/ui" retention_in_days = var.log_retention_days + + tags = local.tags } resource "aws_cloudwatch_log_group" "migrations" { name = "/ecs/${local.name}/migrations" retention_in_days = var.log_retention_days + + tags = local.tags } # Shared env block fed to gateway, backend, and the migration task. Mirrors @@ -34,6 +44,38 @@ resource "aws_cloudwatch_log_group" "migrations" { # HOST/PORT/USER/NAME plus an IAM-signed token, so no DB password is needed # in the task definition. locals { + # OTel v2 is opt-in and gated on otel_endpoint, matching the GCP stack. + # When set, LITELLM_OTEL_V2 flips on alongside the OTEL_* block, with + # OTEL_SERVICE_NAME stamped per component so spans land tagged with the + # right hop. Any OTEL_* key set in *_extra_env wins over the default for + # that service (ECS allows duplicates but last-wins is undefined, so we + # filter here for the same predictable behavior GCP gets from Cloud Run's + # hard duplicate-rejection). + otel_enabled = var.otel_endpoint != "" + otel_environment_name = var.otel_environment_name != "" ? var.otel_environment_name : var.env + otel_shared_env = local.otel_enabled ? [ + { name = "LITELLM_OTEL_V2", value = "true" }, + { name = "OTEL_EXPORTER", value = var.otel_exporter }, + { name = "OTEL_ENDPOINT", value = var.otel_endpoint }, + { name = "OTEL_ENVIRONMENT_NAME", value = local.otel_environment_name }, + { name = "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", value = var.otel_capture_message_content }, + ] : [] + gateway_otel_env_raw = concat(local.otel_shared_env, local.otel_enabled ? [ + { name = "OTEL_SERVICE_NAME", value = "${local.name}-gateway" }, + ] : []) + backend_otel_env_raw = concat(local.otel_shared_env, local.otel_enabled ? [ + { name = "OTEL_SERVICE_NAME", value = "${local.name}-backend" }, + ] : []) + gateway_otel_env = [ + for e in local.gateway_otel_env_raw : e if !contains(keys(var.gateway_extra_env), e.name) + ] + backend_otel_env = [ + for e in local.backend_otel_env_raw : e if !contains(keys(var.backend_extra_env), e.name) + ] + otel_secrets = local.otel_enabled && var.otel_headers_secret_arn != "" ? [ + { name = "OTEL_HEADERS", valueFrom = var.otel_headers_secret_arn }, + ] : [] + shared_env = [ { name = "IAM_TOKEN_DB_AUTH", value = "true" }, { name = "DATABASE_HOST", value = aws_rds_cluster.this.endpoint }, @@ -65,6 +107,7 @@ locals { var.litellm_license == "" ? [] : [ { name = "LITELLM_LICENSE", valueFrom = aws_secretsmanager_secret.license[0].arn }, ], + local.otel_secrets, ) # Backend-only managed secrets. UI_PASSWORD is consumed by the management @@ -91,20 +134,26 @@ locals { ] # Mirrors the helm chart's gateway.config.create / configmap pattern. - # ECS Fargate has no ConfigMap analogue, so we pass the YAML as a - # base64-encoded env var and decode it at container start via a tiny - # python shim that prepends the image's normal uvicorn entrypoint. + # ECS Fargate has no ConfigMap analogue, so the YAML is uploaded to S3 + # (see aws_s3_object.proxy_config in s3.tf) and the container entrypoint + # downloads it to /tmp/litellm-config.yaml via boto3 before exec'ing + # uvicorn. The S3 object's etag is embedded in the task definition so a + # config edit forces a new task-def revision and a rolling redeploy. proxy_config_enabled = length(keys(var.proxy_config)) > 0 - proxy_config_b64 = local.proxy_config_enabled ? base64encode(yamlencode(var.proxy_config)) : "" + proxy_config_path = "/tmp/litellm-config.yaml" proxy_config_env = local.proxy_config_enabled ? [ - { name = "LITELLM_PROXY_CONFIG_B64", value = local.proxy_config_b64 }, - { name = "CONFIG_FILE_PATH", value = "/tmp/litellm-config.yaml" }, + { name = "CONFIG_FILE_PATH", value = local.proxy_config_path }, + { name = "LITELLM_PROXY_CONFIG_S3_BUCKET", value = aws_s3_bucket.this.bucket }, + { name = "LITELLM_PROXY_CONFIG_S3_KEY", value = aws_s3_object.proxy_config[0].key }, + { name = "LITELLM_PROXY_CONFIG_S3_ETAG", value = aws_s3_object.proxy_config[0].etag }, ] : [] + proxy_config_fetch_cmd = "python -c \"import os, boto3; boto3.client('s3', region_name=os.environ['AWS_REGION']).download_file(os.environ['LITELLM_PROXY_CONFIG_S3_BUCKET'], os.environ['LITELLM_PROXY_CONFIG_S3_KEY'], os.environ['CONFIG_FILE_PATH'])\"" + # Gateway always needs --workers wired in (no NUM_WORKERS env var support # in the image entrypoint). When proxy_config is enabled we also have to - # decode the base64 config first, so the command goes through `sh -c`; + # pull the config from S3 first, so the command goes through `sh -c`; # otherwise we keep the image's ENTRYPOINT and only override `command`. gateway_uvicorn_args = "--host 0.0.0.0 --port 4000 --workers ${var.gateway_num_workers}" backend_uvicorn_args = "--host 0.0.0.0 --port 4001" @@ -112,7 +161,7 @@ locals { gateway_proxy_overrides = local.proxy_config_enabled ? { entryPoint = ["sh", "-c"] command = [ - "python -c \"import os, base64, pathlib; pathlib.Path(os.environ['CONFIG_FILE_PATH']).write_bytes(base64.b64decode(os.environ['LITELLM_PROXY_CONFIG_B64']))\" && exec uvicorn gateway.main:app ${local.gateway_uvicorn_args}" + "${local.proxy_config_fetch_cmd} && exec uvicorn gateway.main:app ${local.gateway_uvicorn_args}" ] } : { # Mirror the image's ENTRYPOINT so we can append --workers via command. @@ -123,7 +172,7 @@ locals { backend_proxy_overrides = local.proxy_config_enabled ? { entryPoint = ["sh", "-c"] command = [ - "python -c \"import os, base64, pathlib; pathlib.Path(os.environ['CONFIG_FILE_PATH']).write_bytes(base64.b64decode(os.environ['LITELLM_PROXY_CONFIG_B64']))\" && exec uvicorn backend.main:app ${local.backend_uvicorn_args}" + "${local.proxy_config_fetch_cmd} && exec uvicorn backend.main:app ${local.backend_uvicorn_args}" ] } : {} } @@ -148,6 +197,7 @@ resource "aws_ecs_task_definition" "gateway" { portMappings = [{ containerPort = 4000, protocol = "tcp" }] environment = concat( local.shared_env, + local.gateway_otel_env, local.gateway_extra_env_list, local.proxy_config_env, ) @@ -169,6 +219,8 @@ resource "aws_ecs_task_definition" "gateway" { local.gateway_proxy_overrides, ) ]) + + tags = local.tags } resource "aws_ecs_service" "gateway" { @@ -206,6 +258,8 @@ resource "aws_ecs_service" "gateway" { aws_lb_listener.https, terraform_data.migration, ] + + tags = local.tags } # ---------- Backend ---------- @@ -229,6 +283,7 @@ resource "aws_ecs_task_definition" "backend" { environment = concat( local.shared_env, local.backend_default_env, + local.backend_otel_env, local.backend_extra_env_list, local.proxy_config_env, ) @@ -246,6 +301,8 @@ resource "aws_ecs_task_definition" "backend" { local.backend_proxy_overrides, ) ]) + + tags = local.tags } resource "aws_ecs_service" "backend" { @@ -279,6 +336,8 @@ resource "aws_ecs_service" "backend" { aws_lb_listener.https, terraform_data.migration, ] + + tags = local.tags } # ---------- UI ---------- @@ -312,6 +371,8 @@ resource "aws_ecs_task_definition" "ui" { } } ]) + + tags = local.tags } resource "aws_ecs_service" "ui" { @@ -344,4 +405,6 @@ resource "aws_ecs_service" "ui" { aws_lb_listener.http, aws_lb_listener.https, ] + + tags = local.tags } diff --git a/terraform/litellm/aws/examples/default/.terraform.lock.hcl b/terraform/litellm/aws/examples/default/.terraform.lock.hcl new file mode 100644 index 00000000000..4a059b2b268 --- /dev/null +++ b/terraform/litellm/aws/examples/default/.terraform.lock.hcl @@ -0,0 +1,46 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.100.0" + constraints = "~> 5.60" + hashes = [ + "h1:Ijt7pOlB7Tr7maGQIqtsLFbl7pSMIj06TVdkoSBcYOw=", + "zh:054b8dd49f0549c9a7cc27d159e45327b7b65cf404da5e5a20da154b90b8a644", + "zh:0b97bf8d5e03d15d83cc40b0530a1f84b459354939ba6f135a0086c20ebbe6b2", + "zh:1589a2266af699cbd5d80737a0fe02e54ec9cf2ca54e7e00ac51c7359056f274", + "zh:6330766f1d85f01ae6ea90d1b214b8b74cc8c1badc4696b165b36ddd4cc15f7b", + "zh:7c8c2e30d8e55291b86fcb64bdf6c25489d538688545eb48fd74ad622e5d3862", + "zh:99b1003bd9bd32ee323544da897148f46a527f622dc3971af63ea3e251596342", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:9f8b909d3ec50ade83c8062290378b1ec553edef6a447c56dadc01a99f4eaa93", + "zh:aaef921ff9aabaf8b1869a86d692ebd24fbd4e12c21205034bb679b9caf883a2", + "zh:ac882313207aba00dd5a76dbd572a0ddc818bb9cbf5c9d61b28fe30efaec951e", + "zh:bb64e8aff37becab373a1a0cc1080990785304141af42ed6aa3dd4913b000421", + "zh:dfe495f6621df5540d9c92ad40b8067376350b005c637ea6efac5dc15028add4", + "zh:f0ddf0eaf052766cfe09dea8200a946519f653c384ab4336e2a4a64fdd6310e9", + "zh:f1b7e684f4c7ae1eed272b6de7d2049bb87a0275cb04dbb7cda6636f600699c9", + "zh:ff461571e3f233699bf690db319dfe46aec75e58726636a0d97dd9ac6e32fb70", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.9.0" + constraints = "~> 3.6" + hashes = [ + "h1:OO+IuvQJSPmWdN8AyyIEvPJbLvDQpgX/zbktoa9KsJE=", + "zh:161ad0bd9a75768c82f53fb6e7172a9d8be2d4889b012645a34795031aaf1bf1", + "zh:19dc9a5b17729725ccfc4f45b0500af0ee5bc6b6b160c7adb8f2bf617d2c80ea", + "zh:269eda8fe42daa7974d5a34d166c3ba9defe80cde86c01e4dadcfdf2e1f05e5f", + "zh:373f7c65566f8f2cc7f45d698654feb9d988996957e1266a69ca00c52d6d16d0", + "zh:5599d16804c41c83009ec621b6d6b6f74e102f5827678a4750f8809055546b61", + "zh:583be0440469a22bff70dcfa56593b01566860b29607437264adb51060cf46fc", + "zh:5f211d8ec3f2e1f414870d9584bfe26e6995560ef81c748f8447a48164767398", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7b547fd16216761ef86efc3ed516ac5ac0c5c42b7c7eb24a08cef2d93f69ed5e", + "zh:7e7c0679daf2a382151d05068c8c3f0dae6b7b7dccf818827b73dd08638df2ef", + "zh:8089dec888a8038b9b4fb23b3df7e1057293dbc5b60b42cc47ff690d69d4b61b", + "zh:c51f15a031edfd6f23ce8ced3446ca7f8d8d647e2499890d7d5d10d5016d7257", + "zh:c94784f005708890dc6895afd53636ec00ec1e430b15d41e5aebfb1d4b39bd04", + ] +} diff --git a/terraform/litellm/aws/examples/default/main.tf b/terraform/litellm/aws/examples/default/main.tf new file mode 100644 index 00000000000..3d421099aed --- /dev/null +++ b/terraform/litellm/aws/examples/default/main.tf @@ -0,0 +1,41 @@ +# One-command deploy of the LiteLLM AWS stack. +# +# cd terraform/litellm/aws/examples/default +# cp terraform.tfvars.example terraform.tfvars # edit it +# terraform init +# terraform apply +# +# This root just wires the provider (see providers.tf) to the module. The +# module itself (../../) declares no provider, so it can also be consumed +# from your own config with count/for_each/aliased or assume-role providers: +# +# module "litellm" { +# source = "github.com/BerriAI/litellm//terraform/litellm/aws?ref=" +# ... +# } +# +# Knobs not surfaced as variables here (per-component sizing, autoscaling, +# RDS/Redis tuning) can be set directly on this block — see ../../variables.tf. +module "litellm" { + source = "../../" + + region = var.region + tenant = var.tenant + env = var.env + azs = var.azs + + litellm_master_key = var.litellm_master_key + litellm_license = var.litellm_license + ui_password = var.ui_password + + acm_certificate_arn = var.acm_certificate_arn + allow_plaintext_alb = var.allow_plaintext_alb + s3_force_destroy = var.s3_force_destroy + skip_final_snapshot = var.skip_final_snapshot + + proxy_config = var.proxy_config + gateway_extra_env = var.gateway_extra_env + backend_extra_env = var.backend_extra_env + gateway_extra_secrets = var.gateway_extra_secrets + backend_extra_secrets = var.backend_extra_secrets +} diff --git a/terraform/litellm/aws/examples/default/outputs.tf b/terraform/litellm/aws/examples/default/outputs.tf new file mode 100644 index 00000000000..235c069933c --- /dev/null +++ b/terraform/litellm/aws/examples/default/outputs.tf @@ -0,0 +1,54 @@ +output "alb_dns_name" { + description = "Public DNS name of the LiteLLM ALB." + value = module.litellm.alb_dns_name +} + +output "alb_url" { + description = "Proxy URL. Dashboard at /, API at /v1/*." + value = module.litellm.alb_url +} + +output "ecs_cluster" { + description = "ECS cluster name." + value = module.litellm.ecs_cluster +} + +output "aurora_writer_endpoint" { + description = "Aurora writer endpoint." + value = module.litellm.aurora_writer_endpoint +} + +output "aurora_reader_endpoint" { + description = "Aurora reader endpoint." + value = module.litellm.aurora_reader_endpoint +} + +output "redis_endpoint" { + description = "ElastiCache Redis primary endpoint (TLS)." + value = module.litellm.redis_endpoint +} + +output "s3_bucket" { + description = "S3 bucket name." + value = module.litellm.s3_bucket +} + +output "master_key_secret_arn" { + description = "Secrets Manager ARN holding LITELLM_MASTER_KEY." + value = module.litellm.master_key_secret_arn +} + +output "db_master_password_secret_arn" { + description = "Secrets Manager ARN holding the Aurora master credentials (bootstrap-only)." + value = module.litellm.db_master_password_secret_arn +} + +output "db_bootstrap_sql" { + description = "Run once as the master DB user to create the IAM-authed app user." + value = module.litellm.db_bootstrap_sql +} + +output "migration_run_command" { + description = "Break-glass command to re-run the one-off prisma migration task." + value = module.litellm.migration_run_command +} diff --git a/terraform/litellm/aws/examples/default/providers.tf b/terraform/litellm/aws/examples/default/providers.tf new file mode 100644 index 00000000000..92723a92769 --- /dev/null +++ b/terraform/litellm/aws/examples/default/providers.tf @@ -0,0 +1,24 @@ +# The provider is configured HERE, in the root, not in the module. That is +# the whole point of the split: a module that declares its own configured +# `provider` block can't be called with count/for_each/depends_on and gives +# the caller no way to set assume-role, custom endpoints, or aliases. +# +# `default_tags` set here still flow into every resource the module creates +# (provider default_tags propagate through module calls) and merge with the +# module's own `litellm:stack` / `managed-by` / var.tags. Use this block for +# org-wide tags; use the module's `tags` input for per-deployment tags. +provider "aws" { + region = var.region + + # Reserve `default_tags` for pure org-wide tags the module shouldn't know + # about (cost center, team, compliance scope, …). They propagate through the + # module call and merge with the module's own `litellm:stack` / `managed-by` + # / var.tags. The module already stamps `managed-by = "terraform"`, so don't + # duplicate it here — set per-deployment tags via the module's `tags` input. + # + # default_tags { + # tags = { + # "cost-center" = "platform" + # } + # } +} diff --git a/terraform/litellm/aws/terraform.tfvars.example b/terraform/litellm/aws/examples/default/terraform.tfvars.example similarity index 70% rename from terraform/litellm/aws/terraform.tfvars.example rename to terraform/litellm/aws/examples/default/terraform.tfvars.example index 2be573949ef..4fdfb47e678 100644 --- a/terraform/litellm/aws/terraform.tfvars.example +++ b/terraform/litellm/aws/examples/default/terraform.tfvars.example @@ -23,22 +23,17 @@ env = "stage" # allow_plaintext_alb = true # Storage retention: false (default) makes `terraform destroy` refuse on a -# non-empty bucket. Flip to true only for ephemeral / CI stacks. -# s3_force_destroy = false +# non-empty bucket / take an Aurora final snapshot. Flip to true only for +# ephemeral / CI stacks where you accept losing the data. +# s3_force_destroy = false +# skip_final_snapshot = false -# Component images. Defaults pin all four to the same GHCR release tag — -# bump them together when bumping LiteLLM. Override here to pull from a -# private registry or to mix-and-match versions. -# gateway_image = "ghcr.io/berriai/litellm-gateway:1.86.0-dev" -# backend_image = "ghcr.io/berriai/litellm-backend:1.86.0-dev" -# ui_image = "ghcr.io/berriai/litellm-ui:1.86.0-dev" -# migrations_image = "ghcr.io/berriai/litellm-migrations:1.86.0-dev" - -# Per-task sizing for the gateway. Defaults are 1 vCPU / 4 GiB / 1 worker. -# uvicorn rule of thumb for CPU-bound work is (2 * vCPU) + 1 workers. -# gateway_cpu = 1024 # 1024 = 1 vCPU -# gateway_memory = 4096 # MiB -# gateway_num_workers = 1 +# Component images and per-task sizing/autoscaling are NOT exposed as +# variables in this example (it keeps the curated surface small). They +# default to working public GHCR images. To pin images or tune +# CPU/memory/workers/autoscaling, set those inputs directly on the +# `module "litellm"` block in main.tf — the full list is in +# ../../variables.tf — or call the module from your own root config. # ---------- proxy_config (mirrors helm gateway.config.proxy_config) ---------- # proxy_config = { @@ -86,3 +81,13 @@ env = "stage" # OPENAI_API_KEY = "arn:aws:secretsmanager:us-west-2:111122223333:secret:openai-api-key-AbCdEf" # ANTHROPIC_API_KEY = "arn:aws:secretsmanager:us-west-2:111122223333:secret:anthropic-api-key-GhIjKl" # } + +# ---------- OpenTelemetry v2 ---------- +# OTel is gated on otel_endpoint: empty (default) and nothing is added to +# the container env; set it and both gateway and backend gain +# LITELLM_OTEL_V2=true plus the OTEL_* block (with OTEL_SERVICE_NAME +# stamped per component). The knobs aren't surfaced as wrapper vars in +# this example; set them directly on the `module "litellm"` block in +# main.tf (otel_endpoint, otel_exporter, otel_environment_name, +# otel_capture_message_content, otel_headers_secret_arn). Full docs in +# ../../variables.tf. diff --git a/terraform/litellm/aws/examples/default/variables.tf b/terraform/litellm/aws/examples/default/variables.tf new file mode 100644 index 00000000000..74522118a93 --- /dev/null +++ b/terraform/litellm/aws/examples/default/variables.tf @@ -0,0 +1,104 @@ +# Curated surface for the one-command deploy path. The module (../../) +# exposes far more knobs (per-component CPU/memory, autoscaling, RDS/Redis +# sizing, …). To tune those, set them directly on the `module "litellm"` +# block in main.tf, or call the module from your own root config. Full +# per-variable docs live in ../../variables.tf — the module is the source +# of truth; descriptions here are intentionally terse. + +variable "region" { + description = "AWS region to deploy into." + type = string +} + +variable "tenant" { + description = "Tenant slug — prefix for every resource (-litellm-)." + type = string +} + +variable "env" { + description = "Environment suffix (stage, prod, dev)." + type = string +} + +variable "azs" { + description = "Availability zones for subnets. At least 2 (RDS + ALB)." + type = list(string) +} + +# Sensitive — prefer TF_VAR_litellm_master_key / TF_VAR_litellm_license / +# TF_VAR_ui_password so values stay out of any committed tfvars file. +variable "litellm_master_key" { + description = "Pre-existing LITELLM_MASTER_KEY (sk-…). Empty → auto-generated." + type = string + default = "" + sensitive = true +} + +variable "litellm_license" { + description = "LiteLLM enterprise license. Empty → OSS-only." + type = string + default = "" + sensitive = true +} + +variable "ui_password" { + description = "UI admin password. Empty → falls back to LITELLM_MASTER_KEY." + type = string + default = "" + sensitive = true +} + +# TLS — provide an ACM cert for production, or opt into HTTP-only for dev. +variable "acm_certificate_arn" { + description = "ACM cert ARN for the ALB HTTPS listener. Empty → no TLS." + type = string + default = "" +} + +variable "allow_plaintext_alb" { + description = "Opt into HTTP-only ALB (trial/dev only)." + type = bool + default = false +} + +variable "s3_force_destroy" { + description = "Allow destroy of a non-empty S3 bucket (ephemeral/CI only)." + type = bool + default = false +} + +variable "skip_final_snapshot" { + description = "Skip the Aurora final snapshot on destroy (ephemeral/CI only)." + type = bool + default = false +} + +variable "proxy_config" { + description = "LiteLLM proxy config (contents of config.yaml). Empty → defaults." + type = any + default = {} +} + +variable "gateway_extra_env" { + description = "Plain-text env vars layered onto the gateway." + type = map(string) + default = {} +} + +variable "backend_extra_env" { + description = "Plain-text env vars layered onto the backend." + type = map(string) + default = {} +} + +variable "gateway_extra_secrets" { + description = "Gateway env vars sourced from Secrets Manager (name → ARN)." + type = map(string) + default = {} +} + +variable "backend_extra_secrets" { + description = "Backend env vars sourced from Secrets Manager (name → ARN)." + type = map(string) + default = {} +} diff --git a/terraform/litellm/aws/examples/default/versions.tf b/terraform/litellm/aws/examples/default/versions.tf new file mode 100644 index 00000000000..73b88e91dce --- /dev/null +++ b/terraform/litellm/aws/examples/default/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.6.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.60" + } + random = { + source = "hashicorp/random" + version = "~> 3.6" + } + } +} diff --git a/terraform/litellm/aws/iam.tf b/terraform/litellm/aws/iam.tf index 504e0fe1d63..64e1b1ad5f9 100644 --- a/terraform/litellm/aws/iam.tf +++ b/terraform/litellm/aws/iam.tf @@ -13,6 +13,8 @@ data "aws_iam_policy_document" "task_assume" { resource "aws_iam_role" "task_execution" { name = "${local.name}-task-execution" assume_role_policy = data.aws_iam_policy_document.task_assume.json + + tags = local.tags } resource "aws_iam_role_policy_attachment" "task_execution" { @@ -52,6 +54,7 @@ data "aws_iam_policy_document" "secrets_access" { aws_secretsmanager_secret.license[*].arn, aws_secretsmanager_secret.ui_password[*].arn, local.extra_secret_arns, + var.otel_headers_secret_arn == "" ? [] : [var.otel_headers_secret_arn], ) } } @@ -59,6 +62,8 @@ data "aws_iam_policy_document" "secrets_access" { resource "aws_iam_policy" "secrets_access" { name = "${local.name}-secrets-access" policy = data.aws_iam_policy_document.secrets_access.json + + tags = local.tags } resource "aws_iam_role_policy_attachment" "task_execution_secrets" { @@ -75,6 +80,8 @@ resource "aws_iam_role_policy_attachment" "task_execution_secrets" { resource "aws_iam_role" "task" { name = "${local.name}-task" assume_role_policy = data.aws_iam_policy_document.task_assume.json + + tags = local.tags } data "aws_caller_identity" "current" {} @@ -91,6 +98,8 @@ data "aws_iam_policy_document" "rds_iam_connect" { resource "aws_iam_policy" "rds_iam_connect" { name = "${local.name}-rds-iam-connect" policy = data.aws_iam_policy_document.rds_iam_connect.json + + tags = local.tags } resource "aws_iam_role_policy_attachment" "task_rds_iam_connect" { @@ -111,4 +120,6 @@ resource "aws_iam_role_policy_attachment" "task_rds_iam_connect" { resource "aws_iam_role" "ui_task" { name = "${local.name}-ui-task" assume_role_policy = data.aws_iam_policy_document.task_assume.json + + tags = local.tags } diff --git a/terraform/litellm/aws/locals.tf b/terraform/litellm/aws/locals.tf index 85c3b6eaaad..b5e28272d04 100644 --- a/terraform/litellm/aws/locals.tf +++ b/terraform/litellm/aws/locals.tf @@ -11,6 +11,20 @@ locals { # the stack can reference local.name. name = "${var.tenant}-litellm-${var.env}" + # This is a reusable module — it declares no `provider` block, so the AWS + # provider's `default_tags` is the caller's concern, not ours. To keep the + # same per-resource tagging the stack had when it owned the provider, the + # module threads `local.tags` onto every taggable resource itself. Callers + # may layer org-wide tags on top via their own provider `default_tags` + # (those merge with these). `var.tags` is the per-deployment override. + tags = merge( + { + "litellm:stack" = local.name + "managed-by" = "terraform" + }, + var.tags, + ) + gateway_path_prefixes = [ "/v1/chat/*", "/chat/*", "/v1/completions*", "/completions*", diff --git a/terraform/litellm/aws/migrations.tf b/terraform/litellm/aws/migrations.tf index fc4e2ce0cab..62880ebf165 100644 --- a/terraform/litellm/aws/migrations.tf +++ b/terraform/litellm/aws/migrations.tf @@ -42,4 +42,6 @@ resource "aws_ecs_task_definition" "migrations" { } } }]) + + tags = local.tags } diff --git a/terraform/litellm/aws/network.tf b/terraform/litellm/aws/network.tf index d5ed49c1b8a..2f104da6a6b 100644 --- a/terraform/litellm/aws/network.tf +++ b/terraform/litellm/aws/network.tf @@ -7,12 +7,12 @@ resource "aws_vpc" "this" { enable_dns_hostnames = true enable_dns_support = true - tags = { Name = local.name } + tags = merge(local.tags, { Name = local.name }) } resource "aws_internet_gateway" "this" { vpc_id = aws_vpc.this.id - tags = { Name = local.name } + tags = merge(local.tags, { Name = local.name }) } # Public subnets (ALB + NAT). One per AZ. @@ -23,7 +23,7 @@ resource "aws_subnet" "public" { availability_zone = var.azs[count.index] map_public_ip_on_launch = true - tags = { Name = "${local.name}-public-${var.azs[count.index]}" } + tags = merge(local.tags, { Name = "${local.name}-public-${var.azs[count.index]}" }) } # Private subnets (ECS tasks, RDS, ElastiCache). One per AZ, separate from @@ -34,12 +34,12 @@ resource "aws_subnet" "private" { cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + 10) availability_zone = var.azs[count.index] - tags = { Name = "${local.name}-private-${var.azs[count.index]}" } + tags = merge(local.tags, { Name = "${local.name}-private-${var.azs[count.index]}" }) } resource "aws_eip" "nat" { domain = "vpc" - tags = { Name = "${local.name}-nat" } + tags = merge(local.tags, { Name = "${local.name}-nat" }) depends_on = [aws_internet_gateway.this] } @@ -50,7 +50,7 @@ resource "aws_nat_gateway" "this" { allocation_id = aws_eip.nat.id subnet_id = aws_subnet.public[0].id - tags = { Name = local.name } + tags = merge(local.tags, { Name = local.name }) depends_on = [aws_internet_gateway.this] } @@ -63,7 +63,7 @@ resource "aws_route_table" "public" { gateway_id = aws_internet_gateway.this.id } - tags = { Name = "${local.name}-public" } + tags = merge(local.tags, { Name = "${local.name}-public" }) } resource "aws_route_table_association" "public" { @@ -80,7 +80,7 @@ resource "aws_route_table" "private" { nat_gateway_id = aws_nat_gateway.this.id } - tags = { Name = "${local.name}-private" } + tags = merge(local.tags, { Name = "${local.name}-private" }) } resource "aws_route_table_association" "private" { @@ -119,6 +119,8 @@ resource "aws_security_group" "alb" { protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } + + tags = local.tags } resource "aws_security_group" "tasks" { @@ -141,6 +143,8 @@ resource "aws_security_group" "tasks" { protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } + + tags = local.tags } resource "aws_security_group" "rds" { @@ -155,6 +159,8 @@ resource "aws_security_group" "rds" { protocol = "tcp" security_groups = [aws_security_group.tasks.id] } + + tags = local.tags } resource "aws_security_group" "redis" { @@ -169,4 +175,6 @@ resource "aws_security_group" "redis" { protocol = "tcp" security_groups = [aws_security_group.tasks.id] } + + tags = local.tags } diff --git a/terraform/litellm/aws/providers.tf b/terraform/litellm/aws/providers.tf deleted file mode 100644 index 5e7d506c23f..00000000000 --- a/terraform/litellm/aws/providers.tf +++ /dev/null @@ -1,13 +0,0 @@ -provider "aws" { - region = var.region - - default_tags { - tags = merge( - { - "litellm:stack" = local.name - "managed-by" = "terraform" - }, - var.tags, - ) - } -} diff --git a/terraform/litellm/aws/rds.tf b/terraform/litellm/aws/rds.tf index 8e3b70a8d62..d9b7351a805 100644 --- a/terraform/litellm/aws/rds.tf +++ b/terraform/litellm/aws/rds.tf @@ -19,12 +19,16 @@ resource "aws_db_subnet_group" "this" { name = "${local.name}-db" subnet_ids = aws_subnet.private[*].id + + tags = local.tags } resource "aws_rds_cluster_parameter_group" "this" { name = "${local.name}-cluster-pg" family = "aurora-postgresql${split(".", var.db_engine_version)[0]}" description = "LiteLLM Aurora Postgres cluster parameters." + + tags = local.tags } resource "aws_rds_cluster" "this" { @@ -52,6 +56,8 @@ resource "aws_rds_cluster" "this" { backup_retention_period = 7 preferred_backup_window = "07:00-09:00" + + tags = local.tags } resource "aws_rds_cluster_instance" "writer" { @@ -67,6 +73,8 @@ resource "aws_rds_cluster_instance" "writer" { # Promotion tier 0 — first in line during failover, so this instance stays # the writer unless it goes unhealthy. promotion_tier = 0 + + tags = local.tags } resource "aws_rds_cluster_instance" "reader" { @@ -82,4 +90,6 @@ resource "aws_rds_cluster_instance" "reader" { # Higher promotion tier — won't be picked as writer during a failover # unless the writer instance itself is gone. promotion_tier = 15 + + tags = local.tags } diff --git a/terraform/litellm/aws/redis.tf b/terraform/litellm/aws/redis.tf index 2a6fab2d89f..071cbc6d46f 100644 --- a/terraform/litellm/aws/redis.tf +++ b/terraform/litellm/aws/redis.tf @@ -1,6 +1,8 @@ resource "aws_elasticache_subnet_group" "this" { name = "${local.name}-redis" subnet_ids = aws_subnet.private[*].id + + tags = local.tags } # Replication group (not aws_elasticache_cluster, which is the @@ -30,4 +32,6 @@ resource "aws_elasticache_replication_group" "this" { transit_encryption_enabled = true apply_immediately = true + + tags = local.tags } diff --git a/terraform/litellm/aws/s3.tf b/terraform/litellm/aws/s3.tf index 375bc73bb71..a666a790c0c 100644 --- a/terraform/litellm/aws/s3.tf +++ b/terraform/litellm/aws/s3.tf @@ -18,6 +18,8 @@ resource "aws_s3_bucket" "this" { # cached responses, archived request logs, and /v1/files storage stay put. # Flip to true only for ephemeral / CI stacks (`var.s3_force_destroy`). force_destroy = var.s3_force_destroy + + tags = local.tags } resource "aws_s3_bucket_versioning" "this" { @@ -72,9 +74,30 @@ data "aws_iam_policy_document" "s3_access" { resource "aws_iam_policy" "s3_access" { name = "${local.name}-s3-access" policy = data.aws_iam_policy_document.s3_access.json + + tags = local.tags } resource "aws_iam_role_policy_attachment" "task_s3_access" { role = aws_iam_role.task.name policy_arn = aws_iam_policy.s3_access.arn } + +# proxy_config is uploaded as an S3 object so the gateway and backend +# containers can fetch it at startup instead of carrying the YAML inline +# as a base64 env var. ECS Fargate has no native S3 volume type, so +# "mount" here is: container entrypoint runs a boto3 download_file into +# /tmp/litellm-config.yaml before exec'ing uvicorn. The task role already +# has s3:GetObject on this bucket via aws_iam_policy.s3_access. +# +# etag flows into the task definition (see locals.proxy_config_env in +# ecs.tf) so a config edit produces a new task-def revision and ECS rolls +# both services automatically. +resource "aws_s3_object" "proxy_config" { + count = length(keys(var.proxy_config)) > 0 ? 1 : 0 + + bucket = aws_s3_bucket.this.id + key = "config/litellm-config.yaml" + content = yamlencode(var.proxy_config) + content_type = "application/yaml" +} diff --git a/terraform/litellm/aws/secrets.tf b/terraform/litellm/aws/secrets.tf index dd13fdc1239..300d38e4053 100644 --- a/terraform/litellm/aws/secrets.tf +++ b/terraform/litellm/aws/secrets.tf @@ -22,6 +22,8 @@ resource "aws_secretsmanager_secret" "master_key" { name = "${local.name}-master-key" description = "LITELLM_MASTER_KEY for gateway + backend." recovery_window_in_days = 0 + + tags = local.tags } resource "aws_secretsmanager_secret_version" "master_key" { @@ -40,6 +42,8 @@ resource "aws_secretsmanager_secret" "license" { name = "${local.name}-license" description = "LITELLM_LICENSE for gateway + backend." recovery_window_in_days = 0 + + tags = local.tags } resource "aws_secretsmanager_secret_version" "license" { @@ -59,6 +63,8 @@ resource "aws_secretsmanager_secret" "ui_password" { name = "${local.name}-ui-password" description = "UI_PASSWORD for the backend (UI admin login)." recovery_window_in_days = 0 + + tags = local.tags } resource "aws_secretsmanager_secret_version" "ui_password" { @@ -72,6 +78,8 @@ resource "aws_secretsmanager_secret" "db_master_password" { name = "${local.name}-db-master-password" description = "Aurora master-user password - bootstrap only. Runtime auth is IAM-token." recovery_window_in_days = 0 + + tags = local.tags } resource "aws_secretsmanager_secret_version" "db_master_password" { diff --git a/terraform/litellm/aws/variables.tf b/terraform/litellm/aws/variables.tf index 946cd7ebbf3..8db4935664b 100644 --- a/terraform/litellm/aws/variables.tf +++ b/terraform/litellm/aws/variables.tf @@ -24,7 +24,7 @@ variable "env" { } variable "tags" { - description = "Additional tags merged into the provider default_tags." + description = "Per-deployment tags applied to every taggable resource the module creates, on top of the module's own `litellm:stack` / `managed-by` tags. Caller-level provider `default_tags` (if any) merge with these." type = map(string) default = {} } @@ -420,10 +420,12 @@ variable "backend_extra_secrets" { variable "proxy_config" { description = <<-EOT LiteLLM proxy config (the contents of config.yaml). Mirrors the helm - chart's `gateway.config.proxy_config` value. Passed to gateway, backend, - and the migration task as a base64-encoded env var and decoded to - /tmp/litellm-config.yaml at container start; CONFIG_FILE_PATH is set - automatically. + chart's `gateway.config.proxy_config` value. Uploaded to S3 under + `config/litellm-config.yaml` in the stack's bucket; gateway and backend + container entrypoints download it to /tmp/litellm-config.yaml at task + start (CONFIG_FILE_PATH is set automatically). The S3 object's etag is + wired into the task definition, so editing this value produces a new + task-def revision and a rolling redeploy. Example: proxy_config = { @@ -456,3 +458,78 @@ variable "log_retention_days" { type = number default = 30 } + +# ---------- OpenTelemetry v2 ---------- +# +# https://docs.litellm.ai/docs/observability/opentelemetry_v2 +# +# OTel v2 is opt-in and gated entirely on otel_endpoint, matching the GCP +# stack. Leave otel_endpoint = "" and nothing OTel-related lands in the +# container env. Set it and the gateway and backend gain LITELLM_OTEL_V2=true +# plus the OTEL_* block (per-component OTEL_SERVICE_NAME, exporter, endpoint, +# environment name, capture-content), with OTEL_HEADERS sourced from +# otel_headers_secret_arn when provided. + +variable "otel_endpoint" { + description = <<-EOT + OTLP collector endpoint (sets OTEL_ENDPOINT). Empty disables OTel + entirely (no LITELLM_OTEL_V2, no OTEL_* env). Point at any + OTLP-compatible backend (self-hosted collector, Grafana Tempo, + Honeycomb, Datadog). Example: "http://otel-collector.internal:4318" + for OTLP/HTTP. + EOT + type = string + default = "" +} + +variable "otel_exporter" { + description = <<-EOT + OTLP exporter protocol. One of "otlp_http", "otlp_grpc", or "console" + (stdout, useful for verifying instrumentation against CloudWatch logs). + Ignored when otel_endpoint is empty. + EOT + type = string + default = "otlp_http" + + validation { + condition = contains(["otlp_http", "otlp_grpc", "console"], var.otel_exporter) + error_message = "otel_exporter must be one of: otlp_http, otlp_grpc, console." + } +} + +variable "otel_environment_name" { + description = <<-EOT + Value for OTEL_ENVIRONMENT_NAME (becomes `deployment.environment` on + every span). Defaults to var.env when empty so spans land tagged with + the deployment env without extra wiring. + EOT + type = string + default = "" +} + +variable "otel_capture_message_content" { + description = <<-EOT + Value for OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT. Default + `no_content` matches the litellm default; flip to `prompt_and_completion` + only when you've audited what's about to land in your observability + backend, because raw prompts/completions are typically sensitive. + EOT + type = string + default = "no_content" + + validation { + condition = contains(["no_content", "prompt_and_completion"], var.otel_capture_message_content) + error_message = "otel_capture_message_content must be one of: no_content, prompt_and_completion." + } +} + +variable "otel_headers_secret_arn" { + description = <<-EOT + Secrets Manager ARN whose plaintext value becomes OTEL_HEADERS + (comma-separated `key=value` pairs, typically used to pass an API key + header to a managed collector). The execution role auto-gains + secretsmanager:GetSecretValue on this ARN. Empty omits OTEL_HEADERS. + EOT + type = string + default = "" +} diff --git a/terraform/litellm/gcp/README.md b/terraform/litellm/gcp/README.md index 504cfa066e4..1e0bf4319df 100644 --- a/terraform/litellm/gcp/README.md +++ b/terraform/litellm/gcp/README.md @@ -1,5 +1,9 @@ # LiteLLM on GCP (Cloud Run) +[![Open in Cloud Shell](https://gstatic.com/cloudssh/images/open-btn.svg)](https://ssh.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https%3A%2F%2Fgithub.com%2FBerriAI%2Flitellm&cloudshell_workspace=terraform%2Flitellm%2Fgcp%2Fexamples%2Fdefault&cloudshell_tutorial=TUTORIAL.md&cloudshell_image=gcr.io/ds-artifacts-cloudshell/deploystack_custom_image&shellonly=true) + +The button above opens the [DeployStack](https://github.com/GoogleCloudPlatform/deploystack) installer in Cloud Shell, walks you through `TUTORIAL.md`, and runs `terraform apply` once you've answered the prompts. The rest of this README is the manual / advanced path. + Deploys the componentized LiteLLM proxy on GCP: - **VPC** + Private Services Access range + a Serverless VPC Access connector @@ -25,10 +29,14 @@ and `litellm-migrations` (slim image used only by the one-off Cloud Run Job — runs `prisma migrate deploy` against the writer DB and exits). Bump them together when bumping LiteLLM. -Cloud Run only accepts images from Artifact Registry, `[region.]gcr.io`, -or `docker.io` — `ghcr.io` URIs are rejected at apply time. The four -images are published to GHCR upstream, so any real deploy needs an -Artifact Registry remote repository pointed at GHCR. +**Required override.** The `image_registry` default (`ghcr.io/berriai`) +does **not** work as-is — Cloud Run only accepts images from Artifact +Registry, `[region.]gcr.io`, or `docker.io`, and rejects `ghcr.io` URIs +at apply time. Every deploy (including HCP Terraform 1-click) must +supply either `image_registry` pointed at an Artifact Registry remote +repo backed by GHCR, or full per-component `*_image` URIs against +images you've already mirrored. The default is present only so +`terraform plan` succeeds during local iteration. **One-time setup (per project):** create a remote repo and let Cloud Run pull through it. @@ -102,9 +110,13 @@ Unix socket. ### `proxy_config` Mirrors the helm chart's `gateway.config.proxy_config`. The map is -YAML-encoded and base64-passed to gateway, backend, and the migration job; -each container decodes it to `/tmp/litellm-config.yaml` at startup and sets -`CONFIG_FILE_PATH`. +YAML-encoded and uploaded to a dedicated GCS bucket as `config.yaml`, then +mounted read-only into the gateway and backend at `/etc/litellm` via Cloud +Run v2's gcsfuse volume. `CONFIG_FILE_PATH` points at the mount path. A +hash of the YAML rides along as an env var so an edit to `proxy_config` +forces a new Cloud Run revision; without it the new file would sit in the +bucket unread until the next unrelated revision rollover. The migrations +job doesn't get the config (it only runs `prisma migrate deploy`). ```hcl proxy_config = { @@ -160,6 +172,38 @@ reject the version suffix; version is always resolved as `latest`. If you need a pinned version, edit `local.gateway_extra_secret_kv` in `cloudrun.tf` directly to set `version = "3"` for the entry in question. +### OpenTelemetry v2 + +OTel v2 (https://docs.litellm.ai/docs/observability/opentelemetry_v2) is +opt-in and gated entirely on `otel_endpoint`. Empty (default) and nothing +OTel-related lands in the container env. Set it and both gateway and +backend gain `LITELLM_OTEL_V2=true` plus the `OTEL_*` block, with +`OTEL_SERVICE_NAME` stamped per component (`${tenant}-litellm-${env}-gateway` +and `-backend`) so spans land tagged with the right hop. Any `OTEL_*` key +set in `gateway_extra_env` / `backend_extra_env` overrides the default for +that service (Cloud Run rejects duplicate env names, so the override is +predictable). + +```hcl +otel_endpoint = "https://otel.example.com:4318" +otel_exporter = "otlp_http" # or otlp_grpc +otel_environment_name = "prod" # default: var.env +otel_headers_secret = "projects/my-gcp-project/secrets/otel-headers" +``` + +`OTEL_HEADERS` is wired as a Secret Manager `secret_key_ref` since it +typically carries the collector's auth token; create the secret with the +literal header string, e.g. `Authorization=Bearer `. + +`OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` defaults to +`no_content`; flip `otel_capture_message_content = "prompt_and_completion"` +only after auditing what lands in the backend, since prompts and +completions are typically sensitive. + +Behavior matches the AWS stack 1:1; the only naming differences are +`otel_headers_secret` (a Secret Manager resource ID) vs AWS's +`otel_headers_secret_arn` (a Secrets Manager ARN). + ## Tenant deployment Every resource the stack creates is named `${tenant}-litellm-${env}` (or @@ -173,20 +217,25 @@ pair differs: | `acme` | `prod` | `acme-litellm-prod-master-key` | | `globex` | `dev` | `globex-litellm-dev-license` | -For a per-tenant instance, the only inputs that change are the tenant -slug, env, and the two pre-issued secrets: +For a per-tenant instance via the example root, the only inputs that +change are the tenant slug, env, and the two pre-issued secrets: ```bash +cd terraform/litellm/gcp/examples/default export TF_VAR_litellm_master_key="sk-..." # the tenant's master key export TF_VAR_litellm_license="lic-..." # their LITELLM_LICENSE terraform apply \ - -var "project=my-gcp-project" \ + -var "project_id=my-gcp-project" \ -var "region=us-central1" \ -var "tenant=acme" \ -var "env=stage" ``` +To run *many* tenants from a single config, call the module with +`for_each` instead of one root per tenant — only possible because the +module declares no provider block (see "Using as a module"). + Both `litellm_master_key` and `litellm_license` are optional: - Omit `litellm_master_key` → the stack auto-generates a random `sk-…` value (trial/dev path). @@ -200,14 +249,22 @@ example files. ## Quick start ```bash -cd terraform/litellm/gcp +cd terraform/litellm/gcp/examples/default cp terraform.tfvars.example terraform.tfvars -# Edit: project, region, tenant, env, *_image, proxy_config, gateway_extra_secrets. +# Edit: project, region, tenant, env, image_registry, proxy_config, gateway_extra_secrets. terraform init terraform apply ``` +`examples/default/` is a thin root that configures the `google` / +`google-beta` providers and calls the module (`../../`). It exposes a +curated variable surface; for advanced knobs (per-component +CPU/memory/instances, Cloud SQL tier/edition, Memorystore tier, +per-component image pins) set them on the `module "litellm"` block in +`examples/default/main.tf`, or call the module from your own config — see +"Using as a module" below. + That single apply provisions everything, runs the prisma schema migration via the Cloud Run job (auto-triggered by `bootstrap.tf`), and only then starts the gateway/backend services. When it returns, the stack is serving traffic. @@ -251,6 +308,56 @@ Set `allow_plaintext_lb = true` and leave `lb_domains = []`. Without the flag, plan fails with a clear error pointing at the precondition. Intended for short-lived trial / dev stacks only. +## Using as a module + +The directory itself is a module with **no `provider` block** — the caller +owns provider config. You can call it directly with `for_each` (many +tenants from one config), `count`, `depends_on`, or providers configured +to impersonate a service account / target a different project: + +```hcl +provider "google" { + project = "my-gcp-project" + region = "us-central1" +} +provider "google-beta" { + project = "my-gcp-project" + region = "us-central1" +} + +module "litellm" { + source = "github.com/BerriAI/litellm//terraform/litellm/gcp?ref=" + + project = "my-gcp-project" + region = "us-central1" + tenant = "acme" + env = "prod" + # ...any of the inputs in variables.tf... +} +``` + +Both the default `google` and `google-beta` configs are inherited by the +module automatically through the call; declare both in the caller. + +Labels: the module stamps its own `litellm-stack` and `managed-by` labels +onto every label-supporting resource (Cloud Run services and the +migrations job, Cloud SQL writer and reader, Memorystore, Secret Manager +entries, GCS buckets, the LB global address and forwarding rules) and +merges `var.labels` on top. Use the `labels` input for per-deployment +labels; mirrors the AWS stack's `tags` input. + +**`for_each` shares one provider config.** The module's `versions.tf` declares +`google` / `google-beta` *without* `configuration_aliases`, so it only ever +receives the caller's single default (unaliased) `google` / `google-beta` +providers. That's deliberate — it keeps the one-command path simple — but it +means a `for_each` over the module runs every instance against the **same +project, region, and credentials**. Use `for_each` for many tenants in one +project (distinct `tenant`/`env`); it cannot fan out across projects or regions +on its own. To deploy into separate projects/regions, give each its own root +with its own provider config (one `examples/default`-style root per project), +or fork the module to add `configuration_aliases` and pass per-instance +`providers = { ... }`. + ## Storage and database retention Two opt-in tripwires guard against accidental data loss on @@ -281,8 +388,8 @@ or point them at your own CA. | File | What's in it | | ----------------- | -------------------------------------------------------------------- | -| `versions.tf` | Terraform + provider version constraints | -| `providers.tf` | Google + Google-Beta providers | +| `versions.tf` | Terraform + `required_providers` constraints (module declares no provider config) | +| `examples/default/` | Thin root: `google` / `google-beta` providers + a call to the module. The one-command deploy path. | | `variables.tf` | All input variables | | `locals.tf` | Path-prefix lists (mirror of `helm/.../ingress.yaml`) + proxy_config helpers | | `network.tf` | VPC, subnet, PSA range, Serverless VPC connector | diff --git a/terraform/litellm/gcp/bootstrap.tf b/terraform/litellm/gcp/bootstrap.tf index 47ad885ff12..b929c4d76f3 100644 --- a/terraform/litellm/gcp/bootstrap.tf +++ b/terraform/litellm/gcp/bootstrap.tf @@ -25,7 +25,7 @@ resource "terraform_data" "migration" { environment = { JOB = google_cloud_run_v2_job.migrations.name REGION = var.region - PROJECT = var.project + PROJECT = var.project_id } command = <<-EOT set -euo pipefail diff --git a/terraform/litellm/gcp/cloudrun.tf b/terraform/litellm/gcp/cloudrun.tf index 28e1145b081..3e3f5f6924b 100644 --- a/terraform/litellm/gcp/cloudrun.tf +++ b/terraform/litellm/gcp/cloudrun.tf @@ -26,6 +26,39 @@ locals { { name = "GCS_BUCKET_NAME", value = google_storage_bucket.this.name }, ] + # OTel v2 is opt-in and gated on otel_endpoint, matching the AWS stack — + # nothing OTel-related is added to the container env until an endpoint is + # set. LITELLM_OTEL_V2 flips on alongside the OTEL_* block so the proxy + # never boots the instrumentation with no exporter wired in. + otel_enabled = var.otel_endpoint != "" + otel_environment_name = var.otel_environment_name != "" ? var.otel_environment_name : var.env + otel_shared_endpoint_kv = local.otel_enabled ? [ + { name = "LITELLM_OTEL_V2", value = "true" }, + { name = "OTEL_EXPORTER", value = var.otel_exporter }, + { name = "OTEL_ENDPOINT", value = var.otel_endpoint }, + { name = "OTEL_ENVIRONMENT_NAME", value = local.otel_environment_name }, + { name = "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", value = var.otel_capture_message_content }, + ] : [] + # OTel defaults are filtered out when the same key appears in + # *_extra_env, so a caller-supplied OTEL_SERVICE_NAME (or any other + # OTEL_*) takes precedence without colliding at Cloud Run apply time + # (Cloud Run rejects duplicate env var names). + gateway_otel_env_kv_raw = concat(local.otel_shared_endpoint_kv, local.otel_enabled ? [ + { name = "OTEL_SERVICE_NAME", value = "${local.name}-gateway" }, + ] : []) + backend_otel_env_kv_raw = concat(local.otel_shared_endpoint_kv, local.otel_enabled ? [ + { name = "OTEL_SERVICE_NAME", value = "${local.name}-backend" }, + ] : []) + gateway_otel_env_kv = [ + for e in local.gateway_otel_env_kv_raw : e if !contains(keys(var.gateway_extra_env), e.name) + ] + backend_otel_env_kv = [ + for e in local.backend_otel_env_kv_raw : e if !contains(keys(var.backend_extra_env), e.name) + ] + otel_env_secrets = local.otel_enabled && var.otel_headers_secret != "" ? [ + { name = "OTEL_HEADERS", secret = var.otel_headers_secret, version = "latest" }, + ] : [] + # Cloud Run v2 secret env vars use value_source.secret_key_ref pointing at a # secret resource ID. Shared between gateway and backend (the migrations # job has its own narrower env list — see migrations_env_secrets below). @@ -63,13 +96,6 @@ locals { for k, v in var.backend_extra_secrets : { name = k, secret = v, version = "latest" } ] - # Shell fragments composed with && so any failure short-circuits the - # whole startup instead of falling through to `exec uvicorn`. The - # python step is only included when the caller provided a proxy_config. - proxy_config_fragment = local.proxy_config_enabled ? [ - "python -c \"import os, base64, pathlib; pathlib.Path(os.environ['CONFIG_FILE_PATH']).write_bytes(base64.b64decode(os.environ['LITELLM_PROXY_CONFIG_B64']))\"" - ] : [] - # Decode the Memorystore CA cert (passed as REDIS_CA_PEM_B64) to the # path REDIS_SSL_CA_CERTS points at, so the redis-py client can validate # the rediss:// handshake. @@ -83,14 +109,12 @@ locals { ] gateway_args = join(" && ", concat( - local.proxy_config_fragment, local.redis_ca_fragment, local.database_url_fragment, - ["exec uvicorn gateway.main:app --host 0.0.0.0 --port 4000"], + ["exec uvicorn gateway.main:app --host 0.0.0.0 --port 4000 --workers ${var.gateway_num_workers}"], )) backend_args = join(" && ", concat( - local.proxy_config_fragment, local.redis_ca_fragment, local.database_url_fragment, ["exec uvicorn backend.main:app --host 0.0.0.0 --port 4001"], @@ -117,6 +141,7 @@ resource "google_cloud_run_v2_service" "gateway" { name = "${local.name}-gateway" location = var.region ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER" + labels = local.labels template { service_account = google_service_account.runtime.email @@ -149,7 +174,7 @@ resource "google_cloud_run_v2_service" "gateway" { } dynamic "env" { - for_each = concat(local.shared_env_kv, local.gateway_extra_env_kv, local.proxy_config_env) + for_each = concat(local.shared_env_kv, local.gateway_otel_env_kv, local.gateway_extra_env_kv, local.proxy_config_env) content { name = env.value.name value = env.value.value @@ -157,7 +182,7 @@ resource "google_cloud_run_v2_service" "gateway" { } dynamic "env" { - for_each = concat(local.shared_env_secrets, local.gateway_extra_secret_kv) + for_each = concat(local.shared_env_secrets, local.otel_env_secrets, local.gateway_extra_secret_kv) content { name = env.value.name value_source { @@ -169,6 +194,14 @@ resource "google_cloud_run_v2_service" "gateway" { } } + dynamic "volume_mounts" { + for_each = local.proxy_config_enabled ? [1] : [] + content { + name = local.proxy_config_volume + mount_path = local.proxy_config_mount_path + } + } + startup_probe { http_get { path = "/health/readiness" @@ -189,6 +222,17 @@ resource "google_cloud_run_v2_service" "gateway" { timeout_seconds = 5 } } + + dynamic "volumes" { + for_each = local.proxy_config_enabled ? [1] : [] + content { + name = local.proxy_config_volume + gcs { + bucket = google_storage_bucket.proxy_config[0].name + read_only = true + } + } + } } depends_on = [ @@ -196,6 +240,8 @@ resource "google_cloud_run_v2_service" "gateway" { google_secret_manager_secret_iam_member.db_password, google_secret_manager_secret_iam_member.license, google_secret_manager_secret_iam_member.extras, + google_secret_manager_secret_iam_member.otel_headers, + google_storage_bucket_iam_member.proxy_config_runtime, google_sql_user.app, # Don't go live until the schema is migrated; otherwise the proxy boots, # fails on missing tables, and Cloud Run keeps cold-restarting. @@ -208,6 +254,7 @@ resource "google_cloud_run_v2_service" "backend" { name = "${local.name}-backend" location = var.region ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER" + labels = local.labels template { service_account = google_service_account.runtime.email @@ -240,7 +287,7 @@ resource "google_cloud_run_v2_service" "backend" { } dynamic "env" { - for_each = concat(local.shared_env_kv, local.backend_default_env_kv, local.backend_extra_env_kv, local.proxy_config_env) + for_each = concat(local.shared_env_kv, local.backend_default_env_kv, local.backend_otel_env_kv, local.backend_extra_env_kv, local.proxy_config_env) content { name = env.value.name value = env.value.value @@ -248,7 +295,7 @@ resource "google_cloud_run_v2_service" "backend" { } dynamic "env" { - for_each = concat(local.shared_env_secrets, local.backend_managed_env_secrets, local.backend_extra_secret_kv) + for_each = concat(local.shared_env_secrets, local.backend_managed_env_secrets, local.otel_env_secrets, local.backend_extra_secret_kv) content { name = env.value.name value_source { @@ -260,6 +307,14 @@ resource "google_cloud_run_v2_service" "backend" { } } + dynamic "volume_mounts" { + for_each = local.proxy_config_enabled ? [1] : [] + content { + name = local.proxy_config_volume + mount_path = local.proxy_config_mount_path + } + } + startup_probe { http_get { path = "/health/readiness" @@ -280,6 +335,17 @@ resource "google_cloud_run_v2_service" "backend" { timeout_seconds = 5 } } + + dynamic "volumes" { + for_each = local.proxy_config_enabled ? [1] : [] + content { + name = local.proxy_config_volume + gcs { + bucket = google_storage_bucket.proxy_config[0].name + read_only = true + } + } + } } depends_on = [ @@ -288,6 +354,8 @@ resource "google_cloud_run_v2_service" "backend" { google_secret_manager_secret_iam_member.license, google_secret_manager_secret_iam_member.ui_password, google_secret_manager_secret_iam_member.extras, + google_secret_manager_secret_iam_member.otel_headers, + google_storage_bucket_iam_member.proxy_config_runtime, google_sql_user.app, terraform_data.migration, ] @@ -301,6 +369,7 @@ resource "google_cloud_run_v2_service" "ui" { name = "${local.name}-ui" location = var.region ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER" + labels = local.labels template { service_account = google_service_account.ui_runtime.email @@ -344,7 +413,7 @@ resource "google_cloud_run_v2_service" "ui" { # (LITELLM_MASTER_KEY); these IAM bindings just open up Cloud Run's invoker # gate so the LB request makes it to the container. resource "google_cloud_run_v2_service_iam_member" "gateway_allusers" { - project = var.project + project = var.project_id location = google_cloud_run_v2_service.gateway.location name = google_cloud_run_v2_service.gateway.name role = "roles/run.invoker" @@ -352,7 +421,7 @@ resource "google_cloud_run_v2_service_iam_member" "gateway_allusers" { } resource "google_cloud_run_v2_service_iam_member" "backend_allusers" { - project = var.project + project = var.project_id location = google_cloud_run_v2_service.backend.location name = google_cloud_run_v2_service.backend.name role = "roles/run.invoker" @@ -360,7 +429,7 @@ resource "google_cloud_run_v2_service_iam_member" "backend_allusers" { } resource "google_cloud_run_v2_service_iam_member" "ui_allusers" { - project = var.project + project = var.project_id location = google_cloud_run_v2_service.ui.location name = google_cloud_run_v2_service.ui.name role = "roles/run.invoker" @@ -374,6 +443,7 @@ resource "google_cloud_run_v2_service_iam_member" "ui_allusers" { resource "google_cloud_run_v2_job" "migrations" { name = "${local.name}-migrations" location = var.region + labels = local.labels template { template { diff --git a/terraform/litellm/gcp/cloudsql.tf b/terraform/litellm/gcp/cloudsql.tf index 70939c049c3..0af15eec22f 100644 --- a/terraform/litellm/gcp/cloudsql.tf +++ b/terraform/litellm/gcp/cloudsql.tf @@ -26,6 +26,8 @@ resource "google_sql_database_instance" "writer" { disk_size = 20 disk_autoresize = true + user_labels = local.labels + backup_configuration { enabled = true point_in_time_recovery_enabled = true @@ -45,6 +47,15 @@ resource "google_sql_database_instance" "writer" { } deletion_protection = var.cloudsql_deletion_protection + + lifecycle { + # disk_autoresize grows storage but never shrinks it. Without this, + # the first plan after any auto-grow reads disk_size as a shrink, which + # is an immutable change and forces a destroy/recreate of the instance + # (full data loss). Set the initial size only; let Cloud SQL own it + # thereafter. + ignore_changes = [settings[0].disk_size] + } } resource "google_sql_database_instance" "reader" { @@ -61,6 +72,8 @@ resource "google_sql_database_instance" "reader" { availability_type = "ZONAL" disk_autoresize = true + user_labels = local.labels + ip_configuration { ipv4_enabled = false private_network = google_compute_network.this.id @@ -68,6 +81,12 @@ resource "google_sql_database_instance" "reader" { } deletion_protection = var.cloudsql_deletion_protection + + lifecycle { + # Same autoresize footgun as the writer — the replica grows its disk + # independently. Never let a perceived shrink replace the instance. + ignore_changes = [settings[0].disk_size] + } } resource "google_sql_database" "this" { @@ -91,6 +110,7 @@ resource "google_sql_user" "app" { resource "google_secret_manager_secret" "db_password" { secret_id = "${local.name}-db-password" + labels = local.labels replication { auto {} } diff --git a/terraform/litellm/gcp/examples/default/.terraform.lock.hcl b/terraform/litellm/gcp/examples/default/.terraform.lock.hcl new file mode 100644 index 00000000000..e6285567315 --- /dev/null +++ b/terraform/litellm/gcp/examples/default/.terraform.lock.hcl @@ -0,0 +1,63 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/google" { + version = "6.50.0" + constraints = "~> 6.10" + hashes = [ + "h1:79CwMTsp3Ud1nOl5hFS5mxQHyT0fGVye7pqpU0PPlHI=", + "zh:1f3513fcfcbf7ca53d667a168c5067a4dd91a4d4cccd19743e248ff31065503c", + "zh:3da7db8fc2c51a77dd958ea8baaa05c29cd7f829bd8941c26e2ea9cb3aadc1e5", + "zh:3e09ac3f6ca8111cbb659d38c251771829f4347ab159a12db195e211c76068bb", + "zh:7bb9e41c568df15ccf1a8946037355eefb4dfb4e35e3b190808bb7c4abae547d", + "zh:81e5d78bdec7778e6d67b5c3544777505db40a826b6eb5abe9b86d4ba396866b", + "zh:8d309d020fb321525883f5c4ea864df3d5942b6087f6656d6d8b3a1377f340fc", + "zh:93e112559655ab95a523193158f4a4ac0f2bfed7eeaa712010b85ebb551d5071", + "zh:d3efe589ffd625b300cef5917c4629513f77e3a7b111c9df65075f76a46a63c7", + "zh:d4a4d672bbef756a870d8f32b35925f8ce2ef4f6bbd5b71a3cb764f1b6c85421", + "zh:e13a86bca299ba8a118e80d5f84fbdd708fe600ecdceea1a13d4919c068379fe", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:fec30c095647b583a246c39d557704947195a1b7d41f81e369ba377d997faef6", + ] +} + +provider "registry.terraform.io/hashicorp/google-beta" { + version = "6.50.0" + constraints = "~> 6.10" + hashes = [ + "h1:P2GiUJM1frlPtBViwKn1A9V2dVBdGuWcX80w9TdH8ZE=", + "zh:18b442bd0a05321d39dda1e9e3f1bdede4e61bc2ac62cc7a67037a3864f75101", + "zh:2e387c51455862828bec923a3ec81abf63a4d998da470cf00e09003bda53d668", + "zh:3942e708fa84ebe54996086f4b1398cb747fe19cbcd0be07ace528291fb35dee", + "zh:496287dd48b34ae6197cb1f887abeafd07c33f389dbe431bb01e24846754cfdd", + "zh:6eca885419969ce5c2a706f34dce1f10bde9774757675f2d8a92d12e5a1be390", + "zh:710dbef826c3fe7f76f844dae47937e8e4c1279dd9205ec4610be04cf3327244", + "zh:777ebf44b24bfc7bdbf770dc089f1a72f143b4718fdedb8c6bd75983115a1ec2", + "zh:9c8703bba37b8c7ad857efc3513392c5a096c519397c1cb822d7612f38e4262f", + "zh:c4f1d3a73de2702277c99d5348ad6d374705bcfdd367ad964ff4cfd2cf06c281", + "zh:eca8df11af3f5a948492d5b8b5d01b4ec705aad10bc30ec1524205508ae28393", + "zh:f41e7fd5f2628e8fd6b8ea136366923858f54428d1729898925469b862c275c2", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.9.0" + constraints = "~> 3.6" + hashes = [ + "h1:OO+IuvQJSPmWdN8AyyIEvPJbLvDQpgX/zbktoa9KsJE=", + "zh:161ad0bd9a75768c82f53fb6e7172a9d8be2d4889b012645a34795031aaf1bf1", + "zh:19dc9a5b17729725ccfc4f45b0500af0ee5bc6b6b160c7adb8f2bf617d2c80ea", + "zh:269eda8fe42daa7974d5a34d166c3ba9defe80cde86c01e4dadcfdf2e1f05e5f", + "zh:373f7c65566f8f2cc7f45d698654feb9d988996957e1266a69ca00c52d6d16d0", + "zh:5599d16804c41c83009ec621b6d6b6f74e102f5827678a4750f8809055546b61", + "zh:583be0440469a22bff70dcfa56593b01566860b29607437264adb51060cf46fc", + "zh:5f211d8ec3f2e1f414870d9584bfe26e6995560ef81c748f8447a48164767398", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7b547fd16216761ef86efc3ed516ac5ac0c5c42b7c7eb24a08cef2d93f69ed5e", + "zh:7e7c0679daf2a382151d05068c8c3f0dae6b7b7dccf818827b73dd08638df2ef", + "zh:8089dec888a8038b9b4fb23b3df7e1057293dbc5b60b42cc47ff690d69d4b61b", + "zh:c51f15a031edfd6f23ce8ced3446ca7f8d8d647e2499890d7d5d10d5016d7257", + "zh:c94784f005708890dc6895afd53636ec00ec1e430b15d41e5aebfb1d4b39bd04", + ] +} diff --git a/terraform/litellm/gcp/examples/default/TUTORIAL.md b/terraform/litellm/gcp/examples/default/TUTORIAL.md new file mode 100644 index 00000000000..9026207563d --- /dev/null +++ b/terraform/litellm/gcp/examples/default/TUTORIAL.md @@ -0,0 +1,134 @@ +# Deploy LiteLLM on GCP + + + +This walkthrough provisions the full LiteLLM stack on GCP via Cloud Run, Cloud SQL, Memorystore Redis, and an external HTTPS load balancer. You'll answer a few prompts; DeployStack writes a `terraform.tfvars` and runs `terraform apply` against the project you select. + +## Prerequisites + + + +Pick the GCP project you want to deploy into, then make sure billing is enabled on it. The stack provisions paid resources (Cloud SQL, Memorystore, an LB anycast IP). + +## Enable required APIs + +The stack needs these APIs enabled in the target project. Click to enable, or run the gcloud command below. + + + +```bash +gcloud services enable \ + run.googleapis.com \ + sqladmin.googleapis.com \ + redis.googleapis.com \ + secretmanager.googleapis.com \ + vpcaccess.googleapis.com \ + compute.googleapis.com \ + servicenetworking.googleapis.com \ + storage.googleapis.com \ + artifactregistry.googleapis.com +``` + +## Create the Artifact Registry passthrough to GHCR + +Cloud Run only pulls from Artifact Registry, `gcr.io`, or `docker.io`; it rejects `ghcr.io` URIs at apply time. The four LiteLLM images live on GHCR, so the stack needs a remote Artifact Registry repo pointed at GHCR. This is a one-time setup per project. + +```bash +gcloud artifacts repositories create litellm \ + --repository-format=docker \ + --location= \ + --mode=remote-repository \ + --remote-repo-config-desc="GitHub Container Registry passthrough" \ + --remote-docker-repo=https://ghcr.io +``` + +If the repo already exists, this command exits with a clear error and you can move on. Then set `image_registry` in `terraform.tfvars` to `-docker.pkg.dev//litellm/berriai` before applying. + +## (Optional) Set tenant secrets + +The stack auto-generates a `LITELLM_MASTER_KEY` if you don't supply one. If you have an enterprise license or want a pre-chosen master key, export them as `TF_VAR_*` env vars before running the installer so they end up in Secret Manager but not in `terraform.tfvars`. + +```bash +export TF_VAR_litellm_master_key="sk-..." # optional; auto-generated if omitted +export TF_VAR_litellm_license="lic-..." # optional; OSS-only without it +export TF_VAR_ui_password="..." # optional; falls back to master_key for UI login +``` + +Skip this step entirely for a trial deploy. + +## Run the installer + +DeployStack will prompt for project, region, tenant, env, image tag, and TLS posture, then run `terraform apply`. Open `deploystack.json` if you want to see the prompt definitions first. + +```bash +deploystack install +``` + +The first apply takes 20-25 minutes; most of that is Cloud SQL provisioning. The migration Cloud Run Job runs automatically once the database is ready, and only then do gateway, backend, and UI start. + +## Grab the LB URL + +```bash +terraform output lb_url +``` + +For trial deploys (`allow_plaintext_lb=true`), this is `http://`. The UI lives at `/ui`; sign in with username `admin` and the master key: + +```bash +gcloud secrets versions access latest \ + --secret="$(terraform output -raw master_key_secret_id)" +``` + +## Going to TLS + +If you picked `allow_plaintext_lb=true` to bootstrap but want HTTPS for real, point a DNS A record at the LB IP, then re-run terraform with `lb_domains` set and `allow_plaintext_lb` removed: + +```bash +terraform apply \ + -var 'lb_domains=["proxy.example.com"]' +``` + +Google-managed certs sit in `PROVISIONING` for 15-60 minutes after DNS propagates. You can watch the state with `gcloud compute ssl-certificates describe -litellm--cert`. + +## Adding provider API keys + +Provider keys (OpenAI, Anthropic, etc.) belong in Secret Manager, not in `terraform.tfvars`. Create the secret first, then reference its resource ID from `gateway_extra_secrets` and re-apply: + +```bash +echo -n "sk-proj-..." | gcloud secrets create openai-api-key --data-file=- +``` + +Edit `terraform.tfvars`: + +```hcl +gateway_extra_secrets = { + OPENAI_API_KEY = "projects//secrets/openai-api-key" +} +proxy_config = { + model_list = [ + { + model_name = "gpt-4o" + litellm_params = { + model = "openai/gpt-4o" + api_key = "os.environ/OPENAI_API_KEY" + } + }, + ] +} +``` + +Then `terraform apply`. + +## Tearing it all down + +```bash +deploystack uninstall +``` + +`cloudsql_deletion_protection` is `true` by default; flip it to `false` in `terraform.tfvars` and apply before uninstalling if you actually want the DB gone. Same goes for `gcs_force_destroy` on the bucket. + +## You're done + + + +Full configuration reference is in `README.md`, and every input variable on the underlying module lives in `variables.tf`. diff --git a/terraform/litellm/gcp/examples/default/deploystack.json b/terraform/litellm/gcp/examples/default/deploystack.json new file mode 100644 index 00000000000..6e5339272e7 --- /dev/null +++ b/terraform/litellm/gcp/examples/default/deploystack.json @@ -0,0 +1,37 @@ +{ + "title": "LiteLLM on GCP (Cloud Run)", + "name": "litellm-gcp", + "description": "Deploys the LiteLLM proxy on GCP: Cloud Run gateway/backend/UI, Cloud SQL with a read replica, Memorystore Redis, a GCS bucket, Secret Manager entries, and an external HTTPS load balancer. Takes ~20-25 minutes on the first apply.", + "duration": 25, + "documentation_link": "https://github.com/BerriAI/litellm/blob/main/terraform/litellm/gcp/README.md", + "collect_project": true, + "collect_region": true, + "region_type": "run", + "region_default": "us-central1", + "collect_zone": false, + "custom_settings": [ + { + "name": "tenant", + "description": "Tenant slug used as the prefix for every GCP resource the stack creates (e.g. 'acme' produces 'acme-litellm--gateway'). 1-21 lowercase chars starting with a letter", + "default": "acme", + "validation": "^[a-z][a-z0-9-]{0,20}$" + }, + { + "name": "env", + "description": "Environment suffix appended to every resource name (e.g. 'stage', 'prod', 'dev'). 1-9 lowercase chars starting with a letter", + "default": "stage", + "validation": "^[a-z][a-z0-9-]{0,8}$" + }, + { + "name": "image_tag", + "description": "Tag for the four litellm-* images (gateway, backend, ui, migrations). Bump together when bumping LiteLLM", + "default": "v1.86.0-dev" + }, + { + "name": "allow_plaintext_lb", + "description": "Skip TLS on the load balancer (HTTP-only). Set true for trial/dev. For production, leave false and add lb_domains to terraform.tfvars after the first apply", + "default": "true", + "options": ["true", "false"] + } + ] +} diff --git a/terraform/litellm/gcp/examples/default/main.tf b/terraform/litellm/gcp/examples/default/main.tf new file mode 100644 index 00000000000..8760d445f0c --- /dev/null +++ b/terraform/litellm/gcp/examples/default/main.tf @@ -0,0 +1,51 @@ +# One-command deploy of the LiteLLM GCP stack. +# +# cd terraform/litellm/gcp/examples/default +# cp terraform.tfvars.example terraform.tfvars # edit it +# terraform init +# terraform apply +# +# This root just wires the providers (see providers.tf) to the module. The +# module itself (../../) declares no provider, so it can also be consumed +# from your own config with count/for_each or impersonated-SA providers: +# +# module "litellm" { +# source = "github.com/BerriAI/litellm//terraform/litellm/gcp?ref=" +# ... +# } +# +# Note: the module declares no `configuration_aliases`, so it receives only the +# caller's single default google/google-beta providers — a `for_each` over it +# runs every instance against the same project/region/credentials. To fan out +# across projects or regions, use one root per project. See the GCP README's +# "Using as a module" section. +# +# Knobs not surfaced as variables here (per-component sizing/instances, +# Cloud SQL tier/edition, Memorystore tier, per-component image overrides) +# can be set directly on this block — see ../../variables.tf. +module "litellm" { + source = "../../" + + project_id = var.project_id + region = var.region + tenant = var.tenant + env = var.env + + litellm_master_key = var.litellm_master_key + litellm_license = var.litellm_license + ui_password = var.ui_password + + image_registry = var.image_registry + image_tag = var.image_tag + + lb_domains = var.lb_domains + allow_plaintext_lb = var.allow_plaintext_lb + cloudsql_deletion_protection = var.cloudsql_deletion_protection + gcs_force_destroy = var.gcs_force_destroy + + proxy_config = var.proxy_config + gateway_extra_env = var.gateway_extra_env + backend_extra_env = var.backend_extra_env + gateway_extra_secrets = var.gateway_extra_secrets + backend_extra_secrets = var.backend_extra_secrets +} diff --git a/terraform/litellm/gcp/examples/default/outputs.tf b/terraform/litellm/gcp/examples/default/outputs.tf new file mode 100644 index 00000000000..3a9343c4850 --- /dev/null +++ b/terraform/litellm/gcp/examples/default/outputs.tf @@ -0,0 +1,59 @@ +output "lb_ip" { + description = "Global anycast IP of the external load balancer." + value = module.litellm.lb_ip +} + +output "lb_url" { + description = "Proxy URL. Dashboard at /, API at /v1/*." + value = module.litellm.lb_url +} + +output "gateway_service_url" { + description = "Default Cloud Run URL for the gateway (bypasses the LB)." + value = module.litellm.gateway_service_url +} + +output "backend_service_url" { + description = "Default Cloud Run URL for the backend (bypasses the LB)." + value = module.litellm.backend_service_url +} + +output "ui_service_url" { + description = "Default Cloud Run URL for the UI (bypasses the LB)." + value = module.litellm.ui_service_url +} + +output "cloudsql_writer_ip" { + description = "Private IP of the Cloud SQL writer." + value = module.litellm.cloudsql_writer_ip +} + +output "cloudsql_reader_ip" { + description = "Private IP of the Cloud SQL read replica." + value = module.litellm.cloudsql_reader_ip +} + +output "redis_endpoint" { + description = "Memorystore Redis endpoint." + value = module.litellm.redis_endpoint +} + +output "gcs_bucket" { + description = "GCS bucket name." + value = module.litellm.gcs_bucket +} + +output "master_key_secret_id" { + description = "Secret Manager resource ID holding LITELLM_MASTER_KEY." + value = module.litellm.master_key_secret_id +} + +output "db_password_secret_id" { + description = "Secret Manager resource ID holding the Cloud SQL app-user password." + value = module.litellm.db_password_secret_id +} + +output "migration_run_command" { + description = "Break-glass command to re-run the one-off migration job." + value = module.litellm.migration_run_command +} diff --git a/terraform/litellm/gcp/examples/default/providers.tf b/terraform/litellm/gcp/examples/default/providers.tf new file mode 100644 index 00000000000..d4a9836e887 --- /dev/null +++ b/terraform/litellm/gcp/examples/default/providers.tf @@ -0,0 +1,17 @@ +# Providers are configured HERE, in the root, not in the module. A module +# that declares its own configured `provider` block can't be called with +# count/for_each/depends_on and gives the caller no way to set an +# impersonated service account, a different project, or aliases. +# +# The module's resources inherit these default (unaliased) `google` / +# `google-beta` configs automatically through the module call, so project +# and region set here flow into every resource that doesn't pass its own. +provider "google" { + project = var.project_id + region = var.region +} + +provider "google-beta" { + project = var.project_id + region = var.region +} diff --git a/terraform/litellm/gcp/terraform.tfvars.example b/terraform/litellm/gcp/examples/default/terraform.tfvars.example similarity index 70% rename from terraform/litellm/gcp/terraform.tfvars.example rename to terraform/litellm/gcp/examples/default/terraform.tfvars.example index 5c22a14c6d6..6358ec96e6d 100644 --- a/terraform/litellm/gcp/terraform.tfvars.example +++ b/terraform/litellm/gcp/examples/default/terraform.tfvars.example @@ -1,5 +1,5 @@ -project = "my-gcp-project" -region = "us-central1" +project_id = "my-gcp-project" +region = "us-central1" # Resource naming: every GCP resource the stack creates is named # `${tenant}-litellm-${env}` (or that plus a per-resource suffix). E.g. @@ -28,14 +28,14 @@ env = "stage" # cloudsql_deletion_protection = true # default: refuse destroy on the DB # gcs_force_destroy = false # default: refuse destroy on a non-empty bucket -# Component images. Defaults pin all four to the same GHCR release tag — -# bump them together when bumping LiteLLM. To use private images, mirror -# them into Artifact Registry first — Cloud Run only authenticates against -# AR / gcr.io. -# gateway_image = "us-central1-docker.pkg.dev/my-gcp-project/litellm/gateway:1.86.0-dev" -# backend_image = "us-central1-docker.pkg.dev/my-gcp-project/litellm/backend:1.86.0-dev" -# ui_image = "us-central1-docker.pkg.dev/my-gcp-project/litellm/ui:1.86.0-dev" -# migrations_image = "us-central1-docker.pkg.dev/my-gcp-project/litellm/migrations:1.86.0-dev" +# Images. Cloud Run rejects ghcr.io, so a real deploy must point +# image_registry at an Artifact Registry remote repo (see README "Image +# pulls"); image_tag is applied to all four litellm-* images. Per-component +# *_image overrides are NOT exposed here — set them directly on the +# `module "litellm"` block in main.tf (see ../../variables.tf) if you need +# to mix-and-match versions. +# image_registry = "us-central1-docker.pkg.dev/my-gcp-project/litellm/berriai" +# image_tag = "v1.86.0-dev" # ---------- proxy_config (mirrors helm gateway.config.proxy_config) ---------- # proxy_config = { @@ -75,3 +75,13 @@ env = "stage" # OPENAI_API_KEY = "projects/my-gcp-project/secrets/openai-api-key" # ANTHROPIC_API_KEY = "projects/my-gcp-project/secrets/anthropic-api-key" # } + +# ---------- OpenTelemetry v2 ---------- +# OTel is gated on otel_endpoint: empty (default) and nothing is added to +# the container env; set it and both gateway and backend gain +# LITELLM_OTEL_V2=true plus the OTEL_* block (with OTEL_SERVICE_NAME +# stamped per component). These knobs aren't surfaced as wrapper vars in +# this example; set them directly on the `module "litellm"` block in +# main.tf (otel_endpoint, otel_exporter, otel_environment_name, +# otel_capture_message_content, otel_headers_secret). Full docs in +# ../../variables.tf. diff --git a/terraform/litellm/gcp/examples/default/variables.tf b/terraform/litellm/gcp/examples/default/variables.tf new file mode 100644 index 00000000000..56e5ec88ef8 --- /dev/null +++ b/terraform/litellm/gcp/examples/default/variables.tf @@ -0,0 +1,120 @@ +# Curated surface for the one-command deploy path. The module (../../) +# exposes far more knobs (per-component CPU/memory/instances, Cloud SQL +# tier/edition, Memorystore tier, per-component image overrides, …). To +# tune those, set them directly on the `module "litellm"` block in +# main.tf, or call the module from your own root config. Full per-variable +# docs live in ../../variables.tf — the module is the source of truth. + +variable "project_id" { + description = "GCP project ID." + type = string +} + +variable "region" { + description = "GCP region for VPC, Cloud SQL, Memorystore, Cloud Run, and the LB IP." + type = string + default = "us-central1" +} + +variable "tenant" { + description = "Tenant slug — prefix for every resource (-litellm-)." + type = string +} + +variable "env" { + description = "Environment suffix (stage, prod, dev)." + type = string +} + +# Sensitive — prefer TF_VAR_litellm_master_key / TF_VAR_litellm_license / +# TF_VAR_ui_password so values stay out of any committed tfvars file. +variable "litellm_master_key" { + description = "Pre-existing LITELLM_MASTER_KEY (sk-…). Empty → auto-generated." + type = string + default = "" + sensitive = true +} + +variable "litellm_license" { + description = "LiteLLM enterprise license. Empty → OSS-only." + type = string + default = "" + sensitive = true +} + +variable "ui_password" { + description = "UI admin password. Empty → falls back to LITELLM_MASTER_KEY." + type = string + default = "" + sensitive = true +} + +# Image source. Cloud Run rejects ghcr.io, so a real deploy must point +# image_registry at an Artifact Registry remote repo (see README "Image +# pulls"). Per-component overrides live in ../../variables.tf. +variable "image_registry" { + description = "Registry path prefix; images composed as /litellm-:." + type = string + default = "ghcr.io/berriai" +} + +variable "image_tag" { + description = "Tag applied to all four litellm-* images. Bump in lockstep." + type = string + default = "v1.86.0-dev" +} + +# TLS — provide DNS names for a managed cert, or opt into HTTP-only for dev. +variable "lb_domains" { + description = "DNS names (already pointing at lb_ip) for a Google-managed cert. Empty → no TLS." + type = list(string) + default = [] +} + +variable "allow_plaintext_lb" { + description = "Opt into HTTP-only LB (trial/dev only)." + type = bool + default = false +} + +variable "cloudsql_deletion_protection" { + description = "Cloud SQL deletion protection (writer + reader)." + type = bool + default = true +} + +variable "gcs_force_destroy" { + description = "Allow destroy of a non-empty GCS bucket (ephemeral/CI only)." + type = bool + default = false +} + +variable "proxy_config" { + description = "LiteLLM proxy config (contents of config.yaml). Empty → defaults." + type = any + default = {} +} + +variable "gateway_extra_env" { + description = "Plain-text env vars layered onto the gateway." + type = map(string) + default = {} +} + +variable "backend_extra_env" { + description = "Plain-text env vars layered onto the backend." + type = map(string) + default = {} +} + +variable "gateway_extra_secrets" { + description = "Gateway env vars sourced from Secret Manager (name → secret resource ID)." + type = map(string) + default = {} +} + +variable "backend_extra_secrets" { + description = "Backend env vars sourced from Secret Manager (name → secret resource ID)." + type = map(string) + default = {} +} diff --git a/terraform/litellm/gcp/examples/default/versions.tf b/terraform/litellm/gcp/examples/default/versions.tf new file mode 100644 index 00000000000..a630c59afd0 --- /dev/null +++ b/terraform/litellm/gcp/examples/default/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 1.6.0" + + required_providers { + google = { + source = "hashicorp/google" + version = "~> 6.10" + } + google-beta = { + source = "hashicorp/google-beta" + version = "~> 6.10" + } + random = { + source = "hashicorp/random" + version = "~> 3.6" + } + } +} diff --git a/terraform/litellm/gcp/gcs.tf b/terraform/litellm/gcp/gcs.tf index 86511d38a31..3ba1f482219 100644 --- a/terraform/litellm/gcp/gcs.tf +++ b/terraform/litellm/gcp/gcs.tf @@ -7,7 +7,7 @@ resource "random_id" "bucket_suffix" { } resource "google_storage_bucket" "this" { - name = "${var.project}-${local.name}-${random_id.bucket_suffix.hex}" + name = "${var.project_id}-${local.name}-${random_id.bucket_suffix.hex}" location = var.region uniform_bucket_level_access = true force_destroy = var.gcs_force_destroy @@ -18,7 +18,7 @@ resource "google_storage_bucket" "this" { public_access_prevention = "enforced" - labels = var.labels + labels = local.labels } # Cloud Run runtime SA gains object admin on this bucket only. @@ -27,3 +27,43 @@ resource "google_storage_bucket_iam_member" "runtime" { role = "roles/storage.objectAdmin" member = "serviceAccount:${google_service_account.runtime.email}" } + +# Dedicated bucket holding only config.yaml. Mounted read-only into the +# gateway and backend via Cloud Run v2's gcsfuse volume. Kept separate from +# the data-plane bucket above so the runtime SA can hold a narrower +# objectViewer binding here (config is read-only at runtime) while keeping +# objectAdmin on the data-plane bucket. Only created when proxy_config is +# non-empty. +resource "google_storage_bucket" "proxy_config" { + count = local.proxy_config_enabled ? 1 : 0 + + name = "${var.project_id}-${local.name}-config-${random_id.bucket_suffix.hex}" + location = var.region + uniform_bucket_level_access = true + force_destroy = var.gcs_force_destroy + + versioning { + enabled = true + } + + public_access_prevention = "enforced" + + labels = local.labels +} + +resource "google_storage_bucket_object" "proxy_config" { + count = local.proxy_config_enabled ? 1 : 0 + + name = local.proxy_config_file_name + bucket = google_storage_bucket.proxy_config[0].name + content = local.proxy_config_yaml + content_type = "application/yaml" +} + +resource "google_storage_bucket_iam_member" "proxy_config_runtime" { + count = local.proxy_config_enabled ? 1 : 0 + + bucket = google_storage_bucket.proxy_config[0].name + role = "roles/storage.objectViewer" + member = "serviceAccount:${google_service_account.runtime.email}" +} diff --git a/terraform/litellm/gcp/iam.tf b/terraform/litellm/gcp/iam.tf index 93a9997ed7a..dc3ae5e0912 100644 --- a/terraform/litellm/gcp/iam.tf +++ b/terraform/litellm/gcp/iam.tf @@ -21,7 +21,7 @@ resource "google_service_account" "ui_runtime" { # Cloud SQL client — lets the Cloud Run services connect to the instance # over private IP via the VPC connector. resource "google_project_iam_member" "runtime_cloudsql" { - project = var.project + project = var.project_id role = "roles/cloudsql.client" member = "serviceAccount:${google_service_account.runtime.email}" } @@ -69,3 +69,13 @@ resource "google_secret_manager_secret_iam_member" "extras" { role = "roles/secretmanager.secretAccessor" member = "serviceAccount:${google_service_account.runtime.email}" } + +# OTEL_HEADERS secret accessor — only created when var.otel_headers_secret +# is set. Carries the OTLP collector's auth header(s). +resource "google_secret_manager_secret_iam_member" "otel_headers" { + count = var.otel_headers_secret == "" ? 0 : 1 + + secret_id = var.otel_headers_secret + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_service_account.runtime.email}" +} diff --git a/terraform/litellm/gcp/load_balancer.tf b/terraform/litellm/gcp/load_balancer.tf index b0081786f13..11f30d0f944 100644 --- a/terraform/litellm/gcp/load_balancer.tf +++ b/terraform/litellm/gcp/load_balancer.tf @@ -14,7 +14,8 @@ locals { } resource "google_compute_global_address" "lb" { - name = "${local.name}-lb-ip" + name = "${local.name}-lb-ip" + labels = local.labels } # Serverless NEGs — one per Cloud Run service. @@ -148,6 +149,7 @@ resource "google_compute_global_forwarding_rule" "http" { load_balancing_scheme = "EXTERNAL_MANAGED" ip_address = google_compute_global_address.lb.address target = google_compute_target_http_proxy.this.id + labels = local.labels } # ---------- HTTPS (gated on var.lb_domains) ---------- @@ -160,11 +162,22 @@ resource "google_compute_global_forwarding_rule" "http" { resource "google_compute_managed_ssl_certificate" "this" { count = local.tls_enabled ? 1 : 0 - name = "${local.name}-cert" + + # A managed cert's `domains` is immutable, so changing var.lb_domains + # forces replacement, and the cert is referenced by the HTTPS target + # proxy — a destroy-then-create replacement fails with + # `resourceInUseByAnotherResource`. Hashing the domains into the name + # makes the name change with the domain set, so create_before_destroy + # builds the new cert + repoints the proxy before deleting the old one. + name = "${local.name}-cert-${substr(sha1(join(",", var.lb_domains)), 0, 8)}" managed { domains = var.lb_domains } + + lifecycle { + create_before_destroy = true + } } resource "google_compute_target_https_proxy" "this" { @@ -182,4 +195,5 @@ resource "google_compute_global_forwarding_rule" "https" { load_balancing_scheme = "EXTERNAL_MANAGED" ip_address = google_compute_global_address.lb.address target = google_compute_target_https_proxy.this[0].id + labels = local.labels } diff --git a/terraform/litellm/gcp/locals.tf b/terraform/litellm/gcp/locals.tf index 2d1231fb197..732b4ce7d6b 100644 --- a/terraform/litellm/gcp/locals.tf +++ b/terraform/litellm/gcp/locals.tf @@ -8,6 +8,19 @@ locals { # the stack can reference local.name. name = "${var.tenant}-litellm-${var.env}" + # Mirrors the AWS stack's local.tags: the module stamps its own + # `litellm-stack` / `managed-by` labels onto every label-supporting + # resource (Cloud Run, Cloud SQL, Memorystore, Secret Manager, GCS) and + # merges var.labels on top. GCP label keys/values are lower-kebab/snake + # only, so the key is `litellm-stack`, not AWS's `litellm:stack`. + labels = merge( + { + "litellm-stack" = local.name + "managed-by" = "terraform" + }, + var.labels, + ) + gateway_path_prefixes = [ "/v1/chat/*", "/chat/*", "/v1/completions*", "/completions*", @@ -62,11 +75,18 @@ locals { ] proxy_config_enabled = length(keys(var.proxy_config)) > 0 - proxy_config_b64 = local.proxy_config_enabled ? base64encode(yamlencode(var.proxy_config)) : "" + proxy_config_yaml = local.proxy_config_enabled ? yamlencode(var.proxy_config) : "" + + proxy_config_mount_path = "/etc/litellm" + proxy_config_file_name = "config.yaml" + proxy_config_volume = "proxy-config" proxy_config_env = local.proxy_config_enabled ? [ - { name = "LITELLM_PROXY_CONFIG_B64", value = local.proxy_config_b64 }, - { name = "CONFIG_FILE_PATH", value = "/tmp/litellm-config.yaml" }, + { name = "CONFIG_FILE_PATH", value = "${local.proxy_config_mount_path}/${local.proxy_config_file_name}" }, + # Forces a new Cloud Run revision when the YAML changes; gcsfuse only + # surfaces the new object on container restart, so without this an + # updated proxy_config would sit in the bucket unread. + { name = "PROXY_CONFIG_HASH", value = md5(local.proxy_config_yaml) }, ] : [] # Resolved image URIs: per-component override wins, otherwise compose diff --git a/terraform/litellm/gcp/outputs.tf b/terraform/litellm/gcp/outputs.tf index df25215adcc..6f1f1d5ccf4 100644 --- a/terraform/litellm/gcp/outputs.tf +++ b/terraform/litellm/gcp/outputs.tf @@ -59,6 +59,6 @@ output "migration_run_command" { "gcloud run jobs execute %s --region %s --project %s --wait", google_cloud_run_v2_job.migrations.name, var.region, - var.project, + var.project_id, ) } diff --git a/terraform/litellm/gcp/providers.tf b/terraform/litellm/gcp/providers.tf deleted file mode 100644 index fd1584463f8..00000000000 --- a/terraform/litellm/gcp/providers.tf +++ /dev/null @@ -1,9 +0,0 @@ -provider "google" { - project = var.project - region = var.region -} - -provider "google-beta" { - project = var.project - region = var.region -} diff --git a/terraform/litellm/gcp/redis.tf b/terraform/litellm/gcp/redis.tf index f7e174ecbae..0e07c416e85 100644 --- a/terraform/litellm/gcp/redis.tf +++ b/terraform/litellm/gcp/redis.tf @@ -9,6 +9,8 @@ resource "google_redis_instance" "this" { redis_version = "REDIS_7_0" + labels = local.labels + # In-transit encryption between Cloud Run and Memorystore. The instance # exposes its self-signed CA via `server_ca_certs` (read in cloudrun.tf # and passed to the proxy as REDIS_CA_PEM_B64); the proxy decodes it to diff --git a/terraform/litellm/gcp/secrets.tf b/terraform/litellm/gcp/secrets.tf index 80312e06a91..f93514bb70b 100644 --- a/terraform/litellm/gcp/secrets.tf +++ b/terraform/litellm/gcp/secrets.tf @@ -10,6 +10,7 @@ resource "random_password" "master_key" { # account gets accessor permission on it (see iam.tf). resource "google_secret_manager_secret" "master_key" { secret_id = "${local.name}-master-key" + labels = local.labels replication { auto {} } @@ -29,6 +30,7 @@ resource "google_secret_manager_secret" "license" { count = var.litellm_license == "" ? 0 : 1 secret_id = "${local.name}-license" + labels = local.labels replication { auto {} } @@ -49,6 +51,7 @@ resource "google_secret_manager_secret" "ui_password" { count = var.ui_password == "" ? 0 : 1 secret_id = "${local.name}-ui-password" + labels = local.labels replication { auto {} } diff --git a/terraform/litellm/gcp/variables.tf b/terraform/litellm/gcp/variables.tf index fe726b0317a..4355192e9f1 100644 --- a/terraform/litellm/gcp/variables.tf +++ b/terraform/litellm/gcp/variables.tf @@ -1,4 +1,4 @@ -variable "project" { +variable "project_id" { description = "GCP project ID." type = string } @@ -30,11 +30,9 @@ variable "env" { } variable "labels" { - description = "Resource labels merged into every label-supporting resource." + description = "Per-deployment labels applied to every label-supporting resource the module creates, on top of the module's own `litellm-stack` / `managed-by` labels. Mirrors the AWS stack's `tags` input." type = map(string) - default = { - "managed-by" = "terraform" - } + default = {} } # ---------- Tenant-supplied secrets ---------- @@ -171,6 +169,17 @@ variable "gateway_memory" { default = "4Gi" } +variable "gateway_num_workers" { + description = "uvicorn worker processes per gateway instance (passed as --workers). Size relative to gateway_cpu — uvicorn recommends ~(2 × vCPU) + 1 for CPU-bound work. Mirrors the AWS stack's gateway_num_workers." + type = number + default = 1 + + validation { + condition = var.gateway_num_workers >= 1 + error_message = "gateway_num_workers must be >= 1." + } +} + # Cloud Run autoscales out of the box (request-rate driven). The min/max # bounds mirror the HPA replica bounds in helm/litellm/values.yaml so each # stack scales over the same range. Cloud Run has no direct CPU-utilization @@ -394,12 +403,90 @@ variable "backend_extra_secrets" { variable "proxy_config" { description = <<-EOT LiteLLM proxy config (contents of config.yaml). Mirrors the helm chart's - `gateway.config.proxy_config`. Passed to gateway, backend, and the - migration job as a base64-encoded env var and decoded to - /tmp/litellm-config.yaml at container start; CONFIG_FILE_PATH is set - automatically. Reference env-injected secrets from the YAML via - `os.environ/`. Leave empty ({}) to skip. + `gateway.config.proxy_config`. YAML-encoded and uploaded to a dedicated + GCS bucket as `config.yaml`, then mounted read-only into the gateway + and backend at `/etc/litellm` via Cloud Run v2's gcsfuse volume; + CONFIG_FILE_PATH is set automatically. A hash of the YAML is wired in + as an env var so a config-only edit forces a new revision (gcsfuse + surfaces the new object on container restart). Reference env-injected + secrets from the YAML via `os.environ/`. Leave empty ({}) to + skip — the bucket isn't created and no volume is mounted. EOT type = any default = {} } + +# ---------- OpenTelemetry v2 ---------- +# +# https://docs.litellm.ai/docs/observability/opentelemetry_v2 +# +# OTel v2 is opt-in and gated entirely on otel_endpoint, matching the AWS +# stack. Leave otel_endpoint = "" and nothing OTel-related is added to the +# container env. Set it and the gateway/backend gain LITELLM_OTEL_V2=true +# plus the OTEL_* block (per-component OTEL_SERVICE_NAME, exporter, endpoint, +# environment name, capture-content), with OTEL_HEADERS sourced from +# otel_headers_secret when provided. + +variable "otel_endpoint" { + description = <<-EOT + OTLP collector URL (e.g. https://otel.example.com:4318 for HTTP, or + your collector's :4317 for gRPC). Empty disables OTel entirely (no + LITELLM_OTEL_V2, no OTEL_* env). When set, LITELLM_OTEL_V2=true plus + OTEL_EXPORTER / OTEL_ENDPOINT are injected and spans ship to the + collector. + EOT + type = string + default = "" +} + +variable "otel_exporter" { + description = <<-EOT + OTel exporter protocol. Ignored when otel_endpoint is empty. `otlp_http` + is the safer default (works through a vanilla L7 ingress); `otlp_grpc` + needs the collector reachable over h2 and the `grpcio` extra installed + in the proxy image. + EOT + type = string + default = "otlp_http" + validation { + condition = contains(["otlp_http", "otlp_grpc", "console"], var.otel_exporter) + error_message = "otel_exporter must be one of: otlp_http, otlp_grpc, console." + } +} + +variable "otel_headers_secret" { + description = <<-EOT + Optional Secret Manager secret resource ID + (`projects//secrets/`) whose latest version is the + value of OTEL_HEADERS — used for collector auth, e.g. + `Authorization=Bearer `. Mounted as an env-var secret_key_ref; + the runtime SA auto-gains roles/secretmanager.secretAccessor. + EOT + type = string + default = "" +} + +variable "otel_environment_name" { + description = <<-EOT + Value for OTEL_ENVIRONMENT_NAME (becomes `deployment.environment` on + every span). Defaults to var.env so spans land tagged with the + deployment env without extra wiring. + EOT + type = string + default = "" +} + +variable "otel_capture_message_content" { + description = <<-EOT + Value for OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT. Default + `no_content` matches the litellm default; flip to `prompt_and_completion` + only when you've audited what's about to land in your observability + backend, because raw prompts/completions are typically sensitive. + EOT + type = string + default = "no_content" + validation { + condition = contains(["no_content", "prompt_and_completion"], var.otel_capture_message_content) + error_message = "otel_capture_message_content must be one of: no_content, prompt_and_completion." + } +} From 1975b9691a570b7264bd77ad993c52b180f4f220 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Sat, 6 Jun 2026 13:17:59 -0700 Subject: [PATCH 04/40] chore: update Next.js build artifacts (2026-06-06 20:08 UTC, node v20.20.2) (#29853) --- litellm/proxy/_experimental/out/404.html | 2 +- .../proxy/_experimental/out/404/index.html | 2 +- .../_experimental/out/__next.__PAGE__.txt | 4 +- .../proxy/_experimental/out/__next._full.txt | 28 +-- .../proxy/_experimental/out/__next._head.txt | 2 +- .../proxy/_experimental/out/__next._index.txt | 10 +- .../proxy/_experimental/out/__next._tree.txt | 4 +- .../_buildManifest.js | 0 .../_clientMiddlewareManifest.json | 0 .../_ssgManifest.js | 0 ...dfb3758f3e2c464.js => 10376d0955336027.js} | 4 +- ...6d1c027ba991a4f.js => 10757c2146f43db4.js} | 4 +- ...1c828abd7c0aff5.js => 193886179a5779b5.js} | 2 +- .../_next/static/chunks/1bcca3c38c9deb02.css | 1 - ...f962cd5264be987.js => 1d7b3500478e93ae.js} | 2 +- ...8c348f8e09a5cb0.js => 2063ca6435a47940.js} | 2 +- ...01e9714324ac586.js => 2442f588ee71a3fd.js} | 2 +- ...f01714cd0d9d1a0.js => 259f1b38a33edf27.js} | 4 +- ...32b4c8e836bd956.js => 25c705f79a0254af.js} | 52 ++--- ...f8d94341111533e.js => 274ab32e0ed6ef59.js} | 2 +- .../_next/static/chunks/29b221aa119c1fd9.js | 1 + .../_next/static/chunks/2c27be032d53887b.js | 179 ++++++++++++++++++ .../_next/static/chunks/31d410af92b166aa.js | 1 - ...54a51b6d79d592c.js => 334af1ce9508e323.js} | 4 +- .../_next/static/chunks/37f229ef9335f8c3.js | 1 - ...9c1f51da7e82268.js => 40a2744137b1aec2.js} | 2 +- ...319f744f39fc02b.js => 422057b6f974d749.js} | 20 +- ...142bad67068834f.js => 4251768481e3628c.js} | 2 +- .../_next/static/chunks/47150bfa067220d3.css | 1 + ...1e80d642a40875d.js => 487ad085992f334d.js} | 2 +- .../_next/static/chunks/4c848b12d4ecda3d.js | 10 - .../_next/static/chunks/4cb93eefa53f21a3.js | 179 ++++++++++++++++++ ...efca6f40ea185cd.js => 59002382e3e0d318.js} | 2 +- ...e42010d48ebfb0e.js => 5924be7dfd4e3180.js} | 2 +- ...739f683b0e3528d.js => 5da00c0630555f00.js} | 2 +- .../_next/static/chunks/5fb4cda7d6ffbeeb.js | 179 ------------------ .../_next/static/chunks/61aa637257592262.js | 1 - ...18293fccad2eeda.js => 694efc3611ad294e.js} | 2 +- .../_next/static/chunks/6a9f0c852bce9817.js | 8 + ...7b443d79fba27b6.js => 6b6f59918488256c.js} | 2 +- ...1a2d4575f32b1b4.js => 6f180247948a105b.js} | 2 +- .../_next/static/chunks/75ee9aba04c74e23.js | 10 + .../_next/static/chunks/76b36d8bf27be7b6.js | 1 + .../_next/static/chunks/7a18eb3510b77ce5.js | 1 - ...46c407f7cf4f087.js => 7a8b5e4509475f1b.js} | 2 +- ...fcdf77549c2acb3.js => 7b668a9caf81638c.js} | 4 +- ...ac09b227f50edb4.js => 80f4410629229bf9.js} | 10 +- ...bb663ff806dc32f.js => 81937424fe90f746.js} | 2 +- .../_next/static/chunks/842675b40384437a.js | 8 - ...028f8c28935d281.js => 84a27349dda457cd.js} | 2 +- ...1e93208df46e501.js => 881da92c0aeda03f.js} | 2 +- .../_next/static/chunks/8ddf82e7e0b331fc.js | 1 + .../_next/static/chunks/904981257ceab1f1.js | 179 ------------------ .../_next/static/chunks/90cb6cf32d80a498.js | 1 + ...080ed46e3fb9c07.js => 978a3219a22261f4.js} | 2 +- .../_next/static/chunks/99a78e4dc2223146.js | 1 - ...137ec3c4d835313.js => 9e8f9cc5fd406040.js} | 2 +- .../_next/static/chunks/afa8789677796146.js | 179 ++++++++++++++++++ ...56db9b7eab732b3.js => b47b846925c67711.js} | 2 +- ...5d66b48c48272a4.js => b80871109e9f0047.js} | 2 +- ...c60480dee131419.js => be0fdd72cd27ab4e.js} | 2 +- ...2c4ecc57f72065e.js => bee4095c26818f05.js} | 2 +- ...eeb6544ccf6dff0.js => c14973c0b8a84588.js} | 6 +- ...654f2b1a1d8dec8.js => cd07baa6a7669b93.js} | 2 +- ...303973560527556.js => ce25ece877f8d603.js} | 6 +- ...16f68c890479681.js => d1486de50c4b1eb6.js} | 2 +- ...ff0ab94e133dc3e.js => d1aa35e8d9888fa4.js} | 2 +- .../_next/static/chunks/d734cb3d5659b0da.js | 179 ------------------ ...78832edb30e99a4.js => d7c18aec4a87a237.js} | 2 +- ...f1486622270556b.js => dac86522fa98e760.js} | 8 +- ...4c8f753302918e6.js => dd195df4747f737d.js} | 2 +- ...e8f89c672745c59.js => e2257d8308d35cf4.js} | 2 +- ...c51c487ba59a24a.js => e26b06a3f997aa29.js} | 2 +- ...710770c6333a72f.js => eb1ba04e211a533f.js} | 2 +- ...ff572e986920e2e.js => ef83159ac8b6cc18.js} | 2 +- .../_next/static/chunks/f0e079183e7bb90c.js | 1 + ...c3e8651e0e97232.js => f85083fd17530154.js} | 2 +- ...3c3db1352241a8b.js => ffa46de7b8384155.js} | 112 +++++------ .../out/_not-found/__next._full.txt | 12 +- .../out/_not-found/__next._head.txt | 2 +- .../out/_not-found/__next._index.txt | 10 +- .../_not-found/__next._not-found.__PAGE__.txt | 2 +- .../out/_not-found/__next._not-found.txt | 2 +- .../out/_not-found/__next._tree.txt | 4 +- .../_experimental/out/_not-found/index.html | 2 +- .../_experimental/out/_not-found/index.txt | 12 +- ...KGRhc2hib2FyZCk.api-reference.__PAGE__.txt | 4 +- .../__next.!KGRhc2hib2FyZCk.api-reference.txt | 2 +- .../api-reference/__next.!KGRhc2hib2FyZCk.txt | 4 +- .../out/api-reference/__next._full.txt | 16 +- .../out/api-reference/__next._head.txt | 2 +- .../out/api-reference/__next._index.txt | 10 +- .../out/api-reference/__next._tree.txt | 4 +- .../out/api-reference/index.html | 2 +- .../_experimental/out/api-reference/index.txt | 16 +- .../out/assets/logos/cato_networks.svg | 4 + .../out/assets/logos/langflow.svg | 5 + .../_experimental/out/assets/logos/soniox.svg | 1 + .../_experimental/out/chat/__next._full.txt | 14 +- .../_experimental/out/chat/__next._head.txt | 2 +- .../_experimental/out/chat/__next._index.txt | 10 +- .../_experimental/out/chat/__next._tree.txt | 4 +- .../out/chat/__next.chat.__PAGE__.txt | 4 +- .../_experimental/out/chat/__next.chat.txt | 2 +- .../proxy/_experimental/out/chat/index.html | 2 +- .../proxy/_experimental/out/chat/index.txt | 14 +- litellm/proxy/_experimental/out/index.html | 2 +- litellm/proxy/_experimental/out/index.txt | 28 +-- .../_experimental/out/login/__next._full.txt | 14 +- .../_experimental/out/login/__next._head.txt | 2 +- .../_experimental/out/login/__next._index.txt | 10 +- .../_experimental/out/login/__next._tree.txt | 4 +- .../out/login/__next.login.__PAGE__.txt | 4 +- .../_experimental/out/login/__next.login.txt | 2 +- .../proxy/_experimental/out/login/index.html | 2 +- .../proxy/_experimental/out/login/index.txt | 14 +- .../out/mcp/oauth/callback/__next._full.txt | 14 +- .../out/mcp/oauth/callback/__next._head.txt | 2 +- .../out/mcp/oauth/callback/__next._index.txt | 10 +- .../out/mcp/oauth/callback/__next._tree.txt | 4 +- .../__next.mcp.oauth.callback.__PAGE__.txt | 4 +- .../callback/__next.mcp.oauth.callback.txt | 2 +- .../mcp/oauth/callback/__next.mcp.oauth.txt | 2 +- .../out/mcp/oauth/callback/__next.mcp.txt | 2 +- .../out/mcp/oauth/callback/index.html | 2 +- .../out/mcp/oauth/callback/index.txt | 14 +- .../out/model_hub/__next._full.txt | 14 +- .../out/model_hub/__next._head.txt | 2 +- .../out/model_hub/__next._index.txt | 10 +- .../out/model_hub/__next._tree.txt | 4 +- .../model_hub/__next.model_hub.__PAGE__.txt | 4 +- .../out/model_hub/__next.model_hub.txt | 2 +- .../_experimental/out/model_hub/index.html | 2 +- .../_experimental/out/model_hub/index.txt | 14 +- .../out/model_hub_table/__next._full.txt | 18 +- .../out/model_hub_table/__next._head.txt | 2 +- .../out/model_hub_table/__next._index.txt | 10 +- .../out/model_hub_table/__next._tree.txt | 4 +- .../__next.model_hub_table.__PAGE__.txt | 4 +- .../__next.model_hub_table.txt | 2 +- .../out/model_hub_table/index.html | 2 +- .../out/model_hub_table/index.txt | 18 +- ...ib2FyZCk.models-and-endpoints.__PAGE__.txt | 4 +- ....!KGRhc2hib2FyZCk.models-and-endpoints.txt | 2 +- .../__next.!KGRhc2hib2FyZCk.txt | 4 +- .../out/models-and-endpoints/__next._full.txt | 18 +- .../out/models-and-endpoints/__next._head.txt | 2 +- .../models-and-endpoints/__next._index.txt | 10 +- .../out/models-and-endpoints/__next._tree.txt | 4 +- .../out/models-and-endpoints/index.html | 2 +- .../out/models-and-endpoints/index.txt | 18 +- .../out/onboarding/__next._full.txt | 14 +- .../out/onboarding/__next._head.txt | 2 +- .../out/onboarding/__next._index.txt | 10 +- .../out/onboarding/__next._tree.txt | 4 +- .../onboarding/__next.onboarding.__PAGE__.txt | 4 +- .../out/onboarding/__next.onboarding.txt | 2 +- .../_experimental/out/onboarding/index.html | 2 +- .../_experimental/out/onboarding/index.txt | 14 +- ...KGRhc2hib2FyZCk.organizations.__PAGE__.txt | 4 +- .../__next.!KGRhc2hib2FyZCk.organizations.txt | 2 +- .../organizations/__next.!KGRhc2hib2FyZCk.txt | 4 +- .../out/organizations/__next._full.txt | 24 +-- .../out/organizations/__next._head.txt | 2 +- .../out/organizations/__next._index.txt | 10 +- .../out/organizations/__next._tree.txt | 4 +- .../out/organizations/index.html | 2 +- .../_experimental/out/organizations/index.txt | 24 +-- ...t.!KGRhc2hib2FyZCk.playground.__PAGE__.txt | 4 +- .../__next.!KGRhc2hib2FyZCk.playground.txt | 2 +- .../playground/__next.!KGRhc2hib2FyZCk.txt | 4 +- .../out/playground/__next._full.txt | 24 +-- .../out/playground/__next._head.txt | 2 +- .../out/playground/__next._index.txt | 10 +- .../out/playground/__next._tree.txt | 4 +- .../_experimental/out/playground/index.html | 2 +- .../_experimental/out/playground/index.txt | 24 +-- .../virtual-keys/__next.!KGRhc2hib2FyZCk.txt | 4 +- ...!KGRhc2hib2FyZCk.virtual-keys.__PAGE__.txt | 4 +- .../__next.!KGRhc2hib2FyZCk.virtual-keys.txt | 2 +- .../out/virtual-keys/__next._full.txt | 28 +-- .../out/virtual-keys/__next._head.txt | 2 +- .../out/virtual-keys/__next._index.txt | 10 +- .../out/virtual-keys/__next._tree.txt | 4 +- .../_experimental/out/virtual-keys/index.html | 2 +- .../_experimental/out/virtual-keys/index.txt | 28 +-- 186 files changed, 1129 insertions(+), 1117 deletions(-) rename litellm/proxy/_experimental/out/_next/static/{TrcGiQpTupSbDYFFfkFHY => LpqGBJeKQM0vUG-9uVaiY}/_buildManifest.js (100%) rename litellm/proxy/_experimental/out/_next/static/{TrcGiQpTupSbDYFFfkFHY => LpqGBJeKQM0vUG-9uVaiY}/_clientMiddlewareManifest.json (100%) rename litellm/proxy/_experimental/out/_next/static/{TrcGiQpTupSbDYFFfkFHY => LpqGBJeKQM0vUG-9uVaiY}/_ssgManifest.js (100%) rename litellm/proxy/_experimental/out/_next/static/chunks/{adfb3758f3e2c464.js => 10376d0955336027.js} (98%) rename litellm/proxy/_experimental/out/_next/static/chunks/{36d1c027ba991a4f.js => 10757c2146f43db4.js} (66%) rename litellm/proxy/_experimental/out/_next/static/chunks/{91c828abd7c0aff5.js => 193886179a5779b5.js} (99%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1bcca3c38c9deb02.css rename litellm/proxy/_experimental/out/_next/static/chunks/{bf962cd5264be987.js => 1d7b3500478e93ae.js} (58%) rename litellm/proxy/_experimental/out/_next/static/chunks/{08c348f8e09a5cb0.js => 2063ca6435a47940.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{701e9714324ac586.js => 2442f588ee71a3fd.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{6f01714cd0d9d1a0.js => 259f1b38a33edf27.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{632b4c8e836bd956.js => 25c705f79a0254af.js} (60%) rename litellm/proxy/_experimental/out/_next/static/chunks/{0f8d94341111533e.js => 274ab32e0ed6ef59.js} (99%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/29b221aa119c1fd9.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2c27be032d53887b.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/31d410af92b166aa.js rename litellm/proxy/_experimental/out/_next/static/chunks/{554a51b6d79d592c.js => 334af1ce9508e323.js} (74%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/37f229ef9335f8c3.js rename litellm/proxy/_experimental/out/_next/static/chunks/{09c1f51da7e82268.js => 40a2744137b1aec2.js} (80%) rename litellm/proxy/_experimental/out/_next/static/chunks/{2319f744f39fc02b.js => 422057b6f974d749.js} (72%) rename litellm/proxy/_experimental/out/_next/static/chunks/{2142bad67068834f.js => 4251768481e3628c.js} (99%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/47150bfa067220d3.css rename litellm/proxy/_experimental/out/_next/static/chunks/{a1e80d642a40875d.js => 487ad085992f334d.js} (99%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4c848b12d4ecda3d.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4cb93eefa53f21a3.js rename litellm/proxy/_experimental/out/_next/static/chunks/{aefca6f40ea185cd.js => 59002382e3e0d318.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{3e42010d48ebfb0e.js => 5924be7dfd4e3180.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{f739f683b0e3528d.js => 5da00c0630555f00.js} (99%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5fb4cda7d6ffbeeb.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/61aa637257592262.js rename litellm/proxy/_experimental/out/_next/static/chunks/{018293fccad2eeda.js => 694efc3611ad294e.js} (76%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6a9f0c852bce9817.js rename litellm/proxy/_experimental/out/_next/static/chunks/{07b443d79fba27b6.js => 6b6f59918488256c.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{01a2d4575f32b1b4.js => 6f180247948a105b.js} (99%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/75ee9aba04c74e23.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/76b36d8bf27be7b6.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7a18eb3510b77ce5.js rename litellm/proxy/_experimental/out/_next/static/chunks/{946c407f7cf4f087.js => 7a8b5e4509475f1b.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{7fcdf77549c2acb3.js => 7b668a9caf81638c.js} (86%) rename litellm/proxy/_experimental/out/_next/static/chunks/{0ac09b227f50edb4.js => 80f4410629229bf9.js} (85%) rename litellm/proxy/_experimental/out/_next/static/chunks/{4bb663ff806dc32f.js => 81937424fe90f746.js} (99%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/842675b40384437a.js rename litellm/proxy/_experimental/out/_next/static/chunks/{d028f8c28935d281.js => 84a27349dda457cd.js} (98%) rename litellm/proxy/_experimental/out/_next/static/chunks/{31e93208df46e501.js => 881da92c0aeda03f.js} (99%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8ddf82e7e0b331fc.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/904981257ceab1f1.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/90cb6cf32d80a498.js rename litellm/proxy/_experimental/out/_next/static/chunks/{c080ed46e3fb9c07.js => 978a3219a22261f4.js} (99%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/99a78e4dc2223146.js rename litellm/proxy/_experimental/out/_next/static/chunks/{8137ec3c4d835313.js => 9e8f9cc5fd406040.js} (99%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/afa8789677796146.js rename litellm/proxy/_experimental/out/_next/static/chunks/{556db9b7eab732b3.js => b47b846925c67711.js} (98%) rename litellm/proxy/_experimental/out/_next/static/chunks/{a5d66b48c48272a4.js => b80871109e9f0047.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{ac60480dee131419.js => be0fdd72cd27ab4e.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{52c4ecc57f72065e.js => bee4095c26818f05.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{aeeb6544ccf6dff0.js => c14973c0b8a84588.js} (57%) rename litellm/proxy/_experimental/out/_next/static/chunks/{f654f2b1a1d8dec8.js => cd07baa6a7669b93.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{6303973560527556.js => ce25ece877f8d603.js} (58%) rename litellm/proxy/_experimental/out/_next/static/chunks/{716f68c890479681.js => d1486de50c4b1eb6.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{cff0ab94e133dc3e.js => d1aa35e8d9888fa4.js} (99%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/d734cb3d5659b0da.js rename litellm/proxy/_experimental/out/_next/static/chunks/{878832edb30e99a4.js => d7c18aec4a87a237.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{9f1486622270556b.js => dac86522fa98e760.js} (95%) rename litellm/proxy/_experimental/out/_next/static/chunks/{94c8f753302918e6.js => dd195df4747f737d.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{ee8f89c672745c59.js => e2257d8308d35cf4.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{cc51c487ba59a24a.js => e26b06a3f997aa29.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{9710770c6333a72f.js => eb1ba04e211a533f.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{dff572e986920e2e.js => ef83159ac8b6cc18.js} (99%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/f0e079183e7bb90c.js rename litellm/proxy/_experimental/out/_next/static/chunks/{0c3e8651e0e97232.js => f85083fd17530154.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{43c3db1352241a8b.js => ffa46de7b8384155.js} (50%) create mode 100644 litellm/proxy/_experimental/out/assets/logos/cato_networks.svg create mode 100644 litellm/proxy/_experimental/out/assets/logos/langflow.svg create mode 100644 litellm/proxy/_experimental/out/assets/logos/soniox.svg diff --git a/litellm/proxy/_experimental/out/404.html b/litellm/proxy/_experimental/out/404.html index f27612ff54e..45de348c4d5 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 f27612ff54e..45de348c4d5 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.__PAGE__.txt b/litellm/proxy/_experimental/out/__next.__PAGE__.txt index c024136e8dc..095c8f4339f 100644 --- a/litellm/proxy/_experimental/out/__next.__PAGE__.txt +++ b/litellm/proxy/_experimental/out/__next.__PAGE__.txt @@ -1,10 +1,10 @@ 1:"$Sreact.fragment" 2:I[347257,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"ClientPageRoot"] -3:I[952683,["/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js","/litellm-asset-prefix/_next/static/chunks/52c4ecc57f72065e.js","/litellm-asset-prefix/_next/static/chunks/4bb663ff806dc32f.js","/litellm-asset-prefix/_next/static/chunks/ee8f89c672745c59.js","/litellm-asset-prefix/_next/static/chunks/2954392b7a60a6a1.js","/litellm-asset-prefix/_next/static/chunks/04711b0f8ffa7bbd.js","/litellm-asset-prefix/_next/static/chunks/9710770c6333a72f.js","/litellm-asset-prefix/_next/static/chunks/88c74f8b4b20d25a.js","/litellm-asset-prefix/_next/static/chunks/d734cb3d5659b0da.js","/litellm-asset-prefix/_next/static/chunks/a1ef280b7ad5ae6a.js","/litellm-asset-prefix/_next/static/chunks/09c1f51da7e82268.js","/litellm-asset-prefix/_next/static/chunks/542a1a209eb732c6.js","/litellm-asset-prefix/_next/static/chunks/d028f8c28935d281.js","/litellm-asset-prefix/_next/static/chunks/61aa637257592262.js","/litellm-asset-prefix/_next/static/chunks/bf962cd5264be987.js","/litellm-asset-prefix/_next/static/chunks/37f229ef9335f8c3.js","/litellm-asset-prefix/_next/static/chunks/36d1c027ba991a4f.js","/litellm-asset-prefix/_next/static/chunks/786e88f4abdd5c58.js","/litellm-asset-prefix/_next/static/chunks/43c3db1352241a8b.js","/litellm-asset-prefix/_next/static/chunks/31275eb5c6f6332f.js","/litellm-asset-prefix/_next/static/chunks/0ac09b227f50edb4.js","/litellm-asset-prefix/_next/static/chunks/4c848b12d4ecda3d.js","/litellm-asset-prefix/_next/static/chunks/91c828abd7c0aff5.js","/litellm-asset-prefix/_next/static/chunks/08c348f8e09a5cb0.js","/litellm-asset-prefix/_next/static/chunks/7e417dd24c8becd0.js","/litellm-asset-prefix/_next/static/chunks/b323e0ef008e6348.js","/litellm-asset-prefix/_next/static/chunks/4ac3235460262f36.js","/litellm-asset-prefix/_next/static/chunks/51494a4a4b6fc437.js","/litellm-asset-prefix/_next/static/chunks/878832edb30e99a4.js","/litellm-asset-prefix/_next/static/chunks/9f1486622270556b.js"],"default"] +3:I[952683,["/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js","/litellm-asset-prefix/_next/static/chunks/bee4095c26818f05.js","/litellm-asset-prefix/_next/static/chunks/81937424fe90f746.js","/litellm-asset-prefix/_next/static/chunks/e2257d8308d35cf4.js","/litellm-asset-prefix/_next/static/chunks/2954392b7a60a6a1.js","/litellm-asset-prefix/_next/static/chunks/04711b0f8ffa7bbd.js","/litellm-asset-prefix/_next/static/chunks/eb1ba04e211a533f.js","/litellm-asset-prefix/_next/static/chunks/88c74f8b4b20d25a.js","/litellm-asset-prefix/_next/static/chunks/4cb93eefa53f21a3.js","/litellm-asset-prefix/_next/static/chunks/a1ef280b7ad5ae6a.js","/litellm-asset-prefix/_next/static/chunks/40a2744137b1aec2.js","/litellm-asset-prefix/_next/static/chunks/542a1a209eb732c6.js","/litellm-asset-prefix/_next/static/chunks/84a27349dda457cd.js","/litellm-asset-prefix/_next/static/chunks/8ddf82e7e0b331fc.js","/litellm-asset-prefix/_next/static/chunks/1d7b3500478e93ae.js","/litellm-asset-prefix/_next/static/chunks/f0e079183e7bb90c.js","/litellm-asset-prefix/_next/static/chunks/10757c2146f43db4.js","/litellm-asset-prefix/_next/static/chunks/786e88f4abdd5c58.js","/litellm-asset-prefix/_next/static/chunks/ffa46de7b8384155.js","/litellm-asset-prefix/_next/static/chunks/31275eb5c6f6332f.js","/litellm-asset-prefix/_next/static/chunks/80f4410629229bf9.js","/litellm-asset-prefix/_next/static/chunks/75ee9aba04c74e23.js","/litellm-asset-prefix/_next/static/chunks/193886179a5779b5.js","/litellm-asset-prefix/_next/static/chunks/2063ca6435a47940.js","/litellm-asset-prefix/_next/static/chunks/7e417dd24c8becd0.js","/litellm-asset-prefix/_next/static/chunks/b323e0ef008e6348.js","/litellm-asset-prefix/_next/static/chunks/4ac3235460262f36.js","/litellm-asset-prefix/_next/static/chunks/51494a4a4b6fc437.js","/litellm-asset-prefix/_next/static/chunks/d7c18aec4a87a237.js","/litellm-asset-prefix/_next/static/chunks/dac86522fa98e760.js"],"default"] 6:I[897367,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"OutletBoundary"] 7:"$Sreact.suspense" :HL["/litellm-asset-prefix/_next/static/chunks/3f3fa56b5786d58c.css","style"] -0:{"buildId":"TrcGiQpTupSbDYFFfkFHY","rsc":["$","$1","c",{"children":[["$","$L2",null,{"Component":"$3","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@4","$@5"]}}],[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/3f3fa56b5786d58c.css","precedence":"next"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/52c4ecc57f72065e.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/4bb663ff806dc32f.js","async":true}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/ee8f89c672745c59.js","async":true}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/2954392b7a60a6a1.js","async":true}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/04711b0f8ffa7bbd.js","async":true}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/9710770c6333a72f.js","async":true}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/88c74f8b4b20d25a.js","async":true}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/d734cb3d5659b0da.js","async":true}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/a1ef280b7ad5ae6a.js","async":true}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/09c1f51da7e82268.js","async":true}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/542a1a209eb732c6.js","async":true}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/d028f8c28935d281.js","async":true}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/61aa637257592262.js","async":true}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/bf962cd5264be987.js","async":true}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/37f229ef9335f8c3.js","async":true}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/36d1c027ba991a4f.js","async":true}],["$","script","script-16",{"src":"/litellm-asset-prefix/_next/static/chunks/786e88f4abdd5c58.js","async":true}],["$","script","script-17",{"src":"/litellm-asset-prefix/_next/static/chunks/43c3db1352241a8b.js","async":true}],["$","script","script-18",{"src":"/litellm-asset-prefix/_next/static/chunks/31275eb5c6f6332f.js","async":true}],["$","script","script-19",{"src":"/litellm-asset-prefix/_next/static/chunks/0ac09b227f50edb4.js","async":true}],["$","script","script-20",{"src":"/litellm-asset-prefix/_next/static/chunks/4c848b12d4ecda3d.js","async":true}],["$","script","script-21",{"src":"/litellm-asset-prefix/_next/static/chunks/91c828abd7c0aff5.js","async":true}],["$","script","script-22",{"src":"/litellm-asset-prefix/_next/static/chunks/08c348f8e09a5cb0.js","async":true}],["$","script","script-23",{"src":"/litellm-asset-prefix/_next/static/chunks/7e417dd24c8becd0.js","async":true}],["$","script","script-24",{"src":"/litellm-asset-prefix/_next/static/chunks/b323e0ef008e6348.js","async":true}],["$","script","script-25",{"src":"/litellm-asset-prefix/_next/static/chunks/4ac3235460262f36.js","async":true}],["$","script","script-26",{"src":"/litellm-asset-prefix/_next/static/chunks/51494a4a4b6fc437.js","async":true}],["$","script","script-27",{"src":"/litellm-asset-prefix/_next/static/chunks/878832edb30e99a4.js","async":true}],["$","script","script-28",{"src":"/litellm-asset-prefix/_next/static/chunks/9f1486622270556b.js","async":true}]],["$","$L6",null,{"children":["$","$7",null,{"name":"Next.MetadataOutlet","children":"$@8"}]}]]}],"loading":null,"isPartial":false} +0:{"buildId":"LpqGBJeKQM0vUG-9uVaiY","rsc":["$","$1","c",{"children":[["$","$L2",null,{"Component":"$3","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@4","$@5"]}}],[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/3f3fa56b5786d58c.css","precedence":"next"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/bee4095c26818f05.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/81937424fe90f746.js","async":true}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/e2257d8308d35cf4.js","async":true}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/2954392b7a60a6a1.js","async":true}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/04711b0f8ffa7bbd.js","async":true}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/eb1ba04e211a533f.js","async":true}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/88c74f8b4b20d25a.js","async":true}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/4cb93eefa53f21a3.js","async":true}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/a1ef280b7ad5ae6a.js","async":true}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/40a2744137b1aec2.js","async":true}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/542a1a209eb732c6.js","async":true}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/84a27349dda457cd.js","async":true}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/8ddf82e7e0b331fc.js","async":true}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/1d7b3500478e93ae.js","async":true}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/f0e079183e7bb90c.js","async":true}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/10757c2146f43db4.js","async":true}],["$","script","script-16",{"src":"/litellm-asset-prefix/_next/static/chunks/786e88f4abdd5c58.js","async":true}],["$","script","script-17",{"src":"/litellm-asset-prefix/_next/static/chunks/ffa46de7b8384155.js","async":true}],["$","script","script-18",{"src":"/litellm-asset-prefix/_next/static/chunks/31275eb5c6f6332f.js","async":true}],["$","script","script-19",{"src":"/litellm-asset-prefix/_next/static/chunks/80f4410629229bf9.js","async":true}],["$","script","script-20",{"src":"/litellm-asset-prefix/_next/static/chunks/75ee9aba04c74e23.js","async":true}],["$","script","script-21",{"src":"/litellm-asset-prefix/_next/static/chunks/193886179a5779b5.js","async":true}],["$","script","script-22",{"src":"/litellm-asset-prefix/_next/static/chunks/2063ca6435a47940.js","async":true}],["$","script","script-23",{"src":"/litellm-asset-prefix/_next/static/chunks/7e417dd24c8becd0.js","async":true}],["$","script","script-24",{"src":"/litellm-asset-prefix/_next/static/chunks/b323e0ef008e6348.js","async":true}],["$","script","script-25",{"src":"/litellm-asset-prefix/_next/static/chunks/4ac3235460262f36.js","async":true}],["$","script","script-26",{"src":"/litellm-asset-prefix/_next/static/chunks/51494a4a4b6fc437.js","async":true}],["$","script","script-27",{"src":"/litellm-asset-prefix/_next/static/chunks/d7c18aec4a87a237.js","async":true}],["$","script","script-28",{"src":"/litellm-asset-prefix/_next/static/chunks/dac86522fa98e760.js","async":true}]],["$","$L6",null,{"children":["$","$7",null,{"name":"Next.MetadataOutlet","children":"$@8"}]}]]}],"loading":null,"isPartial":false} 4:{} 5:"$0:rsc:props:children:0:props:serverProvidedParams:params" 8:null diff --git a/litellm/proxy/_experimental/out/__next._full.txt b/litellm/proxy/_experimental/out/__next._full.txt index 0c119086b9e..2b2b3850207 100644 --- a/litellm/proxy/_experimental/out/__next._full.txt +++ b/litellm/proxy/_experimental/out/__next._full.txt @@ -1,34 +1,34 @@ 1:"$Sreact.fragment" -2:I[867271,["/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js"],"default"] -3:I[71195,["/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js"],"default"] -4:I[557951,["/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js"],"AuthProvider"] +2:I[867271,["/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js"],"default"] +3:I[71195,["/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js"],"default"] +4:I[557951,["/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js"],"AuthProvider"] 5:I[339756,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"default"] 6:I[837457,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"default"] 7:I[347257,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"ClientPageRoot"] -8:I[952683,["/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js","/litellm-asset-prefix/_next/static/chunks/52c4ecc57f72065e.js","/litellm-asset-prefix/_next/static/chunks/4bb663ff806dc32f.js","/litellm-asset-prefix/_next/static/chunks/ee8f89c672745c59.js","/litellm-asset-prefix/_next/static/chunks/2954392b7a60a6a1.js","/litellm-asset-prefix/_next/static/chunks/04711b0f8ffa7bbd.js","/litellm-asset-prefix/_next/static/chunks/9710770c6333a72f.js","/litellm-asset-prefix/_next/static/chunks/88c74f8b4b20d25a.js","/litellm-asset-prefix/_next/static/chunks/d734cb3d5659b0da.js","/litellm-asset-prefix/_next/static/chunks/a1ef280b7ad5ae6a.js","/litellm-asset-prefix/_next/static/chunks/09c1f51da7e82268.js","/litellm-asset-prefix/_next/static/chunks/542a1a209eb732c6.js","/litellm-asset-prefix/_next/static/chunks/d028f8c28935d281.js","/litellm-asset-prefix/_next/static/chunks/61aa637257592262.js","/litellm-asset-prefix/_next/static/chunks/bf962cd5264be987.js","/litellm-asset-prefix/_next/static/chunks/37f229ef9335f8c3.js","/litellm-asset-prefix/_next/static/chunks/36d1c027ba991a4f.js","/litellm-asset-prefix/_next/static/chunks/786e88f4abdd5c58.js","/litellm-asset-prefix/_next/static/chunks/43c3db1352241a8b.js","/litellm-asset-prefix/_next/static/chunks/31275eb5c6f6332f.js","/litellm-asset-prefix/_next/static/chunks/0ac09b227f50edb4.js","/litellm-asset-prefix/_next/static/chunks/4c848b12d4ecda3d.js","/litellm-asset-prefix/_next/static/chunks/91c828abd7c0aff5.js","/litellm-asset-prefix/_next/static/chunks/08c348f8e09a5cb0.js","/litellm-asset-prefix/_next/static/chunks/7e417dd24c8becd0.js","/litellm-asset-prefix/_next/static/chunks/b323e0ef008e6348.js","/litellm-asset-prefix/_next/static/chunks/4ac3235460262f36.js","/litellm-asset-prefix/_next/static/chunks/51494a4a4b6fc437.js","/litellm-asset-prefix/_next/static/chunks/878832edb30e99a4.js","/litellm-asset-prefix/_next/static/chunks/9f1486622270556b.js"],"default"] +8:I[952683,["/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js","/litellm-asset-prefix/_next/static/chunks/bee4095c26818f05.js","/litellm-asset-prefix/_next/static/chunks/81937424fe90f746.js","/litellm-asset-prefix/_next/static/chunks/e2257d8308d35cf4.js","/litellm-asset-prefix/_next/static/chunks/2954392b7a60a6a1.js","/litellm-asset-prefix/_next/static/chunks/04711b0f8ffa7bbd.js","/litellm-asset-prefix/_next/static/chunks/eb1ba04e211a533f.js","/litellm-asset-prefix/_next/static/chunks/88c74f8b4b20d25a.js","/litellm-asset-prefix/_next/static/chunks/4cb93eefa53f21a3.js","/litellm-asset-prefix/_next/static/chunks/a1ef280b7ad5ae6a.js","/litellm-asset-prefix/_next/static/chunks/40a2744137b1aec2.js","/litellm-asset-prefix/_next/static/chunks/542a1a209eb732c6.js","/litellm-asset-prefix/_next/static/chunks/84a27349dda457cd.js","/litellm-asset-prefix/_next/static/chunks/8ddf82e7e0b331fc.js","/litellm-asset-prefix/_next/static/chunks/1d7b3500478e93ae.js","/litellm-asset-prefix/_next/static/chunks/f0e079183e7bb90c.js","/litellm-asset-prefix/_next/static/chunks/10757c2146f43db4.js","/litellm-asset-prefix/_next/static/chunks/786e88f4abdd5c58.js","/litellm-asset-prefix/_next/static/chunks/ffa46de7b8384155.js","/litellm-asset-prefix/_next/static/chunks/31275eb5c6f6332f.js","/litellm-asset-prefix/_next/static/chunks/80f4410629229bf9.js","/litellm-asset-prefix/_next/static/chunks/75ee9aba04c74e23.js","/litellm-asset-prefix/_next/static/chunks/193886179a5779b5.js","/litellm-asset-prefix/_next/static/chunks/2063ca6435a47940.js","/litellm-asset-prefix/_next/static/chunks/7e417dd24c8becd0.js","/litellm-asset-prefix/_next/static/chunks/b323e0ef008e6348.js","/litellm-asset-prefix/_next/static/chunks/4ac3235460262f36.js","/litellm-asset-prefix/_next/static/chunks/51494a4a4b6fc437.js","/litellm-asset-prefix/_next/static/chunks/d7c18aec4a87a237.js","/litellm-asset-prefix/_next/static/chunks/dac86522fa98e760.js"],"default"] 1a:I[168027,[],"default"] :HL["/litellm-asset-prefix/_next/static/chunks/4e20891f2fd03463.css","style"] -:HL["/litellm-asset-prefix/_next/static/chunks/1bcca3c38c9deb02.css","style"] +:HL["/litellm-asset-prefix/_next/static/chunks/47150bfa067220d3.css","style"] :HL["/litellm-asset-prefix/_next/static/media/83afe278b6a6bb3c-s.p.3a6ba036.woff2","font",{"crossOrigin":"","type":"font/woff2"}] :HL["/litellm-asset-prefix/_next/static/chunks/3f3fa56b5786d58c.css","style"] -0:{"P":null,"b":"TrcGiQpTupSbDYFFfkFHY","c":["",""],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/4e20891f2fd03463.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/1bcca3c38c9deb02.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.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":[["$","$L7",null,{"Component":"$8","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@9","$@a"]}}],[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/3f3fa56b5786d58c.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/52c4ecc57f72065e.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/4bb663ff806dc32f.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/ee8f89c672745c59.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/2954392b7a60a6a1.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/04711b0f8ffa7bbd.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/9710770c6333a72f.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/88c74f8b4b20d25a.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/d734cb3d5659b0da.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/a1ef280b7ad5ae6a.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/09c1f51da7e82268.js","async":true,"nonce":"$undefined"}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/542a1a209eb732c6.js","async":true,"nonce":"$undefined"}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/d028f8c28935d281.js","async":true,"nonce":"$undefined"}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/61aa637257592262.js","async":true,"nonce":"$undefined"}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/bf962cd5264be987.js","async":true,"nonce":"$undefined"}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/37f229ef9335f8c3.js","async":true,"nonce":"$undefined"}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/36d1c027ba991a4f.js","async":true,"nonce":"$undefined"}],"$Lb","$Lc","$Ld","$Le","$Lf","$L10","$L11","$L12","$L13","$L14","$L15","$L16","$L17"],"$L18"]}],{},null,false,false]},null,false,false],"$L19",false]],"m":"$undefined","G":["$1a",[]],"S":true} +0:{"P":null,"b":"LpqGBJeKQM0vUG-9uVaiY","c":["",""],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/4e20891f2fd03463.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/47150bfa067220d3.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.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":[["$","$L7",null,{"Component":"$8","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@9","$@a"]}}],[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/3f3fa56b5786d58c.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/bee4095c26818f05.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/81937424fe90f746.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/e2257d8308d35cf4.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/2954392b7a60a6a1.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/04711b0f8ffa7bbd.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/eb1ba04e211a533f.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/88c74f8b4b20d25a.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/4cb93eefa53f21a3.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/a1ef280b7ad5ae6a.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/40a2744137b1aec2.js","async":true,"nonce":"$undefined"}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/542a1a209eb732c6.js","async":true,"nonce":"$undefined"}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/84a27349dda457cd.js","async":true,"nonce":"$undefined"}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/8ddf82e7e0b331fc.js","async":true,"nonce":"$undefined"}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/1d7b3500478e93ae.js","async":true,"nonce":"$undefined"}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/f0e079183e7bb90c.js","async":true,"nonce":"$undefined"}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/10757c2146f43db4.js","async":true,"nonce":"$undefined"}],"$Lb","$Lc","$Ld","$Le","$Lf","$L10","$L11","$L12","$L13","$L14","$L15","$L16","$L17"],"$L18"]}],{},null,false,false]},null,false,false],"$L19",false]],"m":"$undefined","G":["$1a",[]],"S":true} 1b:I[897367,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"OutletBoundary"] 1c:"$Sreact.suspense" 1e:I[897367,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"ViewportBoundary"] 20:I[897367,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"MetadataBoundary"] b:["$","script","script-16",{"src":"/litellm-asset-prefix/_next/static/chunks/786e88f4abdd5c58.js","async":true,"nonce":"$undefined"}] -c:["$","script","script-17",{"src":"/litellm-asset-prefix/_next/static/chunks/43c3db1352241a8b.js","async":true,"nonce":"$undefined"}] +c:["$","script","script-17",{"src":"/litellm-asset-prefix/_next/static/chunks/ffa46de7b8384155.js","async":true,"nonce":"$undefined"}] d:["$","script","script-18",{"src":"/litellm-asset-prefix/_next/static/chunks/31275eb5c6f6332f.js","async":true,"nonce":"$undefined"}] -e:["$","script","script-19",{"src":"/litellm-asset-prefix/_next/static/chunks/0ac09b227f50edb4.js","async":true,"nonce":"$undefined"}] -f:["$","script","script-20",{"src":"/litellm-asset-prefix/_next/static/chunks/4c848b12d4ecda3d.js","async":true,"nonce":"$undefined"}] -10:["$","script","script-21",{"src":"/litellm-asset-prefix/_next/static/chunks/91c828abd7c0aff5.js","async":true,"nonce":"$undefined"}] -11:["$","script","script-22",{"src":"/litellm-asset-prefix/_next/static/chunks/08c348f8e09a5cb0.js","async":true,"nonce":"$undefined"}] +e:["$","script","script-19",{"src":"/litellm-asset-prefix/_next/static/chunks/80f4410629229bf9.js","async":true,"nonce":"$undefined"}] +f:["$","script","script-20",{"src":"/litellm-asset-prefix/_next/static/chunks/75ee9aba04c74e23.js","async":true,"nonce":"$undefined"}] +10:["$","script","script-21",{"src":"/litellm-asset-prefix/_next/static/chunks/193886179a5779b5.js","async":true,"nonce":"$undefined"}] +11:["$","script","script-22",{"src":"/litellm-asset-prefix/_next/static/chunks/2063ca6435a47940.js","async":true,"nonce":"$undefined"}] 12:["$","script","script-23",{"src":"/litellm-asset-prefix/_next/static/chunks/7e417dd24c8becd0.js","async":true,"nonce":"$undefined"}] 13:["$","script","script-24",{"src":"/litellm-asset-prefix/_next/static/chunks/b323e0ef008e6348.js","async":true,"nonce":"$undefined"}] 14:["$","script","script-25",{"src":"/litellm-asset-prefix/_next/static/chunks/4ac3235460262f36.js","async":true,"nonce":"$undefined"}] 15:["$","script","script-26",{"src":"/litellm-asset-prefix/_next/static/chunks/51494a4a4b6fc437.js","async":true,"nonce":"$undefined"}] -16:["$","script","script-27",{"src":"/litellm-asset-prefix/_next/static/chunks/878832edb30e99a4.js","async":true,"nonce":"$undefined"}] -17:["$","script","script-28",{"src":"/litellm-asset-prefix/_next/static/chunks/9f1486622270556b.js","async":true,"nonce":"$undefined"}] +16:["$","script","script-27",{"src":"/litellm-asset-prefix/_next/static/chunks/d7c18aec4a87a237.js","async":true,"nonce":"$undefined"}] +17:["$","script","script-28",{"src":"/litellm-asset-prefix/_next/static/chunks/dac86522fa98e760.js","async":true,"nonce":"$undefined"}] 18:["$","$L1b",null,{"children":["$","$1c",null,{"name":"Next.MetadataOutlet","children":"$@1d"}]}] 19:["$","$1","h",{"children":[null,["$","$L1e",null,{"children":"$L1f"}],["$","div",null,{"hidden":true,"children":["$","$L20",null,{"children":["$","$1c",null,{"name":"Next.Metadata","children":"$L21"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}] 9:{} @@ -36,4 +36,4 @@ a:"$0:f:0:1:1:children:0:props:children:0:props:serverProvidedParams:params" 1f:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]] 22:I[27201,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"IconMark"] 1d:null -21:[["$","title","0",{"children":"LiteLLM Dashboard"}],["$","meta","1",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.1d32c690.ico","sizes":"48x48","type":"image/x-icon"}],["$","link","3",{"rel":"icon","href":"./favicon.ico"}],["$","$L22","4",{}]] +21:[["$","title","0",{"children":"LiteLLM Dashboard"}],["$","meta","1",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.1d32c690.ico","sizes":"48x48","type":"image/x-icon"}],["$","link","3",{"rel":"icon","href":"/get_favicon"}],["$","$L22","4",{}]] diff --git a/litellm/proxy/_experimental/out/__next._head.txt b/litellm/proxy/_experimental/out/__next._head.txt index ea6e5095458..870c89c7e11 100644 --- a/litellm/proxy/_experimental/out/__next._head.txt +++ b/litellm/proxy/_experimental/out/__next._head.txt @@ -3,4 +3,4 @@ 3:I[897367,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"MetadataBoundary"] 4:"$Sreact.suspense" 5:I[27201,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"IconMark"] -0:{"buildId":"TrcGiQpTupSbDYFFfkFHY","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.1d32c690.ico","sizes":"48x48","type":"image/x-icon"}],["$","link","3",{"rel":"icon","href":"./favicon.ico"}],["$","$L5","4",{}]]}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],"loading":null,"isPartial":false} +0:{"buildId":"LpqGBJeKQM0vUG-9uVaiY","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.1d32c690.ico","sizes":"48x48","type":"image/x-icon"}],["$","link","3",{"rel":"icon","href":"/get_favicon"}],["$","$L5","4",{}]]}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],"loading":null,"isPartial":false} diff --git a/litellm/proxy/_experimental/out/__next._index.txt b/litellm/proxy/_experimental/out/__next._index.txt index ebf6d8fec08..67c452e8c21 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/aefca6f40ea185cd.js","/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js"],"default"] -3:I[71195,["/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js"],"default"] -4:I[557951,["/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js"],"AuthProvider"] +2:I[867271,["/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js"],"default"] +3:I[71195,["/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js"],"default"] +4:I[557951,["/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js"],"AuthProvider"] 5:I[339756,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"default"] 6:I[837457,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"default"] :HL["/litellm-asset-prefix/_next/static/chunks/4e20891f2fd03463.css","style"] -:HL["/litellm-asset-prefix/_next/static/chunks/1bcca3c38c9deb02.css","style"] -0:{"buildId":"TrcGiQpTupSbDYFFfkFHY","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/4e20891f2fd03463.css","precedence":"next"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/1bcca3c38c9deb02.css","precedence":"next"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.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."}]}]]}]}]],[]]}]}]}]}]}]}]]}],"loading":null,"isPartial":false} +:HL["/litellm-asset-prefix/_next/static/chunks/47150bfa067220d3.css","style"] +0:{"buildId":"LpqGBJeKQM0vUG-9uVaiY","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/4e20891f2fd03463.css","precedence":"next"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/47150bfa067220d3.css","precedence":"next"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.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."}]}]]}]}]],[]]}]}]}]}]}]}]]}],"loading":null,"isPartial":false} diff --git a/litellm/proxy/_experimental/out/__next._tree.txt b/litellm/proxy/_experimental/out/__next._tree.txt index 4a08f4f9e11..86dc121c5f9 100644 --- a/litellm/proxy/_experimental/out/__next._tree.txt +++ b/litellm/proxy/_experimental/out/__next._tree.txt @@ -1,5 +1,5 @@ :HL["/litellm-asset-prefix/_next/static/chunks/4e20891f2fd03463.css","style"] -:HL["/litellm-asset-prefix/_next/static/chunks/1bcca3c38c9deb02.css","style"] +:HL["/litellm-asset-prefix/_next/static/chunks/47150bfa067220d3.css","style"] :HL["/litellm-asset-prefix/_next/static/media/83afe278b6a6bb3c-s.p.3a6ba036.woff2","font",{"crossOrigin":"","type":"font/woff2"}] :HL["/litellm-asset-prefix/_next/static/chunks/3f3fa56b5786d58c.css","style"] -0:{"buildId":"TrcGiQpTupSbDYFFfkFHY","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":true},"staleTime":300} +0:{"buildId":"LpqGBJeKQM0vUG-9uVaiY","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":true},"staleTime":300} diff --git a/litellm/proxy/_experimental/out/_next/static/TrcGiQpTupSbDYFFfkFHY/_buildManifest.js b/litellm/proxy/_experimental/out/_next/static/LpqGBJeKQM0vUG-9uVaiY/_buildManifest.js similarity index 100% rename from litellm/proxy/_experimental/out/_next/static/TrcGiQpTupSbDYFFfkFHY/_buildManifest.js rename to litellm/proxy/_experimental/out/_next/static/LpqGBJeKQM0vUG-9uVaiY/_buildManifest.js diff --git a/litellm/proxy/_experimental/out/_next/static/TrcGiQpTupSbDYFFfkFHY/_clientMiddlewareManifest.json b/litellm/proxy/_experimental/out/_next/static/LpqGBJeKQM0vUG-9uVaiY/_clientMiddlewareManifest.json similarity index 100% rename from litellm/proxy/_experimental/out/_next/static/TrcGiQpTupSbDYFFfkFHY/_clientMiddlewareManifest.json rename to litellm/proxy/_experimental/out/_next/static/LpqGBJeKQM0vUG-9uVaiY/_clientMiddlewareManifest.json diff --git a/litellm/proxy/_experimental/out/_next/static/TrcGiQpTupSbDYFFfkFHY/_ssgManifest.js b/litellm/proxy/_experimental/out/_next/static/LpqGBJeKQM0vUG-9uVaiY/_ssgManifest.js similarity index 100% rename from litellm/proxy/_experimental/out/_next/static/TrcGiQpTupSbDYFFfkFHY/_ssgManifest.js rename to litellm/proxy/_experimental/out/_next/static/LpqGBJeKQM0vUG-9uVaiY/_ssgManifest.js diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/adfb3758f3e2c464.js b/litellm/proxy/_experimental/out/_next/static/chunks/10376d0955336027.js similarity index 98% rename from litellm/proxy/_experimental/out/_next/static/chunks/adfb3758f3e2c464.js rename to litellm/proxy/_experimental/out/_next/static/chunks/10376d0955336027.js index 825fe327e97..55ce00c27b0 100644 --- a/litellm/proxy/_experimental/out/_next/static/chunks/adfb3758f3e2c464.js +++ b/litellm/proxy/_experimental/out/_next/static/chunks/10376d0955336027.js @@ -1,4 +1,4 @@ -(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,295320,e=>{"use strict";e.i(247167);var t=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:"M704 446H320c-4.4 0-8 3.6-8 8v402c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8V454c0-4.4-3.6-8-8-8zm-328 64h272v117H376V510zm272 290H376V683h272v117z"}},{tag:"path",attrs:{d:"M424 748a32 32 0 1064 0 32 32 0 10-64 0zm0-178a32 32 0 1064 0 32 32 0 10-64 0z"}},{tag:"path",attrs:{d:"M811.4 368.9C765.6 248 648.9 162 512.2 162S258.8 247.9 213 368.8C126.9 391.5 63.5 470.2 64 563.6 64.6 668 145.6 752.9 247.6 762c4.7.4 8.7-3.3 8.7-8v-60.4c0-4-3-7.4-7-7.9-27-3.4-52.5-15.2-72.1-34.5-24-23.5-37.2-55.1-37.2-88.6 0-28 9.1-54.4 26.2-76.4 16.7-21.4 40.2-36.9 66.1-43.7l37.9-10 13.9-36.7c8.6-22.8 20.6-44.2 35.7-63.5 14.9-19.2 32.6-36 52.4-50 41.1-28.9 89.5-44.2 140-44.2s98.9 15.3 140 44.3c19.9 14 37.5 30.8 52.4 50 15.1 19.3 27.1 40.7 35.7 63.5l13.8 36.6 37.8 10c54.2 14.4 92.1 63.7 92.1 120 0 33.6-13.2 65.1-37.2 88.6-19.5 19.2-44.9 31.1-71.9 34.5-4 .5-6.9 3.9-6.9 7.9V754c0 4.7 4.1 8.4 8.8 8 101.7-9.2 182.5-94 183.2-198.2.6-93.4-62.7-172.1-148.6-194.9z"}}]},name:"cloud-server",theme:"outlined"};var n=e.i(9583),a=i.forwardRef(function(e,a){return i.createElement(n.default,(0,t.default)({},e,{ref:a,icon:r}))});e.s(["CloudServerOutlined",0,a],295320)},283713,e=>{"use strict";var t=e.i(271645),i=e.i(764205),r=e.i(612256);let n="litellm_selected_worker_id";e.s(["useWorker",0,()=>{let{data:e}=(0,r.useUIConfig)(),a=e?.is_control_plane??!1,o=e?.workers??[],[l,s]=(0,t.useState)(()=>localStorage.getItem(n));(0,t.useEffect)(()=>{if(!l||0===o.length)return;let e=o.find(e=>e.worker_id===l);e&&(0,i.switchToWorkerUrl)(e.url)},[l,o]);let c=o.find(e=>e.worker_id===l)??null,d=(0,t.useCallback)(e=>{let t=o.find(t=>t.worker_id===e);t&&(s(e),localStorage.setItem(n,e),(0,i.switchToWorkerUrl)(t.url))},[o]);return{isControlPlane:a,workers:o,selectedWorkerId:l,selectedWorker:c,selectWorker:d,disconnectFromWorker:(0,t.useCallback)(()=>{s(null),localStorage.removeItem(n),(0,i.switchToWorkerUrl)(null)},[])}}])},954616,e=>{"use strict";var t=e.i(271645),i=e.i(114272),r=e.i(540143),n=e.i(915823),a=e.i(619273),o=class extends n.Subscribable{#e;#t=void 0;#i;#r;constructor(e,t){super(),this.#e=e,this.setOptions(t),this.bindMethods(),this.#n()}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,a.shallowEqualObjects)(this.options,t)||this.#e.getMutationCache().notify({type:"observerOptionsUpdated",mutation:this.#i,observer:this}),t?.mutationKey&&this.options.mutationKey&&(0,a.hashKey)(t.mutationKey)!==(0,a.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.#n(),this.#a(e)}getCurrentResult(){return this.#t}reset(){this.#i?.removeObserver(this),this.#i=void 0,this.#n(),this.#a()}mutate(e,t){return this.#r=t,this.#i?.removeObserver(this),this.#i=this.#e.getMutationCache().build(this.#e,this.options),this.#i.addObserver(this),this.#i.execute(e)}#n(){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}}#a(e){r.notifyManager.batch(()=>{if(this.#r&&this.hasListeners()){let t=this.#t.variables,i=this.#t.context,r={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};if(e?.type==="success"){try{this.#r.onSuccess?.(e.data,t,i,r)}catch(e){Promise.reject(e)}try{this.#r.onSettled?.(e.data,null,t,i,r)}catch(e){Promise.reject(e)}}else if(e?.type==="error"){try{this.#r.onError?.(e.error,t,i,r)}catch(e){Promise.reject(e)}try{this.#r.onSettled?.(void 0,e.error,t,i,r)}catch(e){Promise.reject(e)}}}this.listeners.forEach(e=>{e(this.#t)})})}},l=e.i(912598);function s(e,i){let n=(0,l.useQueryClient)(i),[s]=t.useState(()=>new o(n,e));t.useEffect(()=>{s.setOptions(e)},[s,e]);let c=t.useSyncExternalStore(t.useCallback(e=>s.subscribe(r.notifyManager.batchCalls(e)),[s]),()=>s.getCurrentResult(),()=>s.getCurrentResult()),d=t.useCallback((e,t)=>{s.mutate(e,t).catch(a.noop)},[s]);if(c.error&&(0,a.shouldThrowError)(s.options.throwOnError,[c.error]))throw c.error;return{...c,mutate:d,mutateAsync:c.mutate}}e.s(["useMutation",()=>s],954616)},175712,e=>{"use strict";e.i(247167);var t=e.i(271645),i=e.i(343794),r=e.i(529681),n=e.i(242064),a=e.i(517455),o=e.i(185793),l=e.i(721369),s=function(e,t){var i={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&0>t.indexOf(r)&&(i[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var n=0,r=Object.getOwnPropertySymbols(e);nt.indexOf(r[n])&&Object.prototype.propertyIsEnumerable.call(e,r[n])&&(i[r[n]]=e[r[n]]);return i};let c=e=>{var{prefixCls:r,className:a,hoverable:o=!0}=e,l=s(e,["prefixCls","className","hoverable"]);let{getPrefixCls:c}=t.useContext(n.ConfigContext),d=c("card",r),u=(0,i.default)(`${d}-grid`,a,{[`${d}-grid-hoverable`]:o});return t.createElement("div",Object.assign({},l,{className:u}))};e.i(296059);var d=e.i(915654),u=e.i(183293),m=e.i(246422),p=e.i(838378);let g=(0,m.genStyleHooks)("Card",e=>{let t=(0,p.mergeToken)(e,{cardShadow:e.boxShadowCard,cardHeadPadding:e.padding,cardPaddingBase:e.paddingLG,cardActionsIconSize:e.fontSize});return[(e=>{let{componentCls:t,cardShadow:i,cardHeadPadding:r,colorBorderSecondary:n,boxShadowTertiary:a,bodyPadding:o,extraColor:l}=e;return{[t]:Object.assign(Object.assign({},(0,u.resetComponent)(e)),{position:"relative",background:e.colorBgContainer,borderRadius:e.borderRadiusLG,[`&:not(${t}-bordered)`]:{boxShadow:a},[`${t}-head`]:(e=>{let{antCls:t,componentCls:i,headerHeight:r,headerPadding:n,tabsMarginBottom:a}=e;return Object.assign(Object.assign({display:"flex",justifyContent:"center",flexDirection:"column",minHeight:r,marginBottom:-1,padding:`0 ${(0,d.unit)(n)}`,color:e.colorTextHeading,fontWeight:e.fontWeightStrong,fontSize:e.headerFontSize,background:e.headerBg,borderBottom:`${(0,d.unit)(e.lineWidth)} ${e.lineType} ${e.colorBorderSecondary}`,borderRadius:`${(0,d.unit)(e.borderRadiusLG)} ${(0,d.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),{[` +(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,295320,e=>{"use strict";e.i(247167);var t=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:"M704 446H320c-4.4 0-8 3.6-8 8v402c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8V454c0-4.4-3.6-8-8-8zm-328 64h272v117H376V510zm272 290H376V683h272v117z"}},{tag:"path",attrs:{d:"M424 748a32 32 0 1064 0 32 32 0 10-64 0zm0-178a32 32 0 1064 0 32 32 0 10-64 0z"}},{tag:"path",attrs:{d:"M811.4 368.9C765.6 248 648.9 162 512.2 162S258.8 247.9 213 368.8C126.9 391.5 63.5 470.2 64 563.6 64.6 668 145.6 752.9 247.6 762c4.7.4 8.7-3.3 8.7-8v-60.4c0-4-3-7.4-7-7.9-27-3.4-52.5-15.2-72.1-34.5-24-23.5-37.2-55.1-37.2-88.6 0-28 9.1-54.4 26.2-76.4 16.7-21.4 40.2-36.9 66.1-43.7l37.9-10 13.9-36.7c8.6-22.8 20.6-44.2 35.7-63.5 14.9-19.2 32.6-36 52.4-50 41.1-28.9 89.5-44.2 140-44.2s98.9 15.3 140 44.3c19.9 14 37.5 30.8 52.4 50 15.1 19.3 27.1 40.7 35.7 63.5l13.8 36.6 37.8 10c54.2 14.4 92.1 63.7 92.1 120 0 33.6-13.2 65.1-37.2 88.6-19.5 19.2-44.9 31.1-71.9 34.5-4 .5-6.9 3.9-6.9 7.9V754c0 4.7 4.1 8.4 8.8 8 101.7-9.2 182.5-94 183.2-198.2.6-93.4-62.7-172.1-148.6-194.9z"}}]},name:"cloud-server",theme:"outlined"};var n=e.i(9583),a=i.forwardRef(function(e,a){return i.createElement(n.default,(0,t.default)({},e,{ref:a,icon:r}))});e.s(["CloudServerOutlined",0,a],295320)},283713,e=>{"use strict";var t=e.i(271645),i=e.i(602869),r=e.i(612256);let n="litellm_selected_worker_id";e.s(["useWorker",0,()=>{let{data:e}=(0,r.useUIConfig)(),a=e?.is_control_plane??!1,o=e?.workers??[],[l,s]=(0,t.useState)(()=>localStorage.getItem(n));(0,t.useEffect)(()=>{if(!l||0===o.length)return;let e=o.find(e=>e.worker_id===l);e&&(0,i.switchToWorkerUrl)(e.url)},[l,o]);let c=o.find(e=>e.worker_id===l)??null,d=(0,t.useCallback)(e=>{let t=o.find(t=>t.worker_id===e);t&&(s(e),localStorage.setItem(n,e),(0,i.switchToWorkerUrl)(t.url))},[o]);return{isControlPlane:a,workers:o,selectedWorkerId:l,selectedWorker:c,selectWorker:d,disconnectFromWorker:(0,t.useCallback)(()=>{s(null),localStorage.removeItem(n),(0,i.switchToWorkerUrl)(null)},[])}}])},954616,e=>{"use strict";var t=e.i(271645),i=e.i(114272),r=e.i(540143),n=e.i(915823),a=e.i(619273),o=class extends n.Subscribable{#e;#t=void 0;#i;#r;constructor(e,t){super(),this.#e=e,this.setOptions(t),this.bindMethods(),this.#n()}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,a.shallowEqualObjects)(this.options,t)||this.#e.getMutationCache().notify({type:"observerOptionsUpdated",mutation:this.#i,observer:this}),t?.mutationKey&&this.options.mutationKey&&(0,a.hashKey)(t.mutationKey)!==(0,a.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.#n(),this.#a(e)}getCurrentResult(){return this.#t}reset(){this.#i?.removeObserver(this),this.#i=void 0,this.#n(),this.#a()}mutate(e,t){return this.#r=t,this.#i?.removeObserver(this),this.#i=this.#e.getMutationCache().build(this.#e,this.options),this.#i.addObserver(this),this.#i.execute(e)}#n(){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}}#a(e){r.notifyManager.batch(()=>{if(this.#r&&this.hasListeners()){let t=this.#t.variables,i=this.#t.context,r={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};if(e?.type==="success"){try{this.#r.onSuccess?.(e.data,t,i,r)}catch(e){Promise.reject(e)}try{this.#r.onSettled?.(e.data,null,t,i,r)}catch(e){Promise.reject(e)}}else if(e?.type==="error"){try{this.#r.onError?.(e.error,t,i,r)}catch(e){Promise.reject(e)}try{this.#r.onSettled?.(void 0,e.error,t,i,r)}catch(e){Promise.reject(e)}}}this.listeners.forEach(e=>{e(this.#t)})})}},l=e.i(912598);function s(e,i){let n=(0,l.useQueryClient)(i),[s]=t.useState(()=>new o(n,e));t.useEffect(()=>{s.setOptions(e)},[s,e]);let c=t.useSyncExternalStore(t.useCallback(e=>s.subscribe(r.notifyManager.batchCalls(e)),[s]),()=>s.getCurrentResult(),()=>s.getCurrentResult()),d=t.useCallback((e,t)=>{s.mutate(e,t).catch(a.noop)},[s]);if(c.error&&(0,a.shouldThrowError)(s.options.throwOnError,[c.error]))throw c.error;return{...c,mutate:d,mutateAsync:c.mutate}}e.s(["useMutation",()=>s],954616)},175712,e=>{"use strict";e.i(247167);var t=e.i(271645),i=e.i(343794),r=e.i(529681),n=e.i(242064),a=e.i(517455),o=e.i(185793),l=e.i(721369),s=function(e,t){var i={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&0>t.indexOf(r)&&(i[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var n=0,r=Object.getOwnPropertySymbols(e);nt.indexOf(r[n])&&Object.prototype.propertyIsEnumerable.call(e,r[n])&&(i[r[n]]=e[r[n]]);return i};let c=e=>{var{prefixCls:r,className:a,hoverable:o=!0}=e,l=s(e,["prefixCls","className","hoverable"]);let{getPrefixCls:c}=t.useContext(n.ConfigContext),d=c("card",r),u=(0,i.default)(`${d}-grid`,a,{[`${d}-grid-hoverable`]:o});return t.createElement("div",Object.assign({},l,{className:u}))};e.i(296059);var d=e.i(915654),u=e.i(183293),m=e.i(246422),p=e.i(838378);let g=(0,m.genStyleHooks)("Card",e=>{let t=(0,p.mergeToken)(e,{cardShadow:e.boxShadowCard,cardHeadPadding:e.padding,cardPaddingBase:e.paddingLG,cardActionsIconSize:e.fontSize});return[(e=>{let{componentCls:t,cardShadow:i,cardHeadPadding:r,colorBorderSecondary:n,boxShadowTertiary:a,bodyPadding:o,extraColor:l}=e;return{[t]:Object.assign(Object.assign({},(0,u.resetComponent)(e)),{position:"relative",background:e.colorBgContainer,borderRadius:e.borderRadiusLG,[`&:not(${t}-bordered)`]:{boxShadow:a},[`${t}-head`]:(e=>{let{antCls:t,componentCls:i,headerHeight:r,headerPadding:n,tabsMarginBottom:a}=e;return Object.assign(Object.assign({display:"flex",justifyContent:"center",flexDirection:"column",minHeight:r,marginBottom:-1,padding:`0 ${(0,d.unit)(n)}`,color:e.colorTextHeading,fontWeight:e.fontWeightStrong,fontSize:e.headerFontSize,background:e.headerBg,borderBottom:`${(0,d.unit)(e.lineWidth)} ${e.lineType} ${e.colorBorderSecondary}`,borderRadius:`${(0,d.unit)(e.borderRadiusLG)} ${(0,d.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:a,color:e.colorText,fontWeight:"normal",fontSize:e.fontSize,"&-bar":{borderBottom:`${(0,d.unit)(e.lineWidth)} ${e.lineType} ${e.colorBorderSecondary}`}}})})(e),[`${t}-extra`]:{marginInlineStart:"auto",color:l,fontWeight:"normal",fontSize:e.fontSize},[`${t}-body`]:{padding:o,borderRadius:`0 0 ${(0,d.unit)(e.borderRadiusLG)} ${(0,d.unit)(e.borderRadiusLG)}`},[`${t}-grid`]:(e=>{let{cardPaddingBase:t,colorBorderSecondary:i,cardShadow:r,lineWidth:n}=e;return{width:"33.33%",padding:t,border:0,borderRadius:0,boxShadow:` @@ -9,4 +9,4 @@ 0 ${(0,d.unit)(n)} 0 0 ${i} inset; `,transition:`all ${e.motionDurationMid}`,"&-hoverable:hover":{position:"relative",zIndex:1,boxShadow:r}}})(e),[`${t}-cover`]:{"> *":{display:"block",width:"100%",borderRadius:`${(0,d.unit)(e.borderRadiusLG)} ${(0,d.unit)(e.borderRadiusLG)} 0 0`}},[`${t}-actions`]:(e=>{let{componentCls:t,iconCls:i,actionsLiMargin:r,cardActionsIconSize:n,colorBorderSecondary:a,actionsBg:o}=e;return Object.assign(Object.assign({margin:0,padding:0,listStyle:"none",background:o,borderTop:`${(0,d.unit)(e.lineWidth)} ${e.lineType} ${a}`,display:"flex",borderRadius:`0 0 ${(0,d.unit)(e.borderRadiusLG)} ${(0,d.unit)(e.borderRadiusLG)}`},(0,u.clearFix)()),{"& > li":{margin:r,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,d.unit)(e.fontHeight),transition:`color ${e.motionDurationMid}`,"&:hover":{color:e.colorPrimary}},[`> ${i}`]:{fontSize:n,lineHeight:(0,d.unit)(e.calc(n).mul(e.lineHeight).equal())}},"&:not(:last-child)":{borderInlineEnd:`${(0,d.unit)(e.lineWidth)} ${e.lineType} ${a}`}}})})(e),[`${t}-meta`]:Object.assign(Object.assign({margin:`${(0,d.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,d.unit)(e.lineWidth)} ${e.lineType} ${n}`,[`${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,d.unit)(e.borderRadiusLG)} ${(0,d.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:r}}},[`${t}-type-inner`]:(e=>{let{componentCls:t,colorFillAlter:i,headerPadding:r,bodyPadding:n}=e;return{[`${t}-head`]:{padding:`0 ${(0,d.unit)(r)}`,background:i,"&-title":{fontSize:e.fontSize}},[`${t}-body`]:{padding:`${(0,d.unit)(e.padding)} ${(0,d.unit)(n)}`}}})(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:r,headerHeightSM:n,headerFontSizeSM:a}=e;return{[`${t}-small`]:{[`> ${t}-head`]:{minHeight:n,padding:`0 ${(0,d.unit)(r)}`,fontSize:a,[`> ${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 h=e.i(792812),f=function(e,t){var i={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&0>t.indexOf(r)&&(i[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var n=0,r=Object.getOwnPropertySymbols(e);nt.indexOf(r[n])&&Object.prototype.propertyIsEnumerable.call(e,r[n])&&(i[r[n]]=e[r[n]]);return i};let b=e=>{let{actionClasses:i,actions:r=[],actionStyle:n}=e;return t.createElement("ul",{className:i,style:n},r.map((e,i)=>{let n=`action-${i}`;return t.createElement("li",{style:{width:`${100/r.length}%`},key:n},t.createElement("span",null,e))}))},y=t.forwardRef((e,s)=>{let d,{prefixCls:u,className:m,rootClassName:p,style:y,extra:v,headStyle:x={},bodyStyle:$={},title:S,loading:j,bordered:w,variant:O,size:C,type:E,cover:I,actions:N,tabList:k,children:z,activeTabKey:L,defaultActiveTabKey:M,tabBarExtraContent:R,hoverable:P,tabProps:T={},classNames:_,styles:G}=e,B=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:A,direction:H,card:U}=t.useContext(n.ConfigContext),[W]=(0,h.default)("card",O,w),D=e=>{var t;return(0,i.default)(null==(t=null==U?void 0:U.classNames)?void 0:t[e],null==_?void 0:_[e])},F=e=>{var t;return Object.assign(Object.assign({},null==(t=null==U?void 0:U.styles)?void 0:t[e]),null==G?void 0:G[e])},K=t.useMemo(()=>{let e=!1;return t.Children.forEach(z,t=>{(null==t?void 0:t.type)===c&&(e=!0)}),e},[z]),q=A("card",u),[V,X,J]=g(q),Q=t.createElement(o.default,{loading:!0,active:!0,paragraph:{rows:4},title:!1},z),Y=void 0!==L,Z=Object.assign(Object.assign({},T),{[Y?"activeKey":"defaultActiveKey"]:Y?L:M,tabBarExtraContent:R}),ee=(0,a.default)(C),et=ee&&"default"!==ee?ee:"large",ei=k?t.createElement(l.default,Object.assign({size:et},Z,{className:`${q}-head-tabs`,onChange:t=>{var i;null==(i=e.onTabChange)||i.call(e,t)},items:k.map(e=>{var{tab:t}=e;return Object.assign({label:t},f(e,["tab"]))})})):null;if(S||v||ei){let e=(0,i.default)(`${q}-head`,D("header")),r=(0,i.default)(`${q}-head-title`,D("title")),n=(0,i.default)(`${q}-extra`,D("extra")),a=Object.assign(Object.assign({},x),F("header"));d=t.createElement("div",{className:e,style:a},t.createElement("div",{className:`${q}-head-wrapper`},S&&t.createElement("div",{className:r,style:F("title")},S),v&&t.createElement("div",{className:n,style:F("extra")},v)),ei)}let er=(0,i.default)(`${q}-cover`,D("cover")),en=I?t.createElement("div",{className:er,style:F("cover")},I):null,ea=(0,i.default)(`${q}-body`,D("body")),eo=Object.assign(Object.assign({},$),F("body")),el=t.createElement("div",{className:ea,style:eo},j?Q:z),es=(0,i.default)(`${q}-actions`,D("actions")),ec=(null==N?void 0:N.length)?t.createElement(b,{actionClasses:es,actionStyle:F("actions"),actions:N}):null,ed=(0,r.default)(B,["onTabChange"]),eu=(0,i.default)(q,null==U?void 0:U.className,{[`${q}-loading`]:j,[`${q}-bordered`]:"borderless"!==W,[`${q}-hoverable`]:P,[`${q}-contain-grid`]:K,[`${q}-contain-tabs`]:null==k?void 0:k.length,[`${q}-${ee}`]:ee,[`${q}-type-${E}`]:!!E,[`${q}-rtl`]:"rtl"===H},m,p,X,J),em=Object.assign(Object.assign({},null==U?void 0:U.style),y);return V(t.createElement("div",Object.assign({ref:s},ed,{className:eu,style:em}),d,en,el,ec))});var v=function(e,t){var i={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&0>t.indexOf(r)&&(i[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var n=0,r=Object.getOwnPropertySymbols(e);nt.indexOf(r[n])&&Object.prototype.propertyIsEnumerable.call(e,r[n])&&(i[r[n]]=e[r[n]]);return i};y.Grid=c,y.Meta=e=>{let{prefixCls:r,className:a,avatar:o,title:l,description:s}=e,c=v(e,["prefixCls","className","avatar","title","description"]),{getPrefixCls:d}=t.useContext(n.ConfigContext),u=d("card",r),m=(0,i.default)(`${u}-meta`,a),p=o?t.createElement("div",{className:`${u}-meta-avatar`},o):null,g=l?t.createElement("div",{className:`${u}-meta-title`},l):null,h=s?t.createElement("div",{className:`${u}-meta-description`},s):null,f=g||h?t.createElement("div",{className:`${u}-meta-detail`},g,h):null;return t.createElement("div",Object.assign({},c,{className:m}),p,f)},e.s(["Card",0,y],175712)},770914,908286,38243,e=>{"use strict";e.i(247167);var t=e.i(271645),i=e.i(343794),r=e.i(876556);function n(e){return["small","middle","large"].includes(e)}function a(e){return!!e&&"number"==typeof e&&!Number.isNaN(e)}e.s(["isPresetSize",()=>n,"isValidGapNumber",()=>a],908286);var o=e.i(242064),l=e.i(249616),s=e.i(372409),c=e.i(246422);let d=(0,c.genStyleHooks)(["Space","Addon"],e=>[(e=>{let{componentCls:t,borderRadius:i,paddingSM:r,colorBorder:n,paddingXS:a,fontSizeLG:o,fontSizeSM:l,borderRadiusLG:c,borderRadiusSM:d,colorBgContainerDisabled:u,lineWidth:m}=e;return{[t]:[{display:"inline-flex",alignItems:"center",gap:0,paddingInline:r,margin:0,background:u,borderWidth:m,borderStyle:"solid",borderColor:n,borderRadius:i,"&-large":{fontSize:o,borderRadius:c},"&-small":{paddingInline:a,borderRadius:d,fontSize:l},"&-compact-last-item":{borderEndStartRadius:0,borderStartStartRadius:0},"&-compact-first-item":{borderEndEndRadius:0,borderStartEndRadius:0},"&-compact-item:not(:first-child):not(:last-child)":{borderRadius:0},"&-compact-item:not(:last-child)":{borderInlineEndWidth:0}},(0,s.genCompactItemStyle)(e,{focus:!1})]}})(e)]);var u=function(e,t){var i={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&0>t.indexOf(r)&&(i[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var n=0,r=Object.getOwnPropertySymbols(e);nt.indexOf(r[n])&&Object.prototype.propertyIsEnumerable.call(e,r[n])&&(i[r[n]]=e[r[n]]);return i};let m=t.default.forwardRef((e,r)=>{let{className:n,children:a,style:s,prefixCls:c}=e,m=u(e,["className","children","style","prefixCls"]),{getPrefixCls:p,direction:g}=t.default.useContext(o.ConfigContext),h=p("space-addon",c),[f,b,y]=d(h),{compactItemClassnames:v,compactSize:x}=(0,l.useCompactItemContext)(h,g),$=(0,i.default)(h,b,v,y,{[`${h}-${x}`]:x},n);return f(t.default.createElement("div",Object.assign({ref:r,className:$,style:s},m),a))}),p=t.default.createContext({latestIndex:0}),g=p.Provider,h=({className:e,index:i,children:r,split:n,style:a})=>{let{latestIndex:o}=t.useContext(p);return null==r?null:t.createElement(t.Fragment,null,t.createElement("div",{className:e,style:a},r),i{let t=(0,f.mergeToken)(e,{spaceGapSmallSize:e.paddingXS,spaceGapMiddleSize:e.padding,spaceGapLargeSize:e.paddingLG});return[(e=>{let{componentCls:t,antCls:i}=e;return{[t]:{display:"inline-flex","&-rtl":{direction:"rtl"},"&-vertical":{flexDirection:"column"},"&-align":{flexDirection:"column","&-center":{alignItems:"center"},"&-start":{alignItems:"flex-start"},"&-end":{alignItems:"flex-end"},"&-baseline":{alignItems:"baseline"}},[`${t}-item:empty`]:{display:"none"},[`${t}-item > ${i}-badge-not-a-wrapper:only-child`]:{display:"block"}}}})(t),(e=>{let{componentCls:t}=e;return{[t]:{"&-gap-row-small":{rowGap:e.spaceGapSmallSize},"&-gap-row-middle":{rowGap:e.spaceGapMiddleSize},"&-gap-row-large":{rowGap:e.spaceGapLargeSize},"&-gap-col-small":{columnGap:e.spaceGapSmallSize},"&-gap-col-middle":{columnGap:e.spaceGapMiddleSize},"&-gap-col-large":{columnGap:e.spaceGapLargeSize}}}})(t)]},()=>({}),{resetStyle:!1});var y=function(e,t){var i={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&0>t.indexOf(r)&&(i[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var n=0,r=Object.getOwnPropertySymbols(e);nt.indexOf(r[n])&&Object.prototype.propertyIsEnumerable.call(e,r[n])&&(i[r[n]]=e[r[n]]);return i};let v=t.forwardRef((e,l)=>{var s;let{getPrefixCls:c,direction:d,size:u,className:m,style:p,classNames:f,styles:v}=(0,o.useComponentConfig)("space"),{size:x=null!=u?u:"small",align:$,className:S,rootClassName:j,children:w,direction:O="horizontal",prefixCls:C,split:E,style:I,wrap:N=!1,classNames:k,styles:z}=e,L=y(e,["size","align","className","rootClassName","children","direction","prefixCls","split","style","wrap","classNames","styles"]),[M,R]=Array.isArray(x)?x:[x,x],P=n(R),T=n(M),_=a(R),G=a(M),B=(0,r.default)(w,{keepEmpty:!0}),A=void 0===$&&"horizontal"===O?"center":$,H=c("space",C),[U,W,D]=b(H),F=(0,i.default)(H,m,W,`${H}-${O}`,{[`${H}-rtl`]:"rtl"===d,[`${H}-align-${A}`]:A,[`${H}-gap-row-${R}`]:P,[`${H}-gap-col-${M}`]:T},S,j,D),K=(0,i.default)(`${H}-item`,null!=(s=null==k?void 0:k.item)?s:f.item),q=Object.assign(Object.assign({},v.item),null==z?void 0:z.item),V=B.map((e,i)=>{let r=(null==e?void 0:e.key)||`${K}-${i}`;return t.createElement(h,{className:K,key:r,index:i,split:E,style:q},e)}),X=t.useMemo(()=>({latestIndex:B.reduce((e,t,i)=>null!=t?i:e,0)}),[B]);if(0===B.length)return null;let J={};return N&&(J.flexWrap="wrap"),!T&&G&&(J.columnGap=M),!P&&_&&(J.rowGap=R),U(t.createElement("div",Object.assign({ref:l,className:F,style:Object.assign(Object.assign(Object.assign({},J),p),I)},L),t.createElement(g,{value:X},V)))});v.Compact=l.default,v.Addon=m,e.s(["default",0,v],38243),e.s(["Space",0,v],770914)},560445,e=>{"use strict";e.i(247167);var t=e.i(271645),i=e.i(201072),r=e.i(726289),n=e.i(864517),a=e.i(562901),o=e.i(779573),l=e.i(343794),s=e.i(361275),c=e.i(244009),d=e.i(611935),u=e.i(763731),m=e.i(242064);e.i(296059);var p=e.i(915654),g=e.i(183293),h=e.i(246422);let f=(e,t,i,r,n)=>({background:e,border:`${(0,p.unit)(r.lineWidth)} ${r.lineType} ${t}`,[`${n}-icon`]:{color:i}}),b=(0,h.genStyleHooks)("Alert",e=>[(e=>{let{componentCls:t,motionDurationSlow:i,marginXS:r,marginSM:n,fontSize:a,fontSizeLG:o,lineHeight:l,borderRadiusLG:s,motionEaseInOutCirc:c,withDescriptionIconSize:d,colorText:u,colorTextHeading:m,withDescriptionPadding:p,defaultPadding:h}=e;return{[t]:Object.assign(Object.assign({},(0,g.resetComponent)(e)),{position:"relative",display:"flex",alignItems:"center",padding:h,wordWrap:"break-word",borderRadius:s,[`&${t}-rtl`]:{direction:"rtl"},[`${t}-content`]:{flex:1,minWidth:0},[`${t}-icon`]:{marginInlineEnd:r,lineHeight:0},"&-description":{display:"none",fontSize:a,lineHeight:l},"&-message":{color:m},[`&${t}-motion-leave`]:{overflow:"hidden",opacity:1,transition:`max-height ${i} ${c}, opacity ${i} ${c}, padding-top ${i} ${c}, padding-bottom ${i} ${c}, - margin-bottom ${i} ${c}`},[`&${t}-motion-leave-active`]:{maxHeight:0,marginBottom:"0 !important",paddingTop:0,paddingBottom:0,opacity:0}}),[`${t}-with-description`]:{alignItems:"flex-start",padding:p,[`${t}-icon`]:{marginInlineEnd:n,fontSize:d,lineHeight:0},[`${t}-message`]:{display:"block",marginBottom:r,color:m,fontSize:o},[`${t}-description`]:{display:"block",color:u}},[`${t}-banner`]:{marginBottom:0,border:"0 !important",borderRadius:0}}})(e),(e=>{let{componentCls:t,colorSuccess:i,colorSuccessBorder:r,colorSuccessBg:n,colorWarning:a,colorWarningBorder:o,colorWarningBg:l,colorError:s,colorErrorBorder:c,colorErrorBg:d,colorInfo:u,colorInfoBorder:m,colorInfoBg:p}=e;return{[t]:{"&-success":f(n,r,i,e,t),"&-info":f(p,m,u,e,t),"&-warning":f(l,o,a,e,t),"&-error":Object.assign(Object.assign({},f(d,c,s,e,t)),{[`${t}-description > pre`]:{margin:0,padding:0}})}}})(e),(e=>{let{componentCls:t,iconCls:i,motionDurationMid:r,marginXS:n,fontSizeIcon:a,colorIcon:o,colorIconHover:l}=e;return{[t]:{"&-action":{marginInlineStart:n},[`${t}-close-icon`]:{marginInlineStart:n,padding:0,overflow:"hidden",fontSize:a,lineHeight:(0,p.unit)(a),backgroundColor:"transparent",border:"none",outline:"none",cursor:"pointer",[`${i}-close`]:{color:o,transition:`color ${r}`,"&:hover":{color:l}}},"&-close-text":{color:o,transition:`color ${r}`,"&:hover":{color:l}}}}})(e)],e=>({withDescriptionIconSize:e.fontSizeHeading3,defaultPadding:`${e.paddingContentVerticalSM}px 12px`,withDescriptionPadding:`${e.paddingMD}px ${e.paddingContentHorizontalLG}px`}));var y=function(e,t){var i={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&0>t.indexOf(r)&&(i[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var n=0,r=Object.getOwnPropertySymbols(e);nt.indexOf(r[n])&&Object.prototype.propertyIsEnumerable.call(e,r[n])&&(i[r[n]]=e[r[n]]);return i};let v={success:i.default,info:o.default,error:r.default,warning:a.default},x=e=>{let{icon:i,prefixCls:r,type:n}=e,a=v[n]||null;return i?(0,u.replaceElement)(i,t.createElement("span",{className:`${r}-icon`},i),()=>({className:(0,l.default)(`${r}-icon`,i.props.className)})):t.createElement(a,{className:`${r}-icon`})},$=e=>{let{isClosable:i,prefixCls:r,closeIcon:a,handleClose:o,ariaProps:l}=e,s=!0===a||void 0===a?t.createElement(n.default,null):a;return i?t.createElement("button",Object.assign({type:"button",onClick:o,className:`${r}-close-icon`,tabIndex:0},l),s):null},S=t.forwardRef((e,i)=>{let{description:r,prefixCls:n,message:a,banner:o,className:u,rootClassName:p,style:g,onMouseEnter:h,onMouseLeave:f,onClick:v,afterClose:S,showIcon:j,closable:w,closeText:O,closeIcon:C,action:E,id:I}=e,N=y(e,["description","prefixCls","message","banner","className","rootClassName","style","onMouseEnter","onMouseLeave","onClick","afterClose","showIcon","closable","closeText","closeIcon","action","id"]),[k,z]=t.useState(!1),L=t.useRef(null);t.useImperativeHandle(i,()=>({nativeElement:L.current}));let{getPrefixCls:M,direction:R,closable:P,closeIcon:T,className:_,style:G}=(0,m.useComponentConfig)("alert"),B=M("alert",n),[A,H,U]=b(B),W=t=>{var i;z(!0),null==(i=e.onClose)||i.call(e,t)},D=t.useMemo(()=>void 0!==e.type?e.type:o?"warning":"info",[e.type,o]),F=t.useMemo(()=>"object"==typeof w&&!!w.closeIcon||!!O||("boolean"==typeof w?w:!1!==C&&null!=C||!!P),[O,C,w,P]),K=!!o&&void 0===j||j,q=(0,l.default)(B,`${B}-${D}`,{[`${B}-with-description`]:!!r,[`${B}-no-icon`]:!K,[`${B}-banner`]:!!o,[`${B}-rtl`]:"rtl"===R},_,u,p,U,H),V=(0,c.default)(N,{aria:!0,data:!0}),X=t.useMemo(()=>"object"==typeof w&&w.closeIcon?w.closeIcon:O||(void 0!==C?C:"object"==typeof P&&P.closeIcon?P.closeIcon:T),[C,w,P,O,T]),J=t.useMemo(()=>{let e=null!=w?w:P;if("object"==typeof e){let{closeIcon:t}=e;return y(e,["closeIcon"])}return{}},[w,P]);return A(t.createElement(s.default,{visible:!k,motionName:`${B}-motion`,motionAppear:!1,motionEnter:!1,onLeaveStart:e=>({maxHeight:e.offsetHeight}),onLeaveEnd:S},({className:i,style:n},o)=>t.createElement("div",Object.assign({id:I,ref:(0,d.composeRef)(L,o),"data-show":!k,className:(0,l.default)(q,i),style:Object.assign(Object.assign(Object.assign({},G),g),n),onMouseEnter:h,onMouseLeave:f,onClick:v,role:"alert"},V),K?t.createElement(x,{description:r,icon:e.icon,prefixCls:B,type:D}):null,t.createElement("div",{className:`${B}-content`},a?t.createElement("div",{className:`${B}-message`},a):null,r?t.createElement("div",{className:`${B}-description`},r):null),E?t.createElement("div",{className:`${B}-action`},E):null,t.createElement($,{isClosable:F,prefixCls:B,closeIcon:X,handleClose:W,ariaProps:J}))))});var j=e.i(278409),w=e.i(233848),O=e.i(487806),C=e.i(479671),E=e.i(480002),I=e.i(868917);let N=function(e){function i(){var e,t,r;return(0,j.default)(this,i),t=i,r=arguments,t=(0,O.default)(t),(e=(0,E.default)(this,(0,C.default)()?Reflect.construct(t,r||[],(0,O.default)(this).constructor):t.apply(this,r))).state={error:void 0,info:{componentStack:""}},e}return(0,I.default)(i,e),(0,w.default)(i,[{key:"componentDidCatch",value:function(e,t){this.setState({error:e,info:t})}},{key:"render",value:function(){let{message:e,description:i,id:r,children:n}=this.props,{error:a,info:o}=this.state,l=(null==o?void 0:o.componentStack)||null,s=void 0===e?(a||"").toString():e;return a?t.createElement(S,{id:r,type:"error",message:s,description:t.createElement("pre",{style:{fontSize:"0.9em",overflowX:"auto"}},void 0===i?l:i)}):n}}])}(t.Component);S.ErrorBoundary=N,e.s(["Alert",0,S],560445)},936578,571303,e=>{"use strict";var t=e.i(843476),i=e.i(115504),r=e.i(271645);function n({className:e="",...n}){var a,o;let l=(0,r.useId)();return a=()=>{let e=document.getAnimations().filter(e=>e instanceof CSSAnimation&&"spin"===e.animationName),t=e.find(e=>e.effect.target?.getAttribute("data-spinner-id")===l),i=e.find(e=>e.effect instanceof KeyframeEffect&&e.effect.target?.getAttribute("data-spinner-id")!==l);t&&i&&(t.currentTime=i.currentTime)},o=[l],(0,r.useLayoutEffect)(a,o),(0,t.jsxs)("svg",{"data-spinner-id":l,className:(0,i.cx)("pointer-events-none size-12 animate-spin text-current",e),fill:"none",viewBox:"0 0 24 24",...n,children:[(0,t.jsx)("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),(0,t.jsx)("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"})]})}function a(){return(0,t.jsxs)("div",{className:(0,i.cx)("h-screen","flex items-center justify-center gap-4"),children:[(0,t.jsx)("div",{className:"text-lg font-medium py-2 pr-4 border-r border-r-gray-200",children:"🚅 LiteLLM"}),(0,t.jsxs)("div",{className:"flex items-center justify-center gap-2",children:[(0,t.jsx)(n,{className:"size-4"}),(0,t.jsx)("span",{className:"text-gray-600 text-sm",children:"Loading..."})]})]})}e.s(["UiLoadingSpinner",()=>n],571303),e.s(["default",()=>a],936578)},594542,e=>{"use strict";var t=e.i(843476),i=e.i(954616),r=e.i(764205),n=e.i(612256),a=e.i(936578),o=e.i(268004),l=e.i(161281),s=e.i(321836),c=e.i(827252),d=e.i(295320),u=e.i(560445),m=e.i(464571),p=e.i(175712),g=e.i(808613),h=e.i(311451),f=e.i(282786),b=e.i(199133),y=e.i(770914),v=e.i(898586),x=e.i(618566),$=e.i(271645),S=e.i(283713);function j(){let[e,j]=(0,$.useState)(""),[w,O]=(0,$.useState)(""),[C,E]=(0,$.useState)(!0),{data:I,isLoading:N}=(0,n.useUIConfig)(),k=(0,i.useMutation)({mutationFn:async({username:e,password:t,useV3:i})=>await (0,r.loginCall)(e,t,i)}),z=(0,x.useRouter)(),{workers:L,selectWorker:M}=(0,S.useWorker)(),[R,P]=(0,$.useState)(null);(0,$.useEffect)(()=>{let e=new URLSearchParams(window.location.search).get("worker");e&&P(e)},[]),(0,$.useEffect)(()=>{if(N)return;if(I&&I.admin_ui_disabled)return void E(!1);let e=new URLSearchParams(window.location.search),t=e.get("code"),i=t&&/^[a-zA-Z0-9._~+/=-]+$/.test(t)?t:null;if(i){let t=localStorage.getItem("litellm_worker_url"),n=t&&/^https?:\/\/.+/.test(t)?t:null;(0,r.exchangeLoginCode)(i,n).then(()=>{e.delete("code");let t=e.toString();window.history.replaceState(null,"",window.location.pathname+(t?`?${t}`:"")),z.replace("/ui/?login=success")});return}if(e.has("worker")&&I?.is_control_plane){(0,o.clearTokenCookies)(),E(!1);return}let n=(0,o.getCookieFromDocument)("token");if(n&&!(0,l.isJwtExpired)(n)){let e=(0,s.consumeReturnUrl)();e?z.replace(e):z.replace("/ui");return}if(I&&I.auto_redirect_to_sso){let e=(0,s.getReturnUrl)(),t=`${(0,r.getProxyBaseUrl)()}/sso/key/generate`;e&&(0,s.isValidReturnUrl)(e)&&(t+=`?redirect_to=${encodeURIComponent(e)}`),z.push(t);return}E(!1)},[N,z,I]);let T=k.error instanceof Error?k.error.message:null,_=k.isPending,{Title:G,Text:B,Paragraph:A}=v.Typography;return N||C?(0,t.jsx)(a.default,{}):I&&I.admin_ui_disabled?(0,t.jsx)("div",{className:"min-h-screen flex items-center justify-center bg-gray-50",children:(0,t.jsx)(p.Card,{className:"w-full max-w-lg shadow-md",children:(0,t.jsxs)(y.Space,{direction:"vertical",size:"middle",className:"w-full",children:[(0,t.jsx)("div",{className:"text-center",children:(0,t.jsx)(G,{level:2,children:"🚅 LiteLLM"})}),(0,t.jsx)(u.Alert,{message:"Admin UI Disabled",description:(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(A,{className:"text-sm",children:"The Admin UI has been disabled by the administrator. To re-enable it, please update the following environment variable:"}),(0,t.jsx)(A,{className:"text-sm",children:(0,t.jsx)("code",{className:"bg-gray-100 px-1 py-0.5 rounded text-xs",children:"DISABLE_ADMIN_UI=False"})})]}),type:"warning",showIcon:!0})]})})}):(0,t.jsx)("div",{className:"min-h-screen flex items-center justify-center bg-gray-50",children:(0,t.jsxs)(p.Card,{className:"w-full max-w-lg shadow-md",children:[(0,t.jsxs)(y.Space,{direction:"vertical",size:"middle",className:"w-full",children:[(0,t.jsx)("div",{className:"text-center",children:(0,t.jsx)(G,{level:2,children:"🚅 LiteLLM"})}),(0,t.jsxs)("div",{className:"text-center",children:[(0,t.jsx)(G,{level:3,children:"Login"}),(0,t.jsx)(B,{type:"secondary",children:"Access your LiteLLM Admin UI."})]}),(0,t.jsx)(u.Alert,{message:"Default Credentials",description:(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)(A,{className:"text-sm",children:["By default, Username is ",(0,t.jsx)("code",{className:"bg-gray-100 px-1 py-0.5 rounded text-xs",children:"admin"})," and Password is your set LiteLLM Proxy",(0,t.jsx)("code",{className:"bg-gray-100 px-1 py-0.5 rounded text-xs",children:"MASTER_KEY"}),"."]}),(0,t.jsxs)(A,{className:"text-sm",children:["Need to set UI credentials or SSO?"," ",(0,t.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/ui",target:"_blank",rel:"noopener noreferrer",children:"Check the documentation"}),"."]})]}),type:"info",icon:(0,t.jsx)(c.InfoCircleOutlined,{}),showIcon:!0}),T&&(0,t.jsx)(u.Alert,{message:T,type:"error",showIcon:!0}),(0,t.jsxs)(g.Form,{onFinish:()=>{let t=L.find(e=>e.worker_id===R);t&&(0,r.switchToWorkerUrl)(t.url),k.mutate({username:e,password:w,useV3:!!t},{onSuccess:e=>{if(t)M(t.worker_id),z.push("/ui/?login=success");else{let t=(0,s.consumeReturnUrl)();t?z.push(t):z.push(e.redirect_url)}},onError:()=>{t&&(0,r.switchToWorkerUrl)(null)}})},layout:"vertical",requiredMark:!1,children:[I?.is_control_plane&&L.length>0&&(0,t.jsx)(g.Form.Item,{label:"Worker",style:{marginBottom:16},children:(0,t.jsx)(b.Select,{value:R||void 0,onChange:e=>P(e),placeholder:"Choose a worker to connect to",size:"large",suffixIcon:(0,t.jsx)(d.CloudServerOutlined,{}),options:L.map(e=>({label:e.name,value:e.worker_id}))})}),(0,t.jsx)(g.Form.Item,{label:"Username",name:"username",rules:[{required:!0,message:"Please enter your username"}],children:(0,t.jsx)(h.Input,{placeholder:"Enter your username",autoComplete:"username",value:e,onChange:e=>j(e.target.value),disabled:_,size:"large",className:"rounded-md border-gray-300"})}),(0,t.jsx)(g.Form.Item,{label:"Password",name:"password",rules:[{required:!0,message:"Please enter your password"}],children:(0,t.jsx)(h.Input.Password,{placeholder:"Enter your password",autoComplete:"current-password",value:w,onChange:e=>O(e.target.value),disabled:_,size:"large"})}),(0,t.jsx)(g.Form.Item,{children:(0,t.jsx)(m.Button,{type:"primary",htmlType:"submit",loading:_,disabled:_,block:!0,size:"large",children:_?"Logging in...":"Login"})}),(0,t.jsx)(g.Form.Item,{children:I?.sso_configured?(0,t.jsx)(m.Button,{disabled:_||!!R&&0===L.length,onClick:()=>{let e=L.find(e=>e.worker_id===R);e&&(localStorage.setItem("litellm_selected_worker_id",R),(0,r.switchToWorkerUrl)(e.url));let t=e?.url??(0,r.getProxyBaseUrl)(),i=encodeURIComponent(window.location.origin+"/ui/login");z.push(`${t}/sso/key/generate?return_to=${i}`)},block:!0,size:"large",children:"Login with SSO"}):(0,t.jsx)(f.Popover,{content:"Please configure SSO to log in with SSO.",trigger:"hover",children:(0,t.jsx)(m.Button,{disabled:!0,block:!0,size:"large",children:"Login with SSO"})})})]})]}),I?.sso_configured&&(0,t.jsx)(u.Alert,{type:"info",showIcon:!0,closable:!0,message:(0,t.jsxs)(B,{children:["Single Sign-On (SSO) is enabled. LiteLLM no longer automatically redirects to the SSO login flow upon loading this page. To re-enable auto-redirect-to-SSO, set ",(0,t.jsx)(B,{code:!0,children:"AUTO_REDIRECT_UI_LOGIN_TO_SSO=true"})," in your environment configuration."]})})]})})}e.s(["default",0,function(){return(0,t.jsx)(j,{})}],594542)}]); \ No newline at end of file + margin-bottom ${i} ${c}`},[`&${t}-motion-leave-active`]:{maxHeight:0,marginBottom:"0 !important",paddingTop:0,paddingBottom:0,opacity:0}}),[`${t}-with-description`]:{alignItems:"flex-start",padding:p,[`${t}-icon`]:{marginInlineEnd:n,fontSize:d,lineHeight:0},[`${t}-message`]:{display:"block",marginBottom:r,color:m,fontSize:o},[`${t}-description`]:{display:"block",color:u}},[`${t}-banner`]:{marginBottom:0,border:"0 !important",borderRadius:0}}})(e),(e=>{let{componentCls:t,colorSuccess:i,colorSuccessBorder:r,colorSuccessBg:n,colorWarning:a,colorWarningBorder:o,colorWarningBg:l,colorError:s,colorErrorBorder:c,colorErrorBg:d,colorInfo:u,colorInfoBorder:m,colorInfoBg:p}=e;return{[t]:{"&-success":f(n,r,i,e,t),"&-info":f(p,m,u,e,t),"&-warning":f(l,o,a,e,t),"&-error":Object.assign(Object.assign({},f(d,c,s,e,t)),{[`${t}-description > pre`]:{margin:0,padding:0}})}}})(e),(e=>{let{componentCls:t,iconCls:i,motionDurationMid:r,marginXS:n,fontSizeIcon:a,colorIcon:o,colorIconHover:l}=e;return{[t]:{"&-action":{marginInlineStart:n},[`${t}-close-icon`]:{marginInlineStart:n,padding:0,overflow:"hidden",fontSize:a,lineHeight:(0,p.unit)(a),backgroundColor:"transparent",border:"none",outline:"none",cursor:"pointer",[`${i}-close`]:{color:o,transition:`color ${r}`,"&:hover":{color:l}}},"&-close-text":{color:o,transition:`color ${r}`,"&:hover":{color:l}}}}})(e)],e=>({withDescriptionIconSize:e.fontSizeHeading3,defaultPadding:`${e.paddingContentVerticalSM}px 12px`,withDescriptionPadding:`${e.paddingMD}px ${e.paddingContentHorizontalLG}px`}));var y=function(e,t){var i={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&0>t.indexOf(r)&&(i[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var n=0,r=Object.getOwnPropertySymbols(e);nt.indexOf(r[n])&&Object.prototype.propertyIsEnumerable.call(e,r[n])&&(i[r[n]]=e[r[n]]);return i};let v={success:i.default,info:o.default,error:r.default,warning:a.default},x=e=>{let{icon:i,prefixCls:r,type:n}=e,a=v[n]||null;return i?(0,u.replaceElement)(i,t.createElement("span",{className:`${r}-icon`},i),()=>({className:(0,l.default)(`${r}-icon`,i.props.className)})):t.createElement(a,{className:`${r}-icon`})},$=e=>{let{isClosable:i,prefixCls:r,closeIcon:a,handleClose:o,ariaProps:l}=e,s=!0===a||void 0===a?t.createElement(n.default,null):a;return i?t.createElement("button",Object.assign({type:"button",onClick:o,className:`${r}-close-icon`,tabIndex:0},l),s):null},S=t.forwardRef((e,i)=>{let{description:r,prefixCls:n,message:a,banner:o,className:u,rootClassName:p,style:g,onMouseEnter:h,onMouseLeave:f,onClick:v,afterClose:S,showIcon:j,closable:w,closeText:O,closeIcon:C,action:E,id:I}=e,N=y(e,["description","prefixCls","message","banner","className","rootClassName","style","onMouseEnter","onMouseLeave","onClick","afterClose","showIcon","closable","closeText","closeIcon","action","id"]),[k,z]=t.useState(!1),L=t.useRef(null);t.useImperativeHandle(i,()=>({nativeElement:L.current}));let{getPrefixCls:M,direction:R,closable:P,closeIcon:T,className:_,style:G}=(0,m.useComponentConfig)("alert"),B=M("alert",n),[A,H,U]=b(B),W=t=>{var i;z(!0),null==(i=e.onClose)||i.call(e,t)},D=t.useMemo(()=>void 0!==e.type?e.type:o?"warning":"info",[e.type,o]),F=t.useMemo(()=>"object"==typeof w&&!!w.closeIcon||!!O||("boolean"==typeof w?w:!1!==C&&null!=C||!!P),[O,C,w,P]),K=!!o&&void 0===j||j,q=(0,l.default)(B,`${B}-${D}`,{[`${B}-with-description`]:!!r,[`${B}-no-icon`]:!K,[`${B}-banner`]:!!o,[`${B}-rtl`]:"rtl"===R},_,u,p,U,H),V=(0,c.default)(N,{aria:!0,data:!0}),X=t.useMemo(()=>"object"==typeof w&&w.closeIcon?w.closeIcon:O||(void 0!==C?C:"object"==typeof P&&P.closeIcon?P.closeIcon:T),[C,w,P,O,T]),J=t.useMemo(()=>{let e=null!=w?w:P;if("object"==typeof e){let{closeIcon:t}=e;return y(e,["closeIcon"])}return{}},[w,P]);return A(t.createElement(s.default,{visible:!k,motionName:`${B}-motion`,motionAppear:!1,motionEnter:!1,onLeaveStart:e=>({maxHeight:e.offsetHeight}),onLeaveEnd:S},({className:i,style:n},o)=>t.createElement("div",Object.assign({id:I,ref:(0,d.composeRef)(L,o),"data-show":!k,className:(0,l.default)(q,i),style:Object.assign(Object.assign(Object.assign({},G),g),n),onMouseEnter:h,onMouseLeave:f,onClick:v,role:"alert"},V),K?t.createElement(x,{description:r,icon:e.icon,prefixCls:B,type:D}):null,t.createElement("div",{className:`${B}-content`},a?t.createElement("div",{className:`${B}-message`},a):null,r?t.createElement("div",{className:`${B}-description`},r):null),E?t.createElement("div",{className:`${B}-action`},E):null,t.createElement($,{isClosable:F,prefixCls:B,closeIcon:X,handleClose:W,ariaProps:J}))))});var j=e.i(278409),w=e.i(233848),O=e.i(487806),C=e.i(479671),E=e.i(480002),I=e.i(868917);let N=function(e){function i(){var e,t,r;return(0,j.default)(this,i),t=i,r=arguments,t=(0,O.default)(t),(e=(0,E.default)(this,(0,C.default)()?Reflect.construct(t,r||[],(0,O.default)(this).constructor):t.apply(this,r))).state={error:void 0,info:{componentStack:""}},e}return(0,I.default)(i,e),(0,w.default)(i,[{key:"componentDidCatch",value:function(e,t){this.setState({error:e,info:t})}},{key:"render",value:function(){let{message:e,description:i,id:r,children:n}=this.props,{error:a,info:o}=this.state,l=(null==o?void 0:o.componentStack)||null,s=void 0===e?(a||"").toString():e;return a?t.createElement(S,{id:r,type:"error",message:s,description:t.createElement("pre",{style:{fontSize:"0.9em",overflowX:"auto"}},void 0===i?l:i)}):n}}])}(t.Component);S.ErrorBoundary=N,e.s(["Alert",0,S],560445)},936578,571303,e=>{"use strict";var t=e.i(843476),i=e.i(115504),r=e.i(271645);function n({className:e="",...n}){var a,o;let l=(0,r.useId)();return a=()=>{let e=document.getAnimations().filter(e=>e instanceof CSSAnimation&&"spin"===e.animationName),t=e.find(e=>e.effect.target?.getAttribute("data-spinner-id")===l),i=e.find(e=>e.effect instanceof KeyframeEffect&&e.effect.target?.getAttribute("data-spinner-id")!==l);t&&i&&(t.currentTime=i.currentTime)},o=[l],(0,r.useLayoutEffect)(a,o),(0,t.jsxs)("svg",{"data-spinner-id":l,className:(0,i.cx)("pointer-events-none size-12 animate-spin text-current",e),fill:"none",viewBox:"0 0 24 24",...n,children:[(0,t.jsx)("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),(0,t.jsx)("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"})]})}function a(){return(0,t.jsxs)("div",{className:(0,i.cx)("h-screen","flex items-center justify-center gap-4"),children:[(0,t.jsx)("div",{className:"text-lg font-medium py-2 pr-4 border-r border-r-gray-200",children:"🚅 LiteLLM"}),(0,t.jsxs)("div",{className:"flex items-center justify-center gap-2",children:[(0,t.jsx)(n,{className:"size-4"}),(0,t.jsx)("span",{className:"text-gray-600 text-sm",children:"Loading..."})]})]})}e.s(["UiLoadingSpinner",()=>n],571303),e.s(["default",()=>a],936578)},594542,e=>{"use strict";var t=e.i(843476),i=e.i(954616),r=e.i(602869),n=e.i(612256),a=e.i(936578),o=e.i(268004),l=e.i(161281),s=e.i(321836),c=e.i(827252),d=e.i(295320),u=e.i(560445),m=e.i(464571),p=e.i(175712),g=e.i(808613),h=e.i(311451),f=e.i(282786),b=e.i(199133),y=e.i(770914),v=e.i(898586),x=e.i(618566),$=e.i(271645),S=e.i(283713);function j(){let[e,j]=(0,$.useState)(""),[w,O]=(0,$.useState)(""),[C,E]=(0,$.useState)(!0),{data:I,isLoading:N}=(0,n.useUIConfig)(),k=(0,i.useMutation)({mutationFn:async({username:e,password:t,useV3:i})=>await (0,r.loginCall)(e,t,i)}),z=(0,x.useRouter)(),{workers:L,selectWorker:M}=(0,S.useWorker)(),[R,P]=(0,$.useState)(null);(0,$.useEffect)(()=>{let e=new URLSearchParams(window.location.search).get("worker");e&&P(e)},[]),(0,$.useEffect)(()=>{if(N)return;if(I&&I.admin_ui_disabled)return void E(!1);let e=new URLSearchParams(window.location.search),t=e.get("code"),i=t&&/^[a-zA-Z0-9._~+/=-]+$/.test(t)?t:null;if(i){let t=localStorage.getItem("litellm_worker_url"),n=t&&/^https?:\/\/.+/.test(t)?t:null;(0,r.exchangeLoginCode)(i,n).then(()=>{e.delete("code");let t=e.toString();window.history.replaceState(null,"",window.location.pathname+(t?`?${t}`:"")),z.replace("/ui/?login=success")});return}if(e.has("worker")&&I?.is_control_plane){(0,o.clearTokenCookies)(),E(!1);return}let n=(0,o.getCookieFromDocument)("token");if(n&&!(0,l.isJwtExpired)(n)){let e=(0,s.consumeReturnUrl)();e?z.replace(e):z.replace("/ui");return}if(I&&I.auto_redirect_to_sso){let e=(0,s.getReturnUrl)(),t=`${(0,r.getProxyBaseUrl)()}/sso/key/generate`;e&&(0,s.isValidReturnUrl)(e)&&(t+=`?redirect_to=${encodeURIComponent(e)}`),z.push(t);return}E(!1)},[N,z,I]);let T=k.error instanceof Error?k.error.message:null,_=k.isPending,{Title:G,Text:B,Paragraph:A}=v.Typography;return N||C?(0,t.jsx)(a.default,{}):I&&I.admin_ui_disabled?(0,t.jsx)("div",{className:"min-h-screen flex items-center justify-center bg-gray-50",children:(0,t.jsx)(p.Card,{className:"w-full max-w-lg shadow-md",children:(0,t.jsxs)(y.Space,{direction:"vertical",size:"middle",className:"w-full",children:[(0,t.jsx)("div",{className:"text-center",children:(0,t.jsx)(G,{level:2,children:"🚅 LiteLLM"})}),(0,t.jsx)(u.Alert,{message:"Admin UI Disabled",description:(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(A,{className:"text-sm",children:"The Admin UI has been disabled by the administrator. To re-enable it, please update the following environment variable:"}),(0,t.jsx)(A,{className:"text-sm",children:(0,t.jsx)("code",{className:"bg-gray-100 px-1 py-0.5 rounded text-xs",children:"DISABLE_ADMIN_UI=False"})})]}),type:"warning",showIcon:!0})]})})}):(0,t.jsx)("div",{className:"min-h-screen flex items-center justify-center bg-gray-50",children:(0,t.jsxs)(p.Card,{className:"w-full max-w-lg shadow-md",children:[(0,t.jsxs)(y.Space,{direction:"vertical",size:"middle",className:"w-full",children:[(0,t.jsx)("div",{className:"text-center",children:(0,t.jsx)(G,{level:2,children:"🚅 LiteLLM"})}),(0,t.jsxs)("div",{className:"text-center",children:[(0,t.jsx)(G,{level:3,children:"Login"}),(0,t.jsx)(B,{type:"secondary",children:"Access your LiteLLM Admin UI."})]}),(0,t.jsx)(u.Alert,{message:"Default Credentials",description:(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)(A,{className:"text-sm",children:["By default, Username is ",(0,t.jsx)("code",{className:"bg-gray-100 px-1 py-0.5 rounded text-xs",children:"admin"})," and Password is your set LiteLLM Proxy",(0,t.jsx)("code",{className:"bg-gray-100 px-1 py-0.5 rounded text-xs",children:"MASTER_KEY"}),"."]}),(0,t.jsxs)(A,{className:"text-sm",children:["Need to set UI credentials or SSO?"," ",(0,t.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/ui",target:"_blank",rel:"noopener noreferrer",children:"Check the documentation"}),"."]})]}),type:"info",icon:(0,t.jsx)(c.InfoCircleOutlined,{}),showIcon:!0}),T&&(0,t.jsx)(u.Alert,{message:T,type:"error",showIcon:!0}),(0,t.jsxs)(g.Form,{onFinish:()=>{let t=L.find(e=>e.worker_id===R);t&&(0,r.switchToWorkerUrl)(t.url),k.mutate({username:e,password:w,useV3:!!t},{onSuccess:e=>{if(t)M(t.worker_id),z.push("/ui/?login=success");else{let t=(0,s.consumeReturnUrl)();t?z.push(t):z.push(e.redirect_url)}},onError:()=>{t&&(0,r.switchToWorkerUrl)(null)}})},layout:"vertical",requiredMark:!1,children:[I?.is_control_plane&&L.length>0&&(0,t.jsx)(g.Form.Item,{label:"Worker",style:{marginBottom:16},children:(0,t.jsx)(b.Select,{value:R||void 0,onChange:e=>P(e),placeholder:"Choose a worker to connect to",size:"large",suffixIcon:(0,t.jsx)(d.CloudServerOutlined,{}),options:L.map(e=>({label:e.name,value:e.worker_id}))})}),(0,t.jsx)(g.Form.Item,{label:"Username",name:"username",rules:[{required:!0,message:"Please enter your username"}],children:(0,t.jsx)(h.Input,{placeholder:"Enter your username",autoComplete:"username",value:e,onChange:e=>j(e.target.value),disabled:_,size:"large",className:"rounded-md border-gray-300"})}),(0,t.jsx)(g.Form.Item,{label:"Password",name:"password",rules:[{required:!0,message:"Please enter your password"}],children:(0,t.jsx)(h.Input.Password,{placeholder:"Enter your password",autoComplete:"current-password",value:w,onChange:e=>O(e.target.value),disabled:_,size:"large"})}),(0,t.jsx)(g.Form.Item,{children:(0,t.jsx)(m.Button,{type:"primary",htmlType:"submit",loading:_,disabled:_,block:!0,size:"large",children:_?"Logging in...":"Login"})}),(0,t.jsx)(g.Form.Item,{children:I?.sso_configured?(0,t.jsx)(m.Button,{disabled:_||!!R&&0===L.length,onClick:()=>{let e=L.find(e=>e.worker_id===R);e&&(localStorage.setItem("litellm_selected_worker_id",R),(0,r.switchToWorkerUrl)(e.url));let t=e?.url??(0,r.getProxyBaseUrl)(),i=encodeURIComponent(window.location.origin+"/ui/login");z.push(`${t}/sso/key/generate?return_to=${i}`)},block:!0,size:"large",children:"Login with SSO"}):(0,t.jsx)(f.Popover,{content:"Please configure SSO to log in with SSO.",trigger:"hover",children:(0,t.jsx)(m.Button,{disabled:!0,block:!0,size:"large",children:"Login with SSO"})})})]})]}),I?.sso_configured&&(0,t.jsx)(u.Alert,{type:"info",showIcon:!0,closable:!0,message:(0,t.jsxs)(B,{children:["Single Sign-On (SSO) is enabled. LiteLLM no longer automatically redirects to the SSO login flow upon loading this page. To re-enable auto-redirect-to-SSO, set"," ",(0,t.jsx)(B,{code:!0,children:"AUTO_REDIRECT_UI_LOGIN_TO_SSO=true"})," in your environment configuration."]})})]})})}e.s(["default",0,function(){return(0,t.jsx)(j,{})}],594542)}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/36d1c027ba991a4f.js b/litellm/proxy/_experimental/out/_next/static/chunks/10757c2146f43db4.js similarity index 66% rename from litellm/proxy/_experimental/out/_next/static/chunks/36d1c027ba991a4f.js rename to litellm/proxy/_experimental/out/_next/static/chunks/10757c2146f43db4.js index 091c3b70110..3b6538f90e1 100644 --- a/litellm/proxy/_experimental/out/_next/static/chunks/36d1c027ba991a4f.js +++ b/litellm/proxy/_experimental/out/_next/static/chunks/10757c2146f43db4.js @@ -1,6 +1,6 @@ (globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,127952,869216,368869,e=>{"use strict";var t=e.i(843476),n=e.i(560445),r=e.i(175712);e.i(247167);var l=e.i(271645),a=e.i(343794),o=e.i(908206),i=e.i(242064),s=e.i(517455),d=e.i(150073);let c={xxl:3,xl:3,lg:3,md:3,sm:2,xs:1},u=l.default.createContext({});var f=e.i(876556),m=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&0>t.indexOf(r)&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var l=0,r=Object.getOwnPropertySymbols(e);lt.indexOf(r[l])&&Object.prototype.propertyIsEnumerable.call(e,r[l])&&(n[r[l]]=e[r[l]]);return n},p=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&0>t.indexOf(r)&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var l=0,r=Object.getOwnPropertySymbols(e);lt.indexOf(r[l])&&Object.prototype.propertyIsEnumerable.call(e,r[l])&&(n[r[l]]=e[r[l]]);return n};let g=e=>{let{itemPrefixCls:t,component:n,span:r,className:o,style:i,labelStyle:s,contentStyle:d,bordered:c,label:f,content:m,colon:p,type:g,styles:h}=e,{classNames:x}=l.useContext(u),v=Object.assign(Object.assign({},s),null==h?void 0:h.label),b=Object.assign(Object.assign({},d),null==h?void 0:h.content);if(c)return l.createElement(n,{colSpan:r,style:i,className:(0,a.default)(o,{[`${t}-item-${g}`]:"label"===g||"content"===g,[null==x?void 0:x.label]:(null==x?void 0:x.label)&&"label"===g,[null==x?void 0:x.content]:(null==x?void 0:x.content)&&"content"===g})},null!=f&&l.createElement("span",{style:v},f),null!=m&&l.createElement("span",{style:b},m));return l.createElement(n,{colSpan:r,style:i,className:(0,a.default)(`${t}-item`,o)},l.createElement("div",{className:`${t}-item-container`},null!=f&&l.createElement("span",{style:v,className:(0,a.default)(`${t}-item-label`,null==x?void 0:x.label,{[`${t}-item-no-colon`]:!p})},f),null!=m&&l.createElement("span",{style:b,className:(0,a.default)(`${t}-item-content`,null==x?void 0:x.content)},m)))};function h(e,{colon:t,prefixCls:n,bordered:r},{component:a,type:o,showLabel:i,showContent:s,labelStyle:d,contentStyle:c,styles:u}){return e.map(({label:e,children:f,prefixCls:m=n,className:p,style:h,labelStyle:x,contentStyle:v,span:b=1,key:y,styles:w},j)=>"string"==typeof a?l.createElement(g,{key:`${o}-${y||j}`,className:p,style:h,styles:{label:Object.assign(Object.assign(Object.assign(Object.assign({},d),null==u?void 0:u.label),x),null==w?void 0:w.label),content:Object.assign(Object.assign(Object.assign(Object.assign({},c),null==u?void 0:u.content),v),null==w?void 0:w.content)},span:b,colon:t,component:a,itemPrefixCls:m,bordered:r,label:i?e:null,content:s?f:null,type:o}):[l.createElement(g,{key:`label-${y||j}`,className:p,style:Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},d),null==u?void 0:u.label),h),x),null==w?void 0:w.label),span:1,colon:t,component:a[0],itemPrefixCls:m,bordered:r,label:e,type:"label"}),l.createElement(g,{key:`content-${y||j}`,className:p,style:Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},c),null==u?void 0:u.content),h),v),null==w?void 0:w.content),span:2*b-1,component:a[1],itemPrefixCls:m,bordered:r,content:f,type:"content"})])}let x=e=>{let t=l.useContext(u),{prefixCls:n,vertical:r,row:a,index:o,bordered:i}=e;return r?l.createElement(l.Fragment,null,l.createElement("tr",{key:`label-${o}`,className:`${n}-row`},h(a,e,Object.assign({component:"th",type:"label",showLabel:!0},t))),l.createElement("tr",{key:`content-${o}`,className:`${n}-row`},h(a,e,Object.assign({component:"td",type:"content",showContent:!0},t)))):l.createElement("tr",{key:o,className:`${n}-row`},h(a,e,Object.assign({component:i?["th","td"]:"td",type:"item",showLabel:!0,showContent:!0},t)))};e.i(296059);var v=e.i(915654),b=e.i(183293),y=e.i(246422),w=e.i(838378);let j=(0,y.genStyleHooks)("Descriptions",e=>(e=>{let{componentCls:t,extraColor:n,itemPaddingBottom:r,itemPaddingEnd:l,colonMarginRight:a,colonMarginLeft:o,titleMarginBottom:i}=e;return{[t]:Object.assign(Object.assign(Object.assign({},(0,b.resetComponent)(e)),(e=>{let{componentCls:t,labelBg:n}=e;return{[`&${t}-bordered`]:{[`> ${t}-view`]:{border:`${(0,v.unit)(e.lineWidth)} ${e.lineType} ${e.colorSplit}`,"> table":{tableLayout:"auto"},[`${t}-row`]:{borderBottom:`${(0,v.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,v.unit)(e.padding)} ${(0,v.unit)(e.paddingLG)}`,borderInlineEnd:`${(0,v.unit)(e.lineWidth)} ${e.lineType} ${e.colorSplit}`,"&:last-child":{borderInlineEnd:"none"}},[`> ${t}-item-label`]:{color:e.colorTextSecondary,backgroundColor:n,"&::after":{display:"none"}}}},[`&${t}-middle`]:{[`${t}-row`]:{[`> ${t}-item-label, > ${t}-item-content`]:{padding:`${(0,v.unit)(e.paddingSM)} ${(0,v.unit)(e.paddingLG)}`}}},[`&${t}-small`]:{[`${t}-row`]:{[`> ${t}-item-label, > ${t}-item-content`]:{padding:`${(0,v.unit)(e.paddingXS)} ${(0,v.unit)(e.padding)}`}}}}}})(e)),{"&-rtl":{direction:"rtl"},[`${t}-header`]:{display:"flex",alignItems:"center",marginBottom:i},[`${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:n,fontSize:e.fontSize},[`${t}-view`]:{width:"100%",borderRadius:e.borderRadiusLG,table:{width:"100%",tableLayout:"fixed",borderCollapse:"collapse"}},[`${t}-row`]:{"> th, > td":{paddingBottom:r,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,v.unit)(o)} ${(0,v.unit)(a)}`},[`&${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,w.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 k=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&0>t.indexOf(r)&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var l=0,r=Object.getOwnPropertySymbols(e);lt.indexOf(r[l])&&Object.prototype.propertyIsEnumerable.call(e,r[l])&&(n[r[l]]=e[r[l]]);return n};let C=e=>{let t,{prefixCls:n,title:r,extra:g,column:h,colon:v=!0,bordered:b,layout:y,children:w,className:C,rootClassName:S,style:N,size:E,labelStyle:_,contentStyle:O,styles:$,items:T,classNames:I}=e,P=k(e,["prefixCls","title","extra","column","colon","bordered","layout","children","className","rootClassName","style","size","labelStyle","contentStyle","styles","items","classNames"]),{getPrefixCls:M,direction:R,className:L,style:D,classNames:A,styles:K}=(0,i.useComponentConfig)("descriptions"),B=M("descriptions",n),F=(0,d.default)(),z=l.useMemo(()=>{var e;return"number"==typeof h?h:null!=(e=(0,o.matchScreen)(F,Object.assign(Object.assign({},c),h)))?e:3},[F,h]),H=(t=l.useMemo(()=>T||(0,f.default)(w).map(e=>Object.assign(Object.assign({},null==e?void 0:e.props),{key:e.key})),[T,w]),l.useMemo(()=>t.map(e=>{var{span:t}=e,n=m(e,["span"]);return"filled"===t?Object.assign(Object.assign({},n),{filled:!0}):Object.assign(Object.assign({},n),{span:"number"==typeof t?t:(0,o.matchScreen)(F,t)})}),[t,F])),V=(0,s.default)(E),W=((e,t)=>{let[n,r]=(0,l.useMemo)(()=>{let n,r,l,a;return n=[],r=[],l=!1,a=0,t.filter(e=>e).forEach(t=>{let{filled:o}=t,i=p(t,["filled"]);if(o){r.push(i),n.push(r),r=[],a=0;return}let s=e-a;(a+=t.span||1)>=e?(a>e?(l=!0,r.push(Object.assign(Object.assign({},i),{span:s}))):r.push(i),n.push(r),r=[],a=0):r.push(i)}),r.length>0&&n.push(r),[n=n.map(t=>{let n=t.reduce((e,t)=>e+(t.span||1),0);if(n({labelStyle:_,contentStyle:O,styles:{content:Object.assign(Object.assign({},K.content),null==$?void 0:$.content),label:Object.assign(Object.assign({},K.label),null==$?void 0:$.label)},classNames:{label:(0,a.default)(A.label,null==I?void 0:I.label),content:(0,a.default)(A.content,null==I?void 0:I.content)}}),[_,O,$,I,A,K]);return U(l.createElement(u.Provider,{value:X},l.createElement("div",Object.assign({className:(0,a.default)(B,L,A.root,null==I?void 0:I.root,{[`${B}-${V}`]:V&&"default"!==V,[`${B}-bordered`]:!!b,[`${B}-rtl`]:"rtl"===R},C,S,q,G),style:Object.assign(Object.assign(Object.assign(Object.assign({},D),K.root),null==$?void 0:$.root),N)},P),(r||g)&&l.createElement("div",{className:(0,a.default)(`${B}-header`,A.header,null==I?void 0:I.header),style:Object.assign(Object.assign({},K.header),null==$?void 0:$.header)},r&&l.createElement("div",{className:(0,a.default)(`${B}-title`,A.title,null==I?void 0:I.title),style:Object.assign(Object.assign({},K.title),null==$?void 0:$.title)},r),g&&l.createElement("div",{className:(0,a.default)(`${B}-extra`,A.extra,null==I?void 0:I.extra),style:Object.assign(Object.assign({},K.extra),null==$?void 0:$.extra)},g)),l.createElement("div",{className:`${B}-view`},l.createElement("table",null,l.createElement("tbody",null,W.map((e,t)=>l.createElement(x,{key:t,index:t,colon:v,prefixCls:B,vertical:"vertical"===y,bordered:b,row:e}))))))))};C.Item=({children:e})=>e,e.s(["Descriptions",0,C],869216);var S=e.i(311451),N=e.i(212931),E=e.i(898586),_=e.i(868297),O=e.i(732961),$=e.i(289882),T=e.i(170517),I=e.i(628882),P=e.i(320890),M=e.i(104458),R=e.i(722319),L=e.i(8398),D=e.i(279728);e.i(765846);var A=e.i(602716),K=e.i(328052);e.i(262370);var B=e.i(135551);let F=(e,t)=>new B.FastColor(e).setA(t).toRgbString(),z=(e,t)=>new B.FastColor(e).lighten(t).toHexString(),H=e=>{let t=(0,A.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]}},V=(e,t)=>{let n=e||"#000",r=t||"#fff";return{colorBgBase:n,colorTextBase:r,colorText:F(r,.85),colorTextSecondary:F(r,.65),colorTextTertiary:F(r,.45),colorTextQuaternary:F(r,.25),colorFill:F(r,.18),colorFillSecondary:F(r,.12),colorFillTertiary:F(r,.08),colorFillQuaternary:F(r,.04),colorBgSolid:F(r,.95),colorBgSolidHover:F(r,1),colorBgSolidActive:F(r,.9),colorBgElevated:z(n,12),colorBgContainer:z(n,8),colorBgLayout:z(n,0),colorBgSpotlight:z(n,26),colorBgBlur:F(r,.04),colorBorder:z(n,26),colorBorderSecondary:z(n,19)}},W={defaultSeed:P.defaultConfig.token,useToken:function(){let[e,t,n]=(0,M.useToken)();return{theme:e,token:t,hashId:n}},defaultAlgorithm:R.default,darkAlgorithm:(e,t)=>{let n=Object.keys(T.defaultPresetColors).map(t=>{let n=(0,A.generate)(e[t],{theme:"dark"});return Array.from({length:10},()=>1).reduce((e,r,l)=>(e[`${t}-${l+1}`]=n[l],e[`${t}${l+1}`]=n[l],e),{})}).reduce((e,t)=>e=Object.assign(Object.assign({},e),t),{}),r=null!=t?t:(0,R.default)(e),l=(0,K.default)(e,{generateColorPalettes:H,generateNeutralColorPalettes:V});return Object.assign(Object.assign(Object.assign(Object.assign({},r),n),l),{colorPrimaryBg:l.colorPrimaryBorder,colorPrimaryBgHover:l.colorPrimaryBorderHover})},compactAlgorithm:(e,t)=>{let n=null!=t?t:(0,R.default)(e),r=n.fontSizeSM,l=n.controlHeight-4;return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},n),function(e){let{sizeUnit:t,sizeStep:n}=e,r=n-2;return{sizeXXL:t*(r+10),sizeXL:t*(r+6),sizeLG:t*(r+2),sizeMD:t*(r+2),sizeMS:t*(r+1),size:t*r,sizeSM:t*r,sizeXS:t*(r-1),sizeXXS:t*(r-1)}}(null!=t?t:e)),(0,D.default)(r)),{controlHeight:l}),(0,L.default)(Object.assign(Object.assign({},n),{controlHeight:l})))},getDesignToken:e=>{let t=(null==e?void 0:e.algorithm)?(0,_.createTheme)(e.algorithm):$.default,n=Object.assign(Object.assign({},T.default),null==e?void 0:e.token);return(0,O.getComputedToken)(n,{override:null==e?void 0:e.token},t,I.default)},defaultConfig:P.defaultConfig,_internalContext:P.DesignTokenContext};e.s(["theme",0,W],368869);var U=e.i(270377);function q({isOpen:e,title:a,alertMessage:o,message:i,resourceInformationTitle:s,resourceInformation:d,onCancel:c,onOk:u,confirmLoading:f,requiredConfirmation:m}){let{Title:p,Text:g}=E.Typography,{token:h}=W.useToken(),[x,v]=(0,l.useState)("");return(0,l.useEffect)(()=>{e&&v("")},[e]),(0,t.jsx)(N.Modal,{title:a,open:e,onOk:u,onCancel:c,confirmLoading:f,okText:f?"Deleting...":"Delete",cancelText:"Cancel",okButtonProps:{danger:!0,disabled:!!m&&x!==m||f},cancelButtonProps:{disabled:f},children:(0,t.jsxs)("div",{className:"space-y-4",children:[o&&(0,t.jsx)(n.Alert,{message:o,type:"warning"}),(0,t.jsx)(r.Card,{title:s,className:"mt-4",styles:{body:{padding:"16px"},header:{backgroundColor:h.colorErrorBg,borderColor:h.colorErrorBorder}},style:{backgroundColor:h.colorErrorBg,borderColor:h.colorErrorBorder},children:(0,t.jsx)(C,{column:1,size:"small",children:d&&d.map(({label:e,value:n,...r})=>(0,t.jsx)(C.Item,{label:(0,t.jsx)("span",{className:"font-semibold",children:e}),children:(0,t.jsx)(g,{...r,children:n??"-"})},e))})}),(0,t.jsx)("div",{children:(0,t.jsx)(g,{children:i})}),m&&(0,t.jsxs)("div",{className:"mb-6 mt-4 pt-4 border-t border-gray-200 dark:border-gray-700",children:[(0,t.jsxs)(g,{className:"block text-base font-medium text-gray-700 dark:text-gray-300 mb-2",children:[(0,t.jsx)(g,{children:"Type "}),(0,t.jsx)(g,{strong:!0,type:"danger",children:m}),(0,t.jsx)(g,{children:" to confirm deletion:"})]}),(0,t.jsx)(S.Input,{value:x,onChange:e=>v(e.target.value),placeholder:m,className:"rounded-md",prefix:(0,t.jsx)(U.ExclamationCircleOutlined,{style:{color:h.colorError}}),autoFocus:!0})]})]})})}e.s(["default",()=>q],127952)},950724,(e,t,n)=>{t.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},100236,(e,t,n)=>{t.exports=e.g&&e.g.Object===Object&&e.g},139088,(e,t,n)=>{var r=e.r(100236),l="object"==typeof self&&self&&self.Object===Object&&self;t.exports=r||l||Function("return this")()},631926,(e,t,n)=>{var r=e.r(139088);t.exports=function(){return r.Date.now()}},748891,(e,t,n)=>{var r=/\s/;t.exports=function(e){for(var t=e.length;t--&&r.test(e.charAt(t)););return t}},830364,(e,t,n)=>{var r=e.r(748891),l=/^\s+/;t.exports=function(e){return e?e.slice(0,r(e)+1).replace(l,""):e}},630353,(e,t,n)=>{t.exports=e.r(139088).Symbol},243436,(e,t,n)=>{var r=e.r(630353),l=Object.prototype,a=l.hasOwnProperty,o=l.toString,i=r?r.toStringTag:void 0;t.exports=function(e){var t=a.call(e,i),n=e[i];try{e[i]=void 0;var r=!0}catch(e){}var l=o.call(e);return r&&(t?e[i]=n:delete e[i]),l}},223243,(e,t,n)=>{var r=Object.prototype.toString;t.exports=function(e){return r.call(e)}},377684,(e,t,n)=>{var r=e.r(630353),l=e.r(243436),a=e.r(223243),o=r?r.toStringTag:void 0;t.exports=function(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":o&&o in Object(e)?l(e):a(e)}},877289,(e,t,n)=>{t.exports=function(e){return null!=e&&"object"==typeof e}},361884,(e,t,n)=>{var r=e.r(377684),l=e.r(877289);t.exports=function(e){return"symbol"==typeof e||l(e)&&"[object Symbol]"==r(e)}},773759,(e,t,n)=>{var r=e.r(830364),l=e.r(950724),a=e.r(361884),o=0/0,i=/^[-+]0x[0-9a-f]+$/i,s=/^0b[01]+$/i,d=/^0o[0-7]+$/i,c=parseInt;t.exports=function(e){if("number"==typeof e)return e;if(a(e))return o;if(l(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=l(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=r(e);var n=s.test(e);return n||d.test(e)?c(e.slice(2),n?2:8):i.test(e)?o:+e}},374009,(e,t,n)=>{var r=e.r(950724),l=e.r(631926),a=e.r(773759),o=Math.max,i=Math.min;t.exports=function(e,t,n){var s,d,c,u,f,m,p=0,g=!1,h=!1,x=!0;if("function"!=typeof e)throw TypeError("Expected a function");function v(t){var n=s,r=d;return s=d=void 0,p=t,u=e.apply(r,n)}function b(e){var n=e-m,r=e-p;return void 0===m||n>=t||n<0||h&&r>=c}function y(){var e,n,r,a=l();if(b(a))return w(a);f=setTimeout(y,(e=a-m,n=a-p,r=t-e,h?i(r,c-n):r))}function w(e){return(f=void 0,x&&s)?v(e):(s=d=void 0,u)}function j(){var e,n=l(),r=b(n);if(s=arguments,d=this,m=n,r){if(void 0===f)return p=e=m,f=setTimeout(y,t),g?v(e):u;if(h)return clearTimeout(f),f=setTimeout(y,t),v(m)}return void 0===f&&(f=setTimeout(y,t)),u}return t=a(t)||0,r(n)&&(g=!!n.leading,c=(h="maxWait"in n)?o(a(n.maxWait)||0,t):c,x="trailing"in n?!!n.trailing:x),j.cancel=function(){void 0!==f&&clearTimeout(f),p=0,s=m=d=f=void 0},j.flush=function(){return void 0===f?u:w(l())},j}},436289,503269,214520,814379,992704,684653,877891,401141,952744,605083,101852,249578,571616,e=>{"use strict";var t=e.i(271645);function n(e,t){return null!==e&&null!==t&&"object"==typeof e&&"object"==typeof t&&"id"in e&&"id"in t?e.id===t.id:e===t}function r(e=n){return(0,t.useCallback)((t,n)=>"string"==typeof e?(null==t?void 0:t[e])===(null==n?void 0:n[e]):e(t,n),[e])}e.s(["useByComparator",()=>r],436289);var l=e.i(914189);function a(e,n,r){let[a,o]=(0,t.useState)(r),i=void 0!==e,s=(0,t.useRef)(i),d=(0,t.useRef)(!1),c=(0,t.useRef)(!1);return!i||s.current||d.current?i||!s.current||c.current||(c.current=!0,s.current=i,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,s.current=i,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.")),[i?e:a,(0,l.useEvent)(e=>(i||o(e),null==n?void 0:n(e)))]}function o(e){let[n]=(0,t.useState)(e);return n}e.s(["useControllable",()=>a],503269),e.s(["useDefaultValue",()=>o],214520);var i=e.i(835696);function s(e,n){let r=(0,t.useRef)({left:0,top:0});if((0,i.useIsoMorphicEffect)(()=>{if(!n)return;let e=n.getBoundingClientRect();e&&(r.current=e)},[e,n]),null==n||!e||n===document.activeElement)return!1;let l=n.getBoundingClientRect();return l.top!==r.current.top||l.left!==r.current.left}function d(e,n=!1){let[r,l]=(0,t.useReducer)(()=>({}),{}),a=(0,t.useMemo)(()=>(function(e){if(null===e)return{width:0,height:0};let{width:t,height:n}=e.getBoundingClientRect();return{width:t,height:n}})(e),[e,r]);return(0,i.useIsoMorphicEffect)(()=>{if(!e)return;let t=new ResizeObserver(l);return t.observe(e),()=>{t.disconnect()}},[e]),n?{width:`${a.width}px`,height:`${a.height}px`}:a}e.s(["useDidElementMove",()=>s],814379),e.s(["useElementSize",()=>d],992704);var c=e.i(544508),u=e.i(402155);class f extends Map{constructor(e){super(),this.factory=e}get(e){let t=super.get(e);return void 0===t&&(t=this.factory(e),this.set(e,t)),t}}function m(e,t){let n=e(),r=new Set;return{getSnapshot:()=>n,subscribe:e=>(r.add(e),()=>r.delete(e)),dispatch(e,...l){let a=t[e].call(n,...l);a&&(n=a,r.forEach(e=>e()))}}}function p(e){return(0,t.useSyncExternalStore)(e.subscribe,e.getSnapshot,e.getSnapshot)}let g=new f(()=>m(()=>[],{ADD(e){return this.includes(e)?this:[...this,e]},REMOVE(e){let t=this.indexOf(e);if(-1===t)return this;let n=this.slice();return n.splice(t,1),n}}));function h(e,n){let r=g.get(n),l=(0,t.useId)(),a=p(r);if((0,i.useIsoMorphicEffect)(()=>{if(e)return r.dispatch("ADD",l),()=>r.dispatch("REMOVE",l)},[r,e]),!e)return!1;let o=a.indexOf(l),s=a.length;return -1===o&&(o=s,s+=1),o===s-1}let x=new Map,v=new Map;function b(e){var t;let n=null!=(t=v.get(e))?t:0;return v.set(e,n+1),0!==n||(x.set(e,{"aria-hidden":e.getAttribute("aria-hidden"),inert:e.inert}),e.setAttribute("aria-hidden","true"),e.inert=!0),()=>(function(e){var t;let n=null!=(t=v.get(e))?t:1;if(1===n?v.delete(e):v.set(e,n-1),1!==n)return;let r=x.get(e);r&&(null===r["aria-hidden"]?e.removeAttribute("aria-hidden"):e.setAttribute("aria-hidden",r["aria-hidden"]),e.inert=r.inert,x.delete(e))})(e)}function y(e,{allowed:t,disallowed:n}={}){let r=h(e,"inert-others");(0,i.useIsoMorphicEffect)(()=>{var e,l;if(!r)return;let a=(0,c.disposables)();for(let t of null!=(e=null==n?void 0:n())?e:[])t&&a.add(b(t));let o=null!=(l=null==t?void 0:t())?l:[];for(let e of o){if(!e)continue;let t=(0,u.getOwnerDocument)(e);if(!t)continue;let n=e.parentElement;for(;n&&n!==t.body;){for(let e of n.children)o.some(t=>e.contains(t))||a.add(b(e));n=n.parentElement}}return a.dispose},[r,t,n])}e.s(["useInertOthers",()=>y],684653);var w=e.i(941444);function j(e,n,r){let l=(0,w.useLatestValue)(e=>{let t=e.getBoundingClientRect();0===t.x&&0===t.y&&0===t.width&&0===t.height&&r()});(0,t.useEffect)(()=>{if(!e)return;let t=null===n?null:n instanceof HTMLElement?n:n.current;if(!t)return;let r=(0,c.disposables)();if("u">typeof ResizeObserver){let e=new ResizeObserver(()=>l.current(t));e.observe(t),r.add(()=>e.disconnect())}if("u">typeof IntersectionObserver){let e=new IntersectionObserver(()=>l.current(t));e.observe(t),r.add(()=>e.disconnect())}return()=>r.dispose()},[n,l,e])}e.s(["useOnDisappear",()=>j],877891);var k=e.i(652265);function C(){return/iPhone/gi.test(window.navigator.platform)||/Mac/gi.test(window.navigator.platform)&&window.navigator.maxTouchPoints>0}function S(e,n,r,l){let a=(0,w.useLatestValue)(r);(0,t.useEffect)(()=>{if(e)return document.addEventListener(n,t,l),()=>document.removeEventListener(n,t,l);function t(e){a.current(e)}},[e,n,l])}function N(e,n,r,l){let a=(0,w.useLatestValue)(r);(0,t.useEffect)(()=>{if(e)return window.addEventListener(n,t,l),()=>window.removeEventListener(n,t,l);function t(e){a.current(e)}},[e,n,l])}function E(e,n,r){let l=h(e,"outside-click"),a=(0,w.useLatestValue)(r),o=(0,t.useCallback)(function(e,t){if(e.defaultPrevented)return;let r=t(e);if(null!==r&&r.getRootNode().contains(r)&&r.isConnected){for(let t of function e(t){return"function"==typeof t?e(t()):Array.isArray(t)||t instanceof Set?t:[t]}(n))if(null!==t&&(t.contains(r)||e.composed&&e.composedPath().includes(t)))return;return(0,k.isFocusableElement)(r,k.FocusableMode.Loose)||-1===r.tabIndex||e.preventDefault(),a.current(e,r)}},[a,n]),i=(0,t.useRef)(null);S(l,"pointerdown",e=>{var t,n;i.current=(null==(n=null==(t=e.composedPath)?void 0:t.call(e))?void 0:n[0])||e.target},!0),S(l,"mousedown",e=>{var t,n;i.current=(null==(n=null==(t=e.composedPath)?void 0:t.call(e))?void 0:n[0])||e.target},!0),S(l,"click",e=>{C()||/Android/gi.test(window.navigator.userAgent)||i.current&&(o(e,()=>i.current),i.current=null)},!0);let s=(0,t.useRef)({x:0,y:0});S(l,"touchstart",e=>{s.current.x=e.touches[0].clientX,s.current.y=e.touches[0].clientY},!0),S(l,"touchend",e=>{let t={x:e.changedTouches[0].clientX,y:e.changedTouches[0].clientY};if(!(Math.abs(t.x-s.current.x)>=30||Math.abs(t.y-s.current.y)>=30))return o(e,()=>e.target instanceof HTMLElement?e.target:null)},!0),N(l,"blur",e=>o(e,()=>window.document.activeElement instanceof HTMLIFrameElement?window.document.activeElement:null),!0)}function _(...e){return(0,t.useMemo)(()=>(0,u.getOwnerDocument)(...e),[...e])}e.s(["useWindowEvent",()=>N],401141),e.s(["useOutsideClick",()=>E],952744),e.s(["useOwnerDocument",()=>_],605083);let O=m(()=>new Map,{PUSH(e,t){var n;let r=null!=(n=this.get(e))?n:{doc:e,count:0,d:(0,c.disposables)(),meta:new Set};return r.count++,r.meta.add(t),this.set(e,r),this},POP(e,t){let n=this.get(e);return n&&(n.count--,n.meta.delete(t)),this},SCROLL_PREVENT({doc:e,d:t,meta:n}){let r,l={doc:e,d:t,meta:function(e){let t={};for(let n of e)Object.assign(t,n(t));return t}(n)},a=[C()?{before({doc:e,d:t,meta:n}){function r(e){return n.containers.flatMap(e=>e()).some(t=>t.contains(e))}t.microTask(()=>{var n;if("auto"!==window.getComputedStyle(e.documentElement).scrollBehavior){let n=(0,c.disposables)();n.style(e.documentElement,"scrollBehavior","auto"),t.add(()=>t.microTask(()=>n.dispose()))}let l=null!=(n=window.scrollY)?n:window.pageYOffset,a=null;t.addEventListener(e,"click",t=>{if(t.target instanceof HTMLElement)try{let n=t.target.closest("a");if(!n)return;let{hash:l}=new URL(n.href),o=e.querySelector(l);o&&!r(o)&&(a=o)}catch{}},!0),t.addEventListener(e,"touchstart",e=>{if(e.target instanceof HTMLElement)if(r(e.target)){let n=e.target;for(;n.parentElement&&r(n.parentElement);)n=n.parentElement;t.style(n,"overscrollBehavior","contain")}else t.style(e.target,"touchAction","none")}),t.addEventListener(e,"touchmove",e=>{if(e.target instanceof HTMLElement&&"INPUT"!==e.target.tagName)if(r(e.target)){let t=e.target;for(;t.parentElement&&""!==t.dataset.headlessuiPortal&&!(t.scrollHeight>t.clientHeight||t.scrollWidth>t.clientWidth);)t=t.parentElement;""===t.dataset.headlessuiPortal&&e.preventDefault()}else e.preventDefault()},{passive:!1}),t.add(()=>{var e;l!==(null!=(e=window.scrollY)?e:window.pageYOffset)&&window.scrollTo(0,l),a&&a.isConnected&&(a.scrollIntoView({block:"nearest"}),a=null)})})}}:{},{before({doc:e}){var t;let n=e.documentElement;r=Math.max(0,(null!=(t=e.defaultView)?t:window).innerWidth-n.clientWidth)},after({doc:e,d:t}){let n=e.documentElement,l=Math.max(0,n.clientWidth-n.offsetWidth),a=Math.max(0,r-l);t.style(n,"paddingRight",`${a}px`)}},{before({doc:e,d:t}){t.style(e.documentElement,"overflow","hidden")}}];a.forEach(({before:e})=>null==e?void 0:e(l)),a.forEach(({after:e})=>null==e?void 0:e(l))},SCROLL_ALLOW({d:e}){e.dispose()},TEARDOWN({doc:e}){this.delete(e)}});function $(e,t,n=()=>[document.body]){!function(e,t,n=()=>({containers:[]})){let r=p(O),l=t?r.get(t):void 0;l&&l.count,(0,i.useIsoMorphicEffect)(()=>{if(!(!t||!e))return O.dispatch("PUSH",t,n),()=>O.dispatch("POP",t,n)},[e,t])}(h(e,"scroll-lock"),t,e=>{var t;return{containers:[...null!=(t=e.containers)?t:[],n]}})}O.subscribe(()=>{let e=O.getSnapshot(),t=new Map;for(let[n]of e)t.set(n,n.documentElement.style.overflow);for(let n of e.values()){let e="hidden"===t.get(n.doc),r=0!==n.count;(r&&!e||!r&&e)&&O.dispatch(n.count>0?"SCROLL_PREVENT":"SCROLL_ALLOW",n),0===n.count&&O.dispatch("TEARDOWN",n)}}),e.s(["useScrollLock",()=>$],101852);let T=/([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g;function I(e){var t,n;let r=null!=(t=e.innerText)?t:"",l=e.cloneNode(!0);if(!(l instanceof HTMLElement))return r;let a=!1;for(let e of l.querySelectorAll('[hidden],[aria-hidden],[role="img"]'))e.remove(),a=!0;let o=a?null!=(n=l.innerText)?n:"":r;return T.test(o)&&(o=o.replace(T,"")),o}function P(e){let n=(0,t.useRef)(""),r=(0,t.useRef)("");return(0,l.useEvent)(()=>{let t=e.current;if(!t)return"";let l=t.innerText;if(n.current===l)return r.current;let a=(function(e){let t=e.getAttribute("aria-label");if("string"==typeof t)return t.trim();let n=e.getAttribute("aria-labelledby");if(n){let e=n.split(" ").map(e=>{let t=document.getElementById(e);if(t){let e=t.getAttribute("aria-label");return"string"==typeof e?e.trim():I(t).trim()}return null}).filter(Boolean);if(e.length>0)return e.join(", ")}return I(e).trim()})(t).trim().toLowerCase();return n.current=l,r.current=a,a})}function M(e){return[e.screenX,e.screenY]}function R(){let e=(0,t.useRef)([-1,-1]);return{wasMoved(t){let n=M(t);return(e.current[0]!==n[0]||e.current[1]!==n[1])&&(e.current=n,!0)},update(t){e.current=M(t)}}}e.s(["useTextValue",()=>P],249578),e.s(["useTrackedPointer",()=>R],571616)},83733,e=>{"use strict";let t;var n,r,l=e.i(247167),a=e.i(271645),o=e.i(544508),i=e.i(746725),s=e.i(835696);void 0!==l.default&&"u">typeof globalThis&&"u">typeof Element&&(null==(n=null==l.default?void 0:l.default.env)?void 0:n.NODE_ENV)==="test"&&void 0===(null==(r=null==Element?void 0:Element.prototype)?void 0:r.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 d=((t=d||{})[t.None=0]="None",t[t.Closed=1]="Closed",t[t.Enter=2]="Enter",t[t.Leave=4]="Leave",t);function c(e){let t={};for(let n in e)!0===e[n]&&(t[`data-${n}`]="");return t}function u(e,t,n,r){let[l,d]=(0,a.useState)(n),{hasFlag:c,addFlag:u,removeFlag:f}=function(e=0){let[t,n]=(0,a.useState)(e),r=(0,a.useCallback)(e=>n(e),[t]),l=(0,a.useCallback)(e=>n(t=>t|e),[t]),o=(0,a.useCallback)(e=>(t&e)===e,[t]);return{flags:t,setFlag:r,addFlag:l,hasFlag:o,removeFlag:(0,a.useCallback)(e=>n(t=>t&~e),[n]),toggleFlag:(0,a.useCallback)(e=>n(t=>t^e),[n])}}(e&&l?3:0),m=(0,a.useRef)(!1),p=(0,a.useRef)(!1),g=(0,i.useDisposables)();return(0,s.useIsoMorphicEffect)(()=>{var l;if(e){if(n&&d(!0),!t){n&&u(3);return}return null==(l=null==r?void 0:r.start)||l.call(r,n),function(e,{prepare:t,run:n,done:r,inFlight:l}){let a=(0,o.disposables)();return function(e,{inFlight:t,prepare:n}){if(null!=t&&t.current)return n();let r=e.style.transition;e.style.transition="none",n(),e.offsetHeight,e.style.transition=r}(e,{prepare:t,inFlight:l}),a.nextFrame(()=>{n(),a.requestAnimationFrame(()=>{a.add(function(e,t){var n,r;let l=(0,o.disposables)();if(!e)return l.dispose;let a=!1;l.add(()=>{a=!0});let i=null!=(r=null==(n=e.getAnimations)?void 0:n.call(e).filter(e=>e instanceof CSSTransition))?r:[];return 0===i.length?t():Promise.allSettled(i.map(e=>e.finished)).then(()=>{a||t()}),l.dispose}(e,r))})}),a.dispose}(t,{inFlight:m,prepare(){p.current?p.current=!1:p.current=m.current,m.current=!0,p.current||(n?(u(3),f(4)):(u(4),f(2)))},run(){p.current?n?(f(3),u(4)):(f(4),u(3)):n?f(1):u(1)},done(){var e;p.current&&"function"==typeof t.getAnimations&&t.getAnimations().length>0||(m.current=!1,f(7),n||d(!1),null==(e=null==r?void 0:r.end)||e.call(r,n))}})}},[e,n,t,g]),e?[l,{closed:c(1),enter:c(2),leave:c(4),transition:c(2)||c(4)}]:[n,{closed:void 0,enter:void 0,leave:void 0,transition:void 0}]}e.s(["transitionDataAttributes",()=>c,"useTransition",()=>u],83733)},601893,919751,694421,140721,904016,942803,e=>{"use strict";var t=e.i(271645);let n=(0,t.createContext)(void 0);function r(){return(0,t.useContext)(n)}e.s(["useDisabled",()=>r],601893);var l=e.i(953760),a=e.i(174080),o="u">typeof document?t.useLayoutEffect:function(){};function i(e,t){let n,r,l;if(e===t)return!0;if(typeof e!=typeof t)return!1;if("function"==typeof e&&e.toString()===t.toString())return!0;if(e&&t&&"object"==typeof e){if(Array.isArray(e)){if((n=e.length)!==t.length)return!1;for(r=n;0!=r--;)if(!i(e[r],t[r]))return!1;return!0}if((n=(l=Object.keys(e)).length)!==Object.keys(t).length)return!1;for(r=n;0!=r--;)if(!({}).hasOwnProperty.call(t,l[r]))return!1;for(r=n;0!=r--;){let n=l[r];if(("_owner"!==n||!e.$$typeof)&&!i(e[n],t[n]))return!1}return!0}return e!=e&&t!=t}function s(e){return"u"{n.current=e}),n}let u=(e,t)=>({...(0,l.offset)(e),options:[e,t]});e.i(247167);var f=e.i(229315),m=e.i(343084);e.i(397126);let p={...t},g=p.useInsertionEffect||(e=>e());function h(e){let n=t.useRef(()=>{});return g(()=>{n.current=e}),t.useCallback(function(){for(var e=arguments.length,t=Array(e),r=0;rtypeof document?t.useLayoutEffect:t.useEffect;let v=!1,b=0,y=()=>"floating-ui-"+Math.random().toString(36).slice(2,6)+b++,w=p.useId||function(){let[e,n]=t.useState(()=>v?y():void 0);return x(()=>{null==e&&n(y())},[]),t.useEffect(()=>{v=!0},[]),e},j=t.createContext(null),k=t.createContext(null),C="active",S="selected";function N(e,t,n){let r=new Map,l="item"===n,a=e;if(l&&e){let{[C]:t,[S]:n,...r}=e;a=r}return{..."floating"===n&&{tabIndex:-1,"data-floating-ui-focusable":""},...a,...t.map(t=>{let r=t?t[n]:null;return"function"==typeof r?e?r(e):null:r}).concat(e).reduce((e,t)=>(t&&Object.entries(t).forEach(t=>{let[n,a]=t;if(!(l&&[C,S].includes(n)))if(0===n.indexOf("on")){if(r.has(n)||r.set(n,[]),"function"==typeof a){var o;null==(o=r.get(n))||o.push(a),e[n]=function(){for(var e,t=arguments.length,l=Array(t),a=0;ae(...l)).find(e=>void 0!==e)}}}else e[n]=a}),e),{})}}function E(e,t){return{...e,rects:{...e.rects,floating:{...e.rects.floating,height:t}}}}var _=e.i(746725),O=e.i(914189),$=e.i(835696);let T=(0,t.createContext)({styles:void 0,setReference:()=>{},setFloating:()=>{},getReferenceProps:()=>({}),getFloatingProps:()=>({}),slot:{}});T.displayName="FloatingContext";let I=(0,t.createContext)(null);function P(e){return(0,t.useMemo)(()=>e?"string"==typeof e?{to:e}:e:null,[e])}function M(){return(0,t.useContext)(T).setReference}function R(){return(0,t.useContext)(T).getReferenceProps}function L(){let{getFloatingProps:e,slot:n}=(0,t.useContext)(T);return(0,t.useCallback)((...t)=>Object.assign({},e(...t),{"data-anchor":n.anchor}),[e,n])}function D(e=null){!1===e&&(e=null),"string"==typeof e&&(e={to:e});let n=(0,t.useContext)(I),r=(0,t.useMemo)(()=>e,[JSON.stringify(e,(e,t)=>{var n;return null!=(n=null==t?void 0:t.outerHTML)?n:t})]);(0,$.useIsoMorphicEffect)(()=>{null==n||n(null!=r?r:null)},[n,r]);let l=(0,t.useContext)(T);return(0,t.useMemo)(()=>[l.setFloating,e?l.styles:{}],[l.setFloating,e,l.styles])}function A({children:e,enabled:n=!0}){var r,p,g,v,b,y,C;let S,_,P,M,R,L,D,A,B,F,z,H,V,W,U,q,[G,X]=(0,t.useState)(null),[Q,Y]=(0,t.useState)(0),J=(0,t.useRef)(null),[Z,ee]=(0,t.useState)(null);p=Z,(0,$.useIsoMorphicEffect)(()=>{if(!p)return;let e=new MutationObserver(()=>{let e=window.getComputedStyle(p).maxHeight,t=parseFloat(e);if(isNaN(t))return;let n=parseInt(e);isNaN(n)||t!==n&&(p.style.maxHeight=`${Math.ceil(t)}px`)});return e.observe(p,{attributes:!0,attributeFilter:["style"]}),()=>{e.disconnect()}},[p]);let et=n&&null!==G&&null!==Z,{to:en="bottom",gap:er=0,offset:el=0,padding:ea=0,inner:eo}=(g=G,v=Z,S=K(null!=(b=null==g?void 0:g.gap)?b:"var(--anchor-gap, 0)",v),_=K(null!=(y=null==g?void 0:g.offset)?y:"var(--anchor-offset, 0)",v),P=K(null!=(C=null==g?void 0:g.padding)?C:"var(--anchor-padding, 0)",v),{...g,gap:S,offset:_,padding:P}),[ei,es="center"]=en.split(" ");(0,$.useIsoMorphicEffect)(()=>{et&&Y(0)},[et]);let{refs:ed,floatingStyles:ec,context:eu}=function(e){void 0===e&&(e={});let{nodeId:n}=e,r=function(e){var n;let{open:r=!1,onOpenChange:l,elements:a}=e,o=w(),i=t.useRef({}),[s]=t.useState(()=>{let e;return e=new Map,{emit(t,n){var r;null==(r=e.get(t))||r.forEach(e=>e(n))},on(t,n){e.set(t,[...e.get(t)||[],n])},off(t,n){var r;e.set(t,(null==(r=e.get(t))?void 0:r.filter(e=>e!==n))||[])}}}),d=null!=((null==(n=t.useContext(j))?void 0:n.id)||null),[c,u]=t.useState(a.reference),f=h((e,t,n)=>{i.current.openEvent=e?t:void 0,s.emit("openchange",{open:e,event:t,reason:n,nested:d}),null==l||l(e,t,n)}),m=t.useMemo(()=>({setPositionReference:u}),[]),p=t.useMemo(()=>({reference:c||a.reference||null,floating:a.floating||null,domReference:a.reference}),[c,a.reference,a.floating]);return t.useMemo(()=>({dataRef:i,open:r,onOpenChange:f,elements:p,events:s,floatingId:o,refs:m}),[r,f,p,s,o,m])}({...e,elements:{reference:null,floating:null,...e.elements}}),u=e.rootContext||r,m=u.elements,[p,g]=t.useState(null),[v,b]=t.useState(null),y=(null==m?void 0:m.domReference)||p,C=t.useRef(null),S=t.useContext(k);x(()=>{y&&(C.current=y)},[y]);let N=function(e){void 0===e&&(e={});let{placement:n="bottom",strategy:r="absolute",middleware:u=[],platform:f,elements:{reference:m,floating:p}={},transform:g=!0,whileElementsMounted:h,open:x}=e,[v,b]=t.useState({x:0,y:0,strategy:r,placement:n,middlewareData:{},isPositioned:!1}),[y,w]=t.useState(u);i(y,u)||w(u);let[j,k]=t.useState(null),[C,S]=t.useState(null),N=t.useCallback(e=>{e!==$.current&&($.current=e,k(e))},[]),E=t.useCallback(e=>{e!==T.current&&(T.current=e,S(e))},[]),_=m||j,O=p||C,$=t.useRef(null),T=t.useRef(null),I=t.useRef(v),P=null!=h,M=c(h),R=c(f),L=c(x),D=t.useCallback(()=>{if(!$.current||!T.current)return;let e={placement:n,strategy:r,middleware:y};R.current&&(e.platform=R.current),(0,l.computePosition)($.current,T.current,e).then(e=>{let t={...e,isPositioned:!1!==L.current};A.current&&!i(I.current,t)&&(I.current=t,a.flushSync(()=>{b(t)}))})},[y,n,r,R,L]);o(()=>{!1===x&&I.current.isPositioned&&(I.current.isPositioned=!1,b(e=>({...e,isPositioned:!1})))},[x]);let A=t.useRef(!1);o(()=>(A.current=!0,()=>{A.current=!1}),[]),o(()=>{if(_&&($.current=_),O&&(T.current=O),_&&O){if(M.current)return M.current(_,O,D);D()}},[_,O,D,M,P]);let K=t.useMemo(()=>({reference:$,floating:T,setReference:N,setFloating:E}),[N,E]),B=t.useMemo(()=>({reference:_,floating:O}),[_,O]),F=t.useMemo(()=>{let e={position:r,left:0,top:0};if(!B.floating)return e;let t=d(B.floating,v.x),n=d(B.floating,v.y);return g?{...e,transform:"translate("+t+"px, "+n+"px)",...s(B.floating)>=1.5&&{willChange:"transform"}}:{position:r,left:t,top:n}},[r,g,B.floating,v.x,v.y]);return t.useMemo(()=>({...v,update:D,refs:K,elements:B,floatingStyles:F}),[v,D,K,B,F])}({...e,elements:{...m,...v&&{reference:v}}}),E=t.useCallback(e=>{let t=(0,f.isElement)(e)?{getBoundingClientRect:()=>e.getBoundingClientRect(),contextElement:e}:e;b(t),N.refs.setReference(t)},[N.refs]),_=t.useCallback(e=>{((0,f.isElement)(e)||null===e)&&(C.current=e,g(e)),((0,f.isElement)(N.refs.reference.current)||null===N.refs.reference.current||null!==e&&!(0,f.isElement)(e))&&N.refs.setReference(e)},[N.refs]),O=t.useMemo(()=>({...N.refs,setReference:_,setPositionReference:E,domReference:C}),[N.refs,_,E]),$=t.useMemo(()=>({...N.elements,domReference:y}),[N.elements,y]),T=t.useMemo(()=>({...N,...u,refs:O,elements:$,nodeId:n}),[N,O,$,n,u]);return x(()=>{u.dataRef.current.floatingContext=T;let e=null==S?void 0:S.nodesRef.current.find(e=>e.id===n);e&&(e.context=T)}),t.useMemo(()=>({...N,context:T,refs:O,elements:$}),[N,O,$,T])}({open:et,placement:"selection"===ei?"center"===es?"bottom":`bottom-${es}`:"center"===es?`${ei}`:`${ei}-${es}`,strategy:"absolute",transform:!1,middleware:[u({mainAxis:"selection"===ei?0:er,crossAxis:el}),(M={padding:ea},{...(0,l.shift)(M),options:[M,R]}),"selection"!==ei&&(L={padding:ea},{...(0,l.flip)(L),options:[L,D]}),"selection"===ei&&eo?{name:"inner",options:A={...eo,padding:ea,overflowRef:J,offset:Q,minItemsVisible:4,referenceOverflowThreshold:ea,onFallbackChange(e){var t,n;if(!e)return;let r=eu.elements.floating;if(!r)return;let l=parseFloat(getComputedStyle(r).scrollPaddingBottom)||0,a=Math.min(4,r.childElementCount),o=0,i=0;for(let e of null!=(n=null==(t=eu.elements.floating)?void 0:t.childNodes)?n:[])if(e instanceof HTMLElement){let t=e.offsetTop,n=t+e.clientHeight+l,s=r.scrollTop,d=s+r.clientHeight;if(t>=s&&n<=d)a--;else{i=Math.max(0,Math.min(n,d)-Math.max(t,s)),o=e.clientHeight;break}}a>=1&&Y(e=>{let t=o*a-i+l;return e>=t?e:t})}},async fn(e){let{listRef:t,overflowRef:n,onFallbackChange:r,offset:o=0,index:i=0,minItemsVisible:s=4,referenceOverflowThreshold:d=0,scrollRef:c,...f}=(0,m.evaluate)(A,e),{rects:p,elements:{floating:g}}=e,h=t.current[i],x=(null==c?void 0:c.current)||g,v=g.clientTop||x.clientTop,b=0!==g.clientTop,y=0!==x.clientTop,w=g===x;if(!h)return{};let j={...e,...await u(-h.offsetTop-g.clientTop-p.reference.height/2-h.offsetHeight/2-o).fn(e)},k=await (0,l.detectOverflow)(E(j,x.scrollHeight+v+g.clientTop),f),C=await (0,l.detectOverflow)(j,{...f,elementContext:"reference"}),S=(0,m.max)(0,k.top),N=j.y+S,_=(x.scrollHeight>x.clientHeight?e=>e:m.round)((0,m.max)(0,x.scrollHeight+(b&&w||y?2*v:0)-S-(0,m.max)(0,k.bottom)));if(x.style.maxHeight=_+"px",x.scrollTop=S,r){let e=x.offsetHeight=-d||C.bottom>=-d;a.flushSync(()=>r(e))}return n&&(n.current=await (0,l.detectOverflow)(E({...j,y:N},x.offsetHeight+v+g.clientTop),f)),{y:N}}}:null,(B={padding:ea,apply({availableWidth:e,availableHeight:t,elements:n}){Object.assign(n.floating.style,{overflow:"auto",maxWidth:`${e}px`,maxHeight:`min(var(--anchor-max-height, 100vh), ${t}px)`})}},{...(0,l.size)(B),options:[B,F]})].filter(Boolean),whileElementsMounted:l.autoUpdate}),[ef=ei,em=es]=eu.placement.split("-");"selection"===ei&&(ef="selection");let ep=(0,t.useMemo)(()=>({anchor:[ef,em].filter(Boolean).join(" ")}),[ef,em]),{getReferenceProps:eg,getFloatingProps:eh}=(z=(r=[function(e,n){let{open:r,elements:l}=e,{enabled:o=!0,overflowRef:i,scrollRef:s,onChange:d}=n,c=h(d),u=t.useRef(!1),f=t.useRef(null),m=t.useRef(null);t.useEffect(()=>{if(!o)return;function e(e){if(e.ctrlKey||!t||null==i.current)return;let n=e.deltaY,r=i.current.top>=-.5,l=i.current.bottom>=-.5,o=t.scrollHeight-t.clientHeight,s=n<0?-1:1,d=n<0?"max":"min";if(!(t.scrollHeight<=t.clientHeight))if(!r&&n>0||!l&&n<0)e.preventDefault(),a.flushSync(()=>{c(e=>e+Math[d](n,o*s))});else{let e;/firefox/i.test((e=navigator.userAgentData)&&Array.isArray(e.brands)?e.brands.map(e=>{let{brand:t,version:n}=e;return t+"/"+n}).join(" "):navigator.userAgent)&&(t.scrollTop+=n)}}let t=(null==s?void 0:s.current)||l.floating;if(r&&t)return t.addEventListener("wheel",e),requestAnimationFrame(()=>{f.current=t.scrollTop,null!=i.current&&(m.current={...i.current})}),()=>{f.current=null,m.current=null,t.removeEventListener("wheel",e)}},[o,r,l.floating,i,s,c]);let p=t.useMemo(()=>({onKeyDown(){u.current=!0},onWheel(){u.current=!1},onPointerMove(){u.current=!1},onScroll(){let e=(null==s?void 0:s.current)||l.floating;if(i.current&&e&&u.current){if(null!==f.current){let t=e.scrollTop-f.current;(i.current.bottom<-.5&&t<-1||i.current.top<-.5&&t>1)&&a.flushSync(()=>c(e=>e+t))}requestAnimationFrame(()=>{f.current=e.scrollTop})}}}),[l.floating,c,i,s]);return t.useMemo(()=>o?{floating:p}:{},[o,p])}(eu,{overflowRef:J,onChange:Y})]).map(e=>null==e?void 0:e.reference),H=r.map(e=>null==e?void 0:e.floating),V=r.map(e=>null==e?void 0:e.item),W=t.useCallback(e=>N(e,r,"reference"),z),U=t.useCallback(e=>N(e,r,"floating"),H),q=t.useCallback(e=>N(e,r,"item"),V),t.useMemo(()=>({getReferenceProps:W,getFloatingProps:U,getItemProps:q}),[W,U,q])),ex=(0,O.useEvent)(e=>{ee(e),ed.setFloating(e)});return t.createElement(I.Provider,{value:X},t.createElement(T.Provider,{value:{setFloating:ex,setReference:ed.setReference,styles:ec,getReferenceProps:eg,getFloatingProps:eh,slot:ep}},e))}function K(e,n,r){let l=(0,_.useDisposables)(),a=(0,O.useEvent)((e,t)=>{if(null==e)return[r,null];if("number"==typeof e)return[e,null];if("string"==typeof e){if(!t)return[r,null];let n=B(e,t);return[n,r=>{let a=function e(t){let n=/var\((.*)\)/.exec(t);if(n){let t=n[1].indexOf(",");if(-1===t)return[n[1]];let r=n[1].slice(0,t).trim(),l=n[1].slice(t+1).trim();return l?[r,...e(l)]:[r]}return[]}(e);{let o=a.map(e=>window.getComputedStyle(t).getPropertyValue(e));l.requestAnimationFrame(function i(){l.nextFrame(i);let s=!1;for(let[e,n]of a.entries()){let r=window.getComputedStyle(t).getPropertyValue(n);if(o[e]!==r){o[e]=r,s=!0;break}}if(!s)return;let d=B(e,t);n!==d&&(r(d),n=d)})}return l.dispose}]}return[r,null]}),o=(0,t.useMemo)(()=>a(e,n)[0],[e,n]),[i=o,s]=(0,t.useState)();return(0,$.useIsoMorphicEffect)(()=>{let[t,r]=a(e,n);if(s(t),r)return r(s)},[e,n]),i}function B(e,t){let n=document.createElement("div");t.appendChild(n),n.style.setProperty("margin-top","0px","important"),n.style.setProperty("margin-top",e,"important");let r=parseFloat(window.getComputedStyle(n).marginTop)||0;return t.removeChild(n),r}function F(e={},t=null,n=[]){for(let[r,l]of Object.entries(e))!function e(t,n,r){if(Array.isArray(r))for(let[l,a]of r.entries())e(t,z(n,l.toString()),a);else r instanceof Date?t.push([n,r.toISOString()]):"boolean"==typeof r?t.push([n,r?"1":"0"]):"string"==typeof r?t.push([n,r]):"number"==typeof r?t.push([n,`${r}`]):null==r?t.push([n,""]):F(r,n,t)}(n,z(t,r),l);return n}function z(e,t){return e?e+"["+t+"]":t}function H(e){var t,n;let r=null!=(t=null==e?void 0:e.form)?t:e.closest("form");if(r){for(let t of r.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=r.requestSubmit)||n.call(r)}}I.displayName="PlacementContext",e.s(["FloatingProvider",()=>A,"useFloatingPanel",()=>D,"useFloatingPanelProps",()=>L,"useFloatingReference",()=>M,"useFloatingReferenceProps",()=>R,"useResolvedAnchor",()=>P],919751),e.s(["attemptSubmit",()=>H,"objectToFormEntries",()=>F],694421);var V=e.i(700020),W=e.i(2788);let U=(0,t.createContext)(null);function q({children:e}){let n=(0,t.useContext)(U);if(!n)return t.default.createElement(t.default.Fragment,null,e);let{target:r}=n;return r?(0,a.createPortal)(t.default.createElement(t.default.Fragment,null,e),r):null}function G({data:e,form:n,disabled:r,onReset:l,overrides:a}){let[o,i]=(0,t.useState)(null),s=(0,_.useDisposables)();return(0,t.useEffect)(()=>{if(l&&o)return s.addEventListener(o,"reset",l)},[o,n,l]),t.default.createElement(q,null,t.default.createElement(X,{setForm:i,formId:n}),F(e).map(([e,l])=>t.default.createElement(W.Hidden,{features:W.HiddenFeatures.Hidden,...(0,V.compact)({key:e,as:"input",type:"hidden",hidden:!0,readOnly:!0,form:n,disabled:r,name:e,value:l,...a})})))}function X({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(W.Hidden,{features:W.HiddenFeatures.Hidden,as:"input",type:"hidden",hidden:!0,readOnly:!0,ref:t=>{if(!t)return;let n=t.closest("form");n&&e(n)}})}function Q(e,n){let[r,l]=(0,t.useState)(n);return e||r===n||l(n),e?r:n}e.s(["FormFields",()=>G],140721),e.s(["useFrozenData",()=>Q],904016);let Y=(0,t.createContext)(void 0);function J(){return(0,t.useContext)(Y)}e.s(["useProvidedId",()=>J],942803)},233137,233538,e=>{"use strict";let t;var n=e.i(271645);let r=(0,n.createContext)(null);r.displayName="OpenClosedContext";var l=((t=l||{})[t.Open=1]="Open",t[t.Closed=2]="Closed",t[t.Closing=4]="Closing",t[t.Opening=8]="Opening",t);function a(){return(0,n.useContext)(r)}function o({value:e,children:t}){return n.default.createElement(r.Provider,{value:e},t)}function i({children:e}){return n.default.createElement(r.Provider,{value:null},e)}function s(e){let t=e.parentElement,n=null;for(;t&&!(t instanceof HTMLFieldSetElement);)t instanceof HTMLLegendElement&&(n=t),t=t.parentElement;let r=(null==t?void 0:t.getAttribute("disabled"))==="";return!(r&&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))&&r}e.s(["OpenClosedProvider",()=>o,"ResetOpenClosedProvider",()=>i,"State",()=>l,"useOpenClosed",()=>a],233137),e.s(["isDisabledReactIssue7711",()=>s],233538)},35983,35889,722678,178677,635307,495470,333771,e=>{"use strict";let t,n,r,l,a;var o=e.i(290571),i=e.i(271645),s=e.i(429427),d=e.i(371330),c=e.i(174080),u=e.i(394487),f=e.i(436289),m=e.i(503269),p=e.i(214520),g=e.i(814379),h=e.i(746725),x=e.i(992704),v=e.i(914189),b=e.i(684653),y=e.i(835696),w=e.i(941444),j=e.i(877891),k=e.i(952744),C=e.i(605083),S=e.i(144279),N=e.i(101852),E=e.i(294316),_=e.i(249578),O=e.i(571616),$=e.i(83733),T=e.i(601893),I=e.i(919751),P=e.i(140721),M=e.i(904016),R=e.i(942803),L=e.i(233137),D=e.i(233538),A=((t=A||{})[t.First=0]="First",t[t.Previous=1]="Previous",t[t.Next=2]="Next",t[t.Last=3]="Last",t[t.Specific=4]="Specific",t[t.Nothing=5]="Nothing",t);function K(e,t){let n=t.resolveItems();if(n.length<=0)return null;let r=t.resolveActiveIndex(),l=null!=r?r:-1;switch(e.focus){case 0:for(let e=0;e=0;--e)if(!t.resolveDisabled(n[e],e,n))return e;return r;case 2:for(let e=l+1;e=0;--e)if(!t.resolveDisabled(n[e],e,n))return e;return r;case 4:for(let r=0;r0?e.join(" "):void 0,(0,i.useMemo)(()=>function(e){let n=(0,v.useEvent)(e=>(t(t=>[...t,e]),()=>t(t=>{let n=t.slice(),r=n.indexOf(e);return -1!==r&&n.splice(r,1),n}))),r=(0,i.useMemo)(()=>({register:n,slot:e.slot,name:e.name,props:e.props,value:e.value}),[n,e.slot,e.name,e.props,e.value]);return i.default.createElement(U.Provider,{value:r},e.children)},[t])]}U.displayName="DescriptionContext";let X=Object.assign((0,W.forwardRefWithAs)(function(e,t){let n=(0,i.useId)(),r=(0,T.useDisabled)(),{id:l=`headlessui-description-${n}`,...a}=e,o=function e(){let t=(0,i.useContext)(U);if(null===t){let t=Error("You used a component, but it is not inside a relevant parent.");throw Error.captureStackTrace&&Error.captureStackTrace(t,e),t}return t}(),s=(0,E.useSyncRefs)(t);(0,y.useIsoMorphicEffect)(()=>o.register(l),[l,o.register]);let d=r||!1,c=(0,i.useMemo)(()=>({...o.slot,disabled:d}),[o.slot,d]),u={ref:s,...o.props,id:l};return(0,W.useRender)()({ourProps:u,theirProps:a,slot:c,defaultTag:"p",name:o.name||"Description"})}),{});e.s(["Description",()=>X,"useDescribedBy",()=>q,"useDescriptions",()=>G],35889);var Q=e.i(998348);let Y=(0,i.createContext)(null);function J(e){var t,n,r;let l=null!=(n=null==(t=(0,i.useContext)(Y))?void 0:t.value)?n:void 0;return(null!=(r=null==e?void 0:e.length)?r:0)>0?[l,...e].filter(Boolean).join(" "):l}function Z({inherit:e=!1}={}){let t=J(),[n,r]=(0,i.useState)([]),l=e?[t,...n].filter(Boolean):n;return[l.length>0?l.join(" "):void 0,(0,i.useMemo)(()=>function(e){let t=(0,v.useEvent)(e=>(r(t=>[...t,e]),()=>r(t=>{let n=t.slice(),r=n.indexOf(e);return -1!==r&&n.splice(r,1),n}))),n=(0,i.useMemo)(()=>({register:t,slot:e.slot,name:e.name,props:e.props,value:e.value}),[t,e.slot,e.name,e.props,e.value]);return i.default.createElement(Y.Provider,{value:n},e.children)},[r])]}Y.displayName="LabelContext";let ee=Object.assign((0,W.forwardRefWithAs)(function(e,t){var n;let r=(0,i.useId)(),l=function e(){let t=(0,i.useContext)(Y);if(null===t){let t=Error("You used a